diff --git a/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/BitUtils.kt b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/BitUtils.kt index 31c3739..f62a9ee 100644 --- a/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/BitUtils.kt +++ b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/BitUtils.kt @@ -3,6 +3,7 @@ package com.rafambn.kflate.util import com.rafambn.kflate.huffman.FIXED_DISTANCE_EXTRA_BITS import com.rafambn.kflate.huffman.FIXED_DISTANCE_TREE import com.rafambn.kflate.huffman.FIXED_LENGTH_EXTRA_BITS +import com.rafambn.kflate.huffman.FIXED_LENGTH_BASE import com.rafambn.kflate.huffman.FIXED_DISTANCE_MAP import com.rafambn.kflate.huffman.FIXED_LENGTH_MAP import com.rafambn.kflate.huffman.FIXED_LENGTH_TREE @@ -104,9 +105,125 @@ internal fun writeBlock( blockLength: Int, bitPosition: Long ): Long { - var currentBitPosition = bitPosition - writeBits(output, currentBitPosition++, if (isFinal) 1 else 0) - literalFrequencies[256]++ + // Compare one midpoint split using the exact bit end position. The split + // candidate uses only two small frequency sets and never copies output. + val fullPlan = buildDeflateBlockPlan( + literalFrequencies, + distanceFrequencies, + extraBits, + blockStart, + blockLength, + bitPosition, + ) + val fullEnd = deflateBlockEndBitPosition(fullPlan, bitPosition, blockLength) + + if (blockStart < 0 || symbolCount < 2) { + return writeDeflateBlock( + data, + output, + isFinal, + symbols, + symbolStart = 0, + symbolCount = symbolCount, + blockStart = blockStart, + blockLength = blockLength, + bitPosition = bitPosition, + plan = fullPlan, + ) + } + + val firstSymbolCount = symbolCount / 2 + var firstBlockLength = 0 + for (index in 0 until firstSymbolCount) { + firstBlockLength += encodedSymbolByteLength(symbols[index]) + } + val secondBlockLength = blockLength - firstBlockLength + val firstLiteralFrequencies = IntArray(288) + val firstDistanceFrequencies = IntArray(32) + val firstExtraBits = populateBlockFrequencies( + symbols, + symbolStart = 0, + symbolCount = firstSymbolCount, + literalFrequencies = firstLiteralFrequencies, + distanceFrequencies = firstDistanceFrequencies, + ) + val secondLiteralFrequencies = IntArray(288) + val secondDistanceFrequencies = IntArray(32) + val secondExtraBits = populateBlockFrequencies( + symbols, + symbolStart = firstSymbolCount, + symbolCount = symbolCount - firstSymbolCount, + literalFrequencies = secondLiteralFrequencies, + distanceFrequencies = secondDistanceFrequencies, + ) + val firstPlan = buildDeflateBlockPlan( + firstLiteralFrequencies, + firstDistanceFrequencies, + firstExtraBits, + blockStart, + firstBlockLength, + bitPosition, + ) + val firstEnd = deflateBlockEndBitPosition(firstPlan, bitPosition, firstBlockLength) + val secondPlan = buildDeflateBlockPlan( + secondLiteralFrequencies, + secondDistanceFrequencies, + secondExtraBits, + blockStart + firstBlockLength, + secondBlockLength, + firstEnd, + ) + val splitEnd = deflateBlockEndBitPosition(secondPlan, firstEnd, secondBlockLength) + + if (splitEnd < fullEnd) { + val writtenFirstEnd = writeDeflateBlock( + data, + output, + false, + symbols, + symbolStart = 0, + symbolCount = firstSymbolCount, + blockStart = blockStart, + blockLength = firstBlockLength, + bitPosition = bitPosition, + plan = firstPlan, + ) + return writeDeflateBlock( + data, + output, + isFinal, + symbols, + symbolStart = firstSymbolCount, + symbolCount = symbolCount - firstSymbolCount, + blockStart = blockStart + firstBlockLength, + blockLength = secondBlockLength, + bitPosition = writtenFirstEnd, + plan = secondPlan, + ) + } + + return writeDeflateBlock( + data, + output, + isFinal, + symbols, + symbolStart = 0, + symbolCount = symbolCount, + blockStart = blockStart, + blockLength = blockLength, + bitPosition = bitPosition, + plan = fullPlan, + ) +} + +private fun buildDeflateBlockPlan( + literalFrequencies: IntArray, + distanceFrequencies: IntArray, + extraBits: Int, + blockStart: Int, + blockLength: Int, + bitPosition: Long, +): DeflateBlockPlan { var hasDistance = false for (frequency in distanceFrequencies) { if (frequency > 0) { @@ -114,72 +231,157 @@ internal fun writeBlock( break } } + literalFrequencies[256]++ if (!hasDistance) { distanceFrequencies[0] = 1 } - val (dynamicLiteralTree, maxLiteralBits) = buildHuffmanTreeFromFrequencies(literalFrequencies, 15) - val (dynamicDistanceTree, maxDistanceBits) = buildHuffmanTreeFromFrequencies(distanceFrequencies, 15) - val (literalCodeLengths, numLiteralCodes) = generateLengthCodes(dynamicLiteralTree) - val (distanceCodeLengths, numDistanceCodes) = generateLengthCodes(dynamicDistanceTree) + val dynamicLiteralTree = buildHuffmanTreeFromFrequencies(literalFrequencies, 15) + val dynamicDistanceTree = buildHuffmanTreeFromFrequencies(distanceFrequencies, 15) + val (literalCodeLengths, numLiteralCodes) = generateLengthCodes(dynamicLiteralTree.tree) + val (distanceCodeLengths, numDistanceCodes) = generateLengthCodes(dynamicDistanceTree.tree) val codeLengthFrequencies = IntArray(19) - for (i in literalCodeLengths.indices) { - codeLengthFrequencies[(literalCodeLengths[i].toInt() and 31)]++ + for (code in literalCodeLengths) { + codeLengthFrequencies[code.toInt() and 31]++ } - for (i in distanceCodeLengths.indices) { - codeLengthFrequencies[(distanceCodeLengths[i].toInt() and 31)]++ + for (code in distanceCodeLengths) { + codeLengthFrequencies[code.toInt() and 31]++ } - val (codeLengthTree, maxCodeLengthBits) = buildHuffmanTreeFromFrequencies(codeLengthFrequencies, 7) - val numCodeLengthCodes = countCodeLengthCodes(codeLengthTree) - - val fixedBlockLength = (blockLength + 5) shl 3 + val codeLengthTree = buildHuffmanTreeFromFrequencies(codeLengthFrequencies, 7) + val numCodeLengthCodes = countCodeLengthCodes(codeLengthTree.tree) + val codeLengthCodes = ShortArray(literalCodeLengths.size + distanceCodeLengths.size) + literalCodeLengths.copyInto(codeLengthCodes, endIndex = literalCodeLengths.size) + distanceCodeLengths.copyInto( + codeLengthCodes, + destinationOffset = literalCodeLengths.size, + endIndex = distanceCodeLengths.size, + ) val fixedTypedLength = calculateCodeLength(literalFrequencies, FIXED_LENGTH_TREE) + calculateCodeLength(distanceFrequencies, FIXED_DISTANCE_TREE) + extraBits - val dynamicTypedLength = calculateCodeLength(literalFrequencies, dynamicLiteralTree) + - calculateCodeLength(distanceFrequencies, dynamicDistanceTree) + extraBits + 14 + 3 * numCodeLengthCodes + - calculateCodeLength(codeLengthFrequencies, codeLengthTree) + 2 * codeLengthFrequencies[16] + - 3 * codeLengthFrequencies[17] + 7 * codeLengthFrequencies[18] + val dynamicTypedLength = calculateCodeLength(literalFrequencies, dynamicLiteralTree.tree) + + calculateCodeLength(distanceFrequencies, dynamicDistanceTree.tree) + extraBits + + 14 + 3 * numCodeLengthCodes + + calculateCodeLength(codeLengthFrequencies, codeLengthTree.tree) + + 2 * codeLengthFrequencies[16] + 3 * codeLengthFrequencies[17] + + 7 * codeLengthFrequencies[18] + + val usesStoredBlock = shouldUseStoredBlock( + blockStart, + storedBlockBitLength(blockLength, bitPosition), + fixedTypedLength, + dynamicTypedLength, + ) + val blockType = when { + usesStoredBlock -> DeflateBlockType.STORED + dynamicTypedLength < fixedTypedLength -> DeflateBlockType.DYNAMIC + else -> DeflateBlockType.FIXED + } + val typedLength = when (blockType) { + DeflateBlockType.DYNAMIC -> dynamicTypedLength + DeflateBlockType.FIXED -> fixedTypedLength + DeflateBlockType.STORED -> 0 + } + // A distance symbol is synthesized for an otherwise empty distance tree, + // but literal-only blocks do not emit that symbol in their token stream. + val syntheticDistanceBitLength = if (hasDistance || blockType == DeflateBlockType.STORED) { + 0 + } else { + val distanceTree = if (blockType == DeflateBlockType.DYNAMIC) { + dynamicDistanceTree.tree + } else { + FIXED_DISTANCE_TREE + } + calculateCodeLength(distanceFrequencies, distanceTree) + } - if (shouldUseStoredBlock(blockStart, fixedBlockLength, fixedTypedLength, dynamicTypedLength)) { - return writeFixedBlock(output, currentBitPosition, data.sliceArray(blockStart until blockStart + blockLength)) + return DeflateBlockPlan( + literalTree = dynamicLiteralTree, + distanceTree = dynamicDistanceTree, + codeLengthTree = codeLengthTree, + codeLengthCodes = codeLengthCodes, + numLiteralCodes = numLiteralCodes, + numDistanceCodes = numDistanceCodes, + fixedTypedLength = fixedTypedLength, + dynamicTypedLength = dynamicTypedLength, + blockType = blockType, + emittedTypedLength = typedLength - syntheticDistanceBitLength, + ) +} + +private fun deflateBlockEndBitPosition( + plan: DeflateBlockPlan, + bitPosition: Long, + blockLength: Int, +): Long { + if (plan.blockType == DeflateBlockType.STORED) { + val bytePosition = shiftToNextByte(bitPosition + 3L) + return (bytePosition.toLong() + 4L + blockLength.toLong()) * 8L } + return bitPosition + 3L + plan.emittedTypedLength.toLong() +} + +private fun writeDeflateBlock( + data: ByteArray, + output: ByteArray, + isFinal: Boolean, + symbols: IntArray, + symbolStart: Int, + symbolCount: Int, + blockStart: Int, + blockLength: Int, + bitPosition: Long, + plan: DeflateBlockPlan, +): Long { + var currentBitPosition = bitPosition + writeBits(output, currentBitPosition++, if (isFinal) 1 else 0) - var literalMap: ShortArray - var literalLengths: ByteArray - var distanceMap: ShortArray - var distanceLengths: ByteArray + if (plan.blockType == DeflateBlockType.STORED) { + return writeFixedBlock(output, currentBitPosition, data.sliceArray(blockStart until blockStart + blockLength)) + } - writeBits(output, currentBitPosition, 1 + if (dynamicTypedLength < fixedTypedLength) 1 else 0) + val usesDynamicTree = plan.blockType == DeflateBlockType.DYNAMIC + writeBits(output, currentBitPosition, 1 + if (usesDynamicTree) 1 else 0) currentBitPosition += 2 - if (dynamicTypedLength < fixedTypedLength) { - literalMap = createHuffmanTree(dynamicLiteralTree, maxLiteralBits, false) - literalLengths = dynamicLiteralTree - distanceMap = createHuffmanTree(dynamicDistanceTree, maxDistanceBits, false) - distanceLengths = dynamicDistanceTree - - val codeLengthMap = createHuffmanTree(codeLengthTree, maxCodeLengthBits, false) - writeBits(output, currentBitPosition, numLiteralCodes - 257) - writeBits(output, currentBitPosition + 5, numDistanceCodes - 1) + val literalMap: ShortArray + val literalLengths: ByteArray + val distanceMap: ShortArray + val distanceLengths: ByteArray + if (usesDynamicTree) { + literalMap = createHuffmanTree(plan.literalTree.tree, plan.literalTree.maxBits, false) + literalLengths = plan.literalTree.tree + distanceMap = createHuffmanTree(plan.distanceTree.tree, plan.distanceTree.maxBits, false) + distanceLengths = plan.distanceTree.tree + + val codeLengthMap = createHuffmanTree( + plan.codeLengthTree.tree, + plan.codeLengthTree.maxBits, + false, + ) + val numCodeLengthCodes = countCodeLengthCodes(plan.codeLengthTree.tree) + writeBits(output, currentBitPosition, plan.numLiteralCodes - 257) + writeBits(output, currentBitPosition + 5, plan.numDistanceCodes - 1) writeBits(output, currentBitPosition + 10, numCodeLengthCodes - 4) currentBitPosition += 14 - for (i in 0 until numCodeLengthCodes) - writeBits(output, currentBitPosition + 3 * i, codeLengthTree.getOrNull(CODE_LENGTH_INDEX_MAP[i].toInt())?.toInt() ?: 0) + for (i in 0 until numCodeLengthCodes) { + writeBits( + output, + currentBitPosition + 3 * i, + plan.codeLengthTree.tree.getOrNull(CODE_LENGTH_INDEX_MAP[i].toInt())?.toInt() ?: 0, + ) + } currentBitPosition += 3 * numCodeLengthCodes - val codeLengthTrees = arrayOf(literalCodeLengths, distanceCodeLengths) - for (tree in codeLengthTrees) { - for (i in tree.indices) { - val len = tree[i].toInt() and 31 - writeBits(output, currentBitPosition, (codeLengthMap[len].toInt() and 0xFFFF)) - currentBitPosition += codeLengthTree[len].toInt() and 0xFF - if (len > 15) { - writeBits(output, currentBitPosition, (tree[i].toInt() shr 5) and 127) - currentBitPosition += (tree[i].toInt() shr 12) - } + for (code in plan.codeLengthCodes) { + val len = code.toInt() and 31 + writeBits(output, currentBitPosition, codeLengthMap[len].toInt() and 0xFFFF) + currentBitPosition += plan.codeLengthTree.tree[len].toInt() and 0xFF + if (len > 15) { + writeBits(output, currentBitPosition, (code.toInt() shr 5) and 127) + currentBitPosition += code.toInt() shr 12 } } } else { @@ -189,33 +391,63 @@ internal fun writeBlock( distanceLengths = FIXED_DISTANCE_TREE } - for (i in 0 until symbolCount) { + for (i in symbolStart until symbolStart + symbolCount) { val symbol = symbols[i] if (symbol > 255) { val lengthSymbol = (symbol shr 18) and 31 - writeBits16(output, currentBitPosition, (literalMap[lengthSymbol + 257].toInt() and 0xFFFF)) + writeBits16(output, currentBitPosition, literalMap[lengthSymbol + 257].toInt() and 0xFFFF) currentBitPosition += literalLengths[lengthSymbol + 257].toInt() and 0xFF if (lengthSymbol > 7) { writeBits(output, currentBitPosition, (symbol shr 23) and 31) currentBitPosition += FIXED_LENGTH_EXTRA_BITS[lengthSymbol].toInt() and 0xFF } val distanceSymbol = symbol and 31 - writeBits16(output, currentBitPosition, (distanceMap[distanceSymbol].toInt() and 0xFFFF)) + writeBits16(output, currentBitPosition, distanceMap[distanceSymbol].toInt() and 0xFFFF) currentBitPosition += distanceLengths[distanceSymbol].toInt() and 0xFF if (distanceSymbol > 3) { writeBits16(output, currentBitPosition, (symbol shr 5) and 8191) currentBitPosition += FIXED_DISTANCE_EXTRA_BITS[distanceSymbol].toInt() and 0xFF } } else { - writeBits16(output, currentBitPosition, (literalMap[symbol].toInt() and 0xFFFF)) + writeBits16(output, currentBitPosition, literalMap[symbol].toInt() and 0xFFFF) currentBitPosition += literalLengths[symbol].toInt() and 0xFF } } - writeBits16(output, currentBitPosition, (literalMap[256].toInt() and 0xFFFF)) + writeBits16(output, currentBitPosition, literalMap[256].toInt() and 0xFFFF) return currentBitPosition + (literalLengths[256].toInt() and 0xFF) } +private fun populateBlockFrequencies( + symbols: IntArray, + symbolStart: Int, + symbolCount: Int, + literalFrequencies: IntArray, + distanceFrequencies: IntArray, +): Int { + var extraBits = 0 + for (index in symbolStart until symbolStart + symbolCount) { + val symbol = symbols[index] + if (symbol > 255) { + val lengthSymbol = (symbol shr 18) and 31 + val distanceSymbol = symbol and 31 + literalFrequencies[257 + lengthSymbol]++ + distanceFrequencies[distanceSymbol]++ + extraBits += (FIXED_LENGTH_EXTRA_BITS[lengthSymbol].toInt() and 0xFF) + extraBits += (FIXED_DISTANCE_EXTRA_BITS[distanceSymbol].toInt() and 0xFF) + } else { + literalFrequencies[symbol]++ + } + } + return extraBits +} + +private fun encodedSymbolByteLength(symbol: Int): Int { + if (symbol <= 255) return 1 + val lengthSymbol = (symbol shr 18) and 31 + return (FIXED_LENGTH_BASE[lengthSymbol].toInt() and 0xFFFF) + ((symbol shr 23) and 31) +} + internal fun readTwoBytes(data: ByteArray, offset: Int): Int { return (data[offset].toInt() and 0xFF) or ((data[offset + 1].toInt() and 0xFF) shl 8) } @@ -268,3 +500,15 @@ internal fun shouldUseStoredBlock( ): Boolean { return blockStart >= 0 && storedLength <= fixedLength && storedLength <= dynamicLength } + +/** + * Returns the stored block cost after the three bit block header. + * + * The block type comparison excludes the common three bit header, while a stored block + * includes the padding and four byte length header that follow it. + */ +internal fun storedBlockBitLength(blockLength: Int, bitPosition: Long): Int { + val headerEnd = bitPosition + 3L + val padding = ((8L - (headerEnd and 7L)) and 7L).toInt() + return (blockLength shl 3) + 32 + padding +} diff --git a/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockPlan.kt b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockPlan.kt new file mode 100644 index 0000000..e03fdf3 --- /dev/null +++ b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockPlan.kt @@ -0,0 +1,16 @@ +package com.rafambn.kflate.util + +import com.rafambn.kflate.huffman.HuffmanTreeResult + +internal data class DeflateBlockPlan( + val literalTree: HuffmanTreeResult, + val distanceTree: HuffmanTreeResult, + val codeLengthTree: HuffmanTreeResult, + val codeLengthCodes: ShortArray, + val numLiteralCodes: Int, + val numDistanceCodes: Int, + val fixedTypedLength: Int, + val dynamicTypedLength: Int, + val blockType: DeflateBlockType, + val emittedTypedLength: Int, +) diff --git a/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockType.kt b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockType.kt new file mode 100644 index 0000000..378ec45 --- /dev/null +++ b/kflate/src/commonMain/kotlin/com/rafambn/kflate/util/DeflateBlockType.kt @@ -0,0 +1,7 @@ +package com.rafambn.kflate.util + +internal enum class DeflateBlockType { + STORED, + FIXED, + DYNAMIC, +} diff --git a/kflate/src/jvmTest/kotlin/AlgorithmCoverageTest.kt b/kflate/src/jvmTest/kotlin/AlgorithmCoverageTest.kt index 8937532..fa2fb81 100644 --- a/kflate/src/jvmTest/kotlin/AlgorithmCoverageTest.kt +++ b/kflate/src/jvmTest/kotlin/AlgorithmCoverageTest.kt @@ -20,11 +20,15 @@ import com.rafambn.kflate.compression.Raw as CompressionRaw import com.rafambn.kflate.decompression.Raw as DecompressionRaw import com.rafambn.kflate.error.FlateError import com.rafambn.kflate.error.FlateErrorCode +import com.rafambn.kflate.huffman.FIXED_DISTANCE_REVERSE_LOOKUP +import com.rafambn.kflate.huffman.FIXED_LENGTH_REVERSE_LOOKUP import com.rafambn.kflate.huffman.buildHuffmanTreeFromFrequencies import com.rafambn.kflate.huffman.generateLengthCodes import com.rafambn.kflate.huffman.validateHuffmanCodeLengths import com.rafambn.kflate.streaming.DeflateState import com.rafambn.kflate.streaming.InflateState +import com.rafambn.kflate.util.shiftToNextByte +import com.rafambn.kflate.util.writeBlock import java.util.zip.Deflater import kotlin.test.Test import kotlin.test.assertContentEquals @@ -272,6 +276,116 @@ class AlgorithmCoverageTest { assertEquals(4, choices[1]) } + @Test + fun blockSplittingRoundTripsWhenLiteralRangesUseDifferentTrees() { + val symbols = IntArray(1_024) { index -> + if (index < 512) index and 63 else 192 + (index and 63) + } + val data = ByteArray(symbols.size) { symbols[it].toByte() } + val literalFrequencies = IntArray(288) + for (symbol in symbols) { + literalFrequencies[symbol]++ + } + val output = ByteArray(4_096) + val endBitPosition = writeBlock( + data = data, + output = output, + isFinal = true, + symbols = symbols, + literalFrequencies = literalFrequencies, + distanceFrequencies = IntArray(32), + extraBits = 0, + symbolCount = symbols.size, + blockStart = 0, + blockLength = data.size, + bitPosition = 0, + ) + + assertContentEquals( + data, + inflate(output.copyOf(shiftToNextByte(endBitPosition)), InflateState(validationMode = 2)), + ) + } + + @Test + fun blockSplittingTracksLiteralOnlyAndMixedHalves() { + val literalHalf = ByteArray(512) { (it and 63).toByte() } + val mixedHalf = ByteArray(1 + 511 * 3) { 1 } + val data = literalHalf + mixedHalf + val matchSymbol = 268435456 or + (FIXED_LENGTH_REVERSE_LOOKUP[3] shl 18) or + FIXED_DISTANCE_REVERSE_LOOKUP[1] + val symbols = IntArray(1_024) + for (index in literalHalf.indices) { + symbols[index] = literalHalf[index].toInt() and 0xFF + } + symbols[512] = 1 + for (index in 513 until symbols.size) { + symbols[index] = matchSymbol + } + + val literalFrequencies = IntArray(288) + val distanceFrequencies = IntArray(32) + for (symbol in symbols) { + if (symbol <= 255) { + literalFrequencies[symbol]++ + } else { + literalFrequencies[257 + ((symbol shr 18) and 31)]++ + distanceFrequencies[symbol and 31]++ + } + } + + val output = ByteArray(4_096) + val endBitPosition = writeBlock( + data = data, + output = output, + isFinal = true, + symbols = symbols, + literalFrequencies = literalFrequencies, + distanceFrequencies = distanceFrequencies, + extraBits = 0, + symbolCount = symbols.size, + blockStart = 0, + blockLength = data.size, + bitPosition = 0, + ) + val inflateState = InflateState(validationMode = 2) + assertContentEquals( + data, + inflate(output.copyOf(shiftToNextByte(endBitPosition)), inflateState), + ) + assertEquals(endBitPosition.toInt(), inflateState.inputBitPosition) + } + + @Test + fun blockWriterHonorsUnavailableSourceSentinel() { + val data = byteArrayOf(7, 8) + val symbols = intArrayOf(7, 8) + val literalFrequencies = IntArray(288) + for (symbol in symbols) { + literalFrequencies[symbol]++ + } + + val output = ByteArray(128) + val endBitPosition = writeBlock( + data = data, + output = output, + isFinal = true, + symbols = symbols, + literalFrequencies = literalFrequencies, + distanceFrequencies = IntArray(32), + extraBits = 0, + symbolCount = symbols.size, + blockStart = -1, + blockLength = data.size, + bitPosition = 0, + ) + assertContentEquals( + data, + inflate(output.copyOf(shiftToNextByte(endBitPosition)), InflateState(validationMode = 2)), + ) + } + @Test fun costAwareLevelRoundTripsWindowsAndDictionaries() { val multipleWindows = ByteArray(COST_AWARE_WINDOW_SIZE * 2 + 3) diff --git a/performance/2026-09-09-split/RESULTS.md b/performance/2026-09-09-split/RESULTS.md new file mode 100644 index 0000000..24ecd85 --- /dev/null +++ b/performance/2026-09-09-split/RESULTS.md @@ -0,0 +1,83 @@ +# Split blocks when a midpoint reduces their encoded bit count + +Compare the original block with two blocks divided at the midpoint token boundary. Reuse the selected Huffman plans to emit the lower-bit-cost option. Account for stored-block alignment and synthetic distance codes without counting nonexistent distance tokens. The main measured benefit is the map-tile fixture; the weighted aggregate gain is small. Planning adds CPU work and allocations. The exact stored-block cost helper overlaps PR33 and should be deduplicated when merging. + +Base: `fe2ff51`, `dev-1.1.0`. Measured on September 9, 2026 on Linux x86_64, Intel Core i7-11800H. JVM uses JBR 17.0.14; Node uses 22.22.1. + +Only KFlate was timed. Kompress sizes and timings come unchanged from the trusted September 8 archive in `performance/history.json`. Benchmark setup may produce Kompress streams for decoder validation outside timing. + +## Compressed size + +The JVM sweep compresses all seven tracked corpora at every listed level. Every output must round-trip through both the JDK RAW inflater and KFlate. Byte counts are deterministic. Single-run durations in the sweep JSON are diagnostic, not throughput benchmarks. + +Output reduction is `(beforeBytes - afterBytes) / beforeBytes`. Totals weight each corpus by its compressed bytes; they are not averages of percentage changes. + +| Level | Before bytes | After bytes | Output reduction | +| ---: | ---: | ---: | ---: | +| 0 | 76,379,868 | 76,379,868 | 0.0000% | +| 1 | 40,222,941 | 40,221,943 | 0.0025% | +| 2 | 39,269,936 | 39,268,740 | 0.0030% | +| 3 | 38,906,551 | 38,905,056 | 0.0038% | +| 4 | 38,333,169 | 38,331,394 | 0.0046% | +| 5 | 38,293,272 | 38,291,032 | 0.0058% | +| 6 | 37,882,044 | 37,879,680 | 0.0062% | +| 7 | 37,868,330 | 37,865,989 | 0.0062% | +| 8 | 37,737,043 | 37,734,795 | 0.0060% | +| 9 | 37,251,418 | 37,248,714 | 0.0073% | + +### Level 6 + +| Corpus | Before bytes | After bytes | Reduction | Saved JVM Kompress bytes | +| --- | ---: | ---: | ---: | ---: | +| simpleText | 84 | 84 | 0.0000% | 84 | +| text | 506,455 | 506,307 | 0.0292% | 505,318 | +| model3D | 2,153 | 2,153 | 0.0000% | 2,149 | +| Rainier.bmp | 3,283,450 | 3,283,045 | 0.0123% | 3,275,337 | +| Maltese.bmp | 7,158,472 | 7,158,287 | 0.0026% | 7,096,685 | +| Sunrise.bmp | 26,838,825 | 26,838,626 | 0.0007% | 26,698,992 | +| compressed_MVT.pbf | 92,605 | 91,178 | 1.5410% | 91,408 | + +### Level 9 + +| Corpus | Before bytes | After bytes | Reduction | Saved JVM Kompress bytes | +| --- | ---: | ---: | ---: | ---: | +| simpleText | 84 | 84 | 0.0000% | 84 | +| text | 492,523 | 492,432 | 0.0185% | 503,400 | +| model3D | 2,153 | 2,153 | 0.0000% | 2,149 | +| Rainier.bmp | 3,258,570 | 3,258,104 | 0.0143% | 3,270,382 | +| Maltese.bmp | 7,034,092 | 7,033,901 | 0.0027% | 7,090,887 | +| Sunrise.bmp | 26,372,269 | 26,372,060 | 0.0008% | 26,652,810 | +| compressed_MVT.pbf | 91,727 | 89,980 | 1.9046% | 91,400 | + +## Compression timing + +Development measurements use three one-second warmups and five one-second measurement iterations. JVM has one fresh fork per corpus; Native has one process per corpus; Wasm uses one runner. These shorter runs detect broad tradeoffs and are not release-grade speed claims. Raw samples, errors and confidence intervals are retained. No decompression speed claim is made. + +### jvm, level 6 + +| Corpus | Before ms | After ms | Before/after speedup | Saved Kompress/after speedup | +| --- | ---: | ---: | ---: | ---: | +| simpleText | 0.0279 | 0.0277 | 1.005x | 0.153x | +| text | 60.4159 | 62.6884 | 0.964x | 0.831x | +| model3D | 0.0669 | 0.1156 | 0.579x | 0.261x | +| Rainier.bmp | 108.3289 | 122.2448 | 0.886x | 0.844x | +| Maltese.bmp | 477.9739 | 516.8326 | 0.925x | 0.731x | +| Sunrise.bmp | 1673.0244 | 1674.0776 | 0.999x | 0.739x | +| compressed_MVT.pbf | 4.5006 | 4.4917 | 1.002x | 0.869x | + + +## Validation and reproduction + +Linux JVM tests and 100% instruction/branch coverage verification passed. Coverage includes literal-only and mixed block cost/end-position checks and unavailable-source sentinel behavior. All 70 outputs passed both JDK and KFlate decoding with no fixture size regressions; their hashes matched the previous corrected split run after removing an unreachable guard. All seven level 6 JMH compression cases completed. No Native or Wasm timing claim is made for this standalone variant. + +Tracked source diff SHA-256: `f3e6457d40e0d6104061209a66fa277db8452484f3622f33ca8d0fc40cfeff44`. Added source/test files are present in this commit. + +Linux build: `ANDROID_HOME=/home/rafael/Android/Sdk ./gradlew :kflate:jvmTest :kflate:jvmBenchmarkBenchmarkJar --no-parallel --max-workers=1`. + +For level 9 timing, set `BENCHMARK_COMPRESSION_LEVEL` to 9 in `BenchmarkState.kt` before building; leave it at 6 otherwise. Use JBR 17.0.14 and the generated JMH jar with the main and benchmark classes on its classpath. Run `org.openjdk.jmh.Main` with the relevant `CompressionBenchmarks` method filter and `-wi 3 -i 5 -w 1s -r 1s -f 1 -foe true -rf json -rff result.json`. Only KFlate benchmark methods should be selected. + +Compile `RatioSweep.java` against the KFlate classes and generated benchmark jar, then run it with `kflate/src/jvmTest/resources FIRST_LEVEL LAST_LEVEL`. It checks both JDK and KFlate decompression and records deterministic sizes and output hashes. + +## Timing calibration + +A later three-fork unchanged-baseline run measured text at 60.37, 85.43, and 64.00 ms per fork. This spread means the initial single-fork text comparison cannot establish a causal speed change. The deterministic size results are unaffected. See `baseline-recheck-jmh.json` for all samples. diff --git a/performance/2026-09-09-split/RatioSweep.java b/performance/2026-09-09-split/RatioSweep.java new file mode 100644 index 0000000..112ba7d --- /dev/null +++ b/performance/2026-09-09-split/RatioSweep.java @@ -0,0 +1,42 @@ +import com.rafambn.kflate.KFlate; +import com.rafambn.kflate.compression.Raw; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.security.MessageDigest; +import java.util.HexFormat; +import java.util.zip.Inflater; + +public class RatioSweep { + public static void main(String[] args) throws Exception { + String[] names = {"simpleText", "text", "model3D", "Rainier.bmp", "Maltese.bmp", "Sunrise.bmp", "compressed_MVT.pbf"}; + int first = args.length > 1 ? Integer.parseInt(args[1]) : 0; + int last = args.length > 2 ? Integer.parseInt(args[2]) : 9; + for (String name : names) { + if (args.length > 3 && !name.equals(args[3])) continue; + byte[] input = Files.readAllBytes(Path.of(args[0], name)); + for (int level = first; level <= last; level++) { + long start = System.nanoTime(); + byte[] output = KFlate.INSTANCE.compress(input, new Raw(level, null)); + double ms = (System.nanoTime() - start) / 1e6; + Inflater inflater = new Inflater(true); + try { + inflater.setInput(output); + byte[] decoded = new byte[input.length + 1]; + int count = inflater.inflate(decoded); + if (!inflater.finished() || count != input.length || !Arrays.equals(input, Arrays.copyOf(decoded, count))) { + Files.write(Path.of("/tmp/kflate-invalid-" + name + "-" + level + ".deflate"), output); + int mismatch = Arrays.mismatch(input, Arrays.copyOf(decoded, count)); + throw new AssertionError("Invalid output: " + name + " level " + level + " decoded=" + count + " expected=" + input.length + " finished=" + inflater.finished() + " remaining=" + inflater.getRemaining() + " mismatch=" + mismatch); + } + } finally { + inflater.end(); + } + if (!Arrays.equals(input, KFlate.INSTANCE.decompress(output, new com.rafambn.kflate.decompression.Raw(null, null)))) { + throw new AssertionError("KFlate round trip: " + name + " level " + level); + } + System.out.printf(java.util.Locale.ROOT, "{\"corpus\":\"%s\",\"level\":%d,\"originalSizeBytes\":%d,\"compressedSizeBytes\":%d,\"singleRunMs\":%.6f,\"sha256\":\"%s\"}%n", name, level, input.length, output.length, ms, HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(output))); + } + } + } +} diff --git a/performance/2026-09-09-split/after-jmh.json b/performance/2026-09-09-split/after-jmh.json new file mode 100644 index 0000000..48fa027 --- /dev/null +++ b/performance/2026-09-09-split/after-jmh.json @@ -0,0 +1,380 @@ +[ + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "simpleText" + }, + "primaryMetric" : { + "score" : 2.7714392872700348E-5, + "scoreError" : 4.173635293673224E-7, + "scoreConfidence" : [ + 2.7297029343333024E-5, + 2.8131756402067672E-5 + ], + "scorePercentiles" : { + "0.0" : 2.7559622991926372E-5, + "50.0" : 2.774115327009486E-5, + "90.0" : 2.7822526315789473E-5, + "95.0" : 2.7822526315789473E-5, + "99.0" : 2.7822526315789473E-5, + "99.9" : 2.7822526315789473E-5, + "99.99" : 2.7822526315789473E-5, + "99.999" : 2.7822526315789473E-5, + "99.9999" : 2.7822526315789473E-5, + "100.0" : 2.7822526315789473E-5 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 2.7822526315789473E-5, + 2.779655143952868E-5, + 2.7652110346162355E-5, + 2.774115327009486E-5, + 2.7559622991926372E-5 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "text" + }, + "primaryMetric" : { + "score" : 0.06268840281470588, + "scoreError" : 0.00140257977965757, + "scoreConfidence" : [ + 0.06128582303504831, + 0.06409098259436345 + ], + "scorePercentiles" : { + "0.0" : 0.06205713182352941, + "50.0" : 0.062833539375, + "90.0" : 0.0629324450625, + "95.0" : 0.0629324450625, + "99.0" : 0.0629324450625, + "99.9" : 0.0629324450625, + "99.99" : 0.0629324450625, + "99.999" : 0.0629324450625, + "99.9999" : 0.0629324450625, + "100.0" : 0.0629324450625 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.0629324450625, + 0.06291497275, + 0.062833539375, + 0.0627039250625, + 0.06205713182352941 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "model3D" + }, + "primaryMetric" : { + "score" : 1.1556974581585435E-4, + "scoreError" : 1.9407118268287716E-6, + "scoreConfidence" : [ + 1.1362903398902558E-4, + 1.1751045764268313E-4 + ], + "scorePercentiles" : { + "0.0" : 1.1521099147563644E-4, + "50.0" : 1.153883730964467E-4, + "90.0" : 1.1644949662398137E-4, + "95.0" : 1.1644949662398137E-4, + "99.0" : 1.1644949662398137E-4, + "99.9" : 1.1644949662398137E-4, + "99.99" : 1.1644949662398137E-4, + "99.999" : 1.1644949662398137E-4, + "99.9999" : 1.1644949662398137E-4, + "100.0" : 1.1644949662398137E-4 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 1.1521099147563644E-4, + 1.1529312495677233E-4, + 1.153883730964467E-4, + 1.1550674292643492E-4, + 1.1644949662398137E-4 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Rainier.bmp" + }, + "primaryMetric" : { + "score" : 0.12224476079999999, + "scoreError" : 0.001683645233611866, + "scoreConfidence" : [ + 0.12056111556638813, + 0.12392840603361185 + ], + "scorePercentiles" : { + "0.0" : 0.12184590655555555, + "50.0" : 0.12209239288888889, + "90.0" : 0.12298045488888888, + "95.0" : 0.12298045488888888, + "99.0" : 0.12298045488888888, + "99.9" : 0.12298045488888888, + "99.99" : 0.12298045488888888, + "99.999" : 0.12298045488888888, + "99.9999" : 0.12298045488888888, + "100.0" : 0.12298045488888888 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.12298045488888888, + 0.12226239855555555, + 0.12209239288888889, + 0.12204265111111111, + 0.12184590655555555 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Maltese.bmp" + }, + "primaryMetric" : { + "score" : 0.5168325845, + "scoreError" : 0.008266204542854975, + "scoreConfidence" : [ + 0.508566379957145, + 0.525098789042855 + ], + "scorePercentiles" : { + "0.0" : 0.514840077, + "50.0" : 0.516173824, + "90.0" : 0.520262927, + "95.0" : 0.520262927, + "99.0" : 0.520262927, + "99.9" : 0.520262927, + "99.99" : 0.520262927, + "99.999" : 0.520262927, + "99.9999" : 0.520262927, + "100.0" : 0.520262927 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.520262927, + 0.516173824, + 0.514840077, + 0.5174326495, + 0.515453445 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Sunrise.bmp" + }, + "primaryMetric" : { + "score" : 1.6740775581999998, + "scoreError" : 0.3590203569433415, + "scoreConfidence" : [ + 1.3150572012566584, + 2.0330979151433413 + ], + "scorePercentiles" : { + "0.0" : 1.614787885, + "50.0" : 1.640001523, + "90.0" : 1.8376667, + "95.0" : 1.8376667, + "99.0" : 1.8376667, + "99.9" : 1.8376667, + "99.99" : 1.8376667, + "99.999" : 1.8376667, + "99.9999" : 1.8376667, + "100.0" : 1.8376667 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 1.8376667, + 1.659806016, + 1.640001523, + 1.618125667, + 1.614787885 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "compressed_MVT.pbf" + }, + "primaryMetric" : { + "score" : 0.004491660820627802, + "scoreError" : 2.037751901294838E-5, + "scoreConfidence" : [ + 0.004471283301614853, + 0.00451203833964075 + ], + "scorePercentiles" : { + "0.0" : 0.004487120304932735, + "50.0" : 0.004488343677130044, + "90.0" : 0.004497990856502242, + "95.0" : 0.004497990856502242, + "99.0" : 0.004497990856502242, + "99.9" : 0.004497990856502242, + "99.99" : 0.004497990856502242, + "99.999" : 0.004497990856502242, + "99.9999" : 0.004497990856502242, + "100.0" : 0.004497990856502242 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.004496849798206278, + 0.004488343677130044, + 0.004497990856502242, + 0.004487999466367713, + 0.004487120304932735 + ] + ] + }, + "secondaryMetrics" : { + } + } +] diff --git a/performance/2026-09-09-split/after-sizes.jsonl b/performance/2026-09-09-split/after-sizes.jsonl new file mode 100644 index 0000000..0c6c12a --- /dev/null +++ b/performance/2026-09-09-split/after-sizes.jsonl @@ -0,0 +1,70 @@ +{"corpus":"simpleText","level":0,"originalSizeBytes":100,"compressedSizeBytes":105,"singleRunMs":18.777248,"sha256":"7a4f86cab2c64d7a755ba6244b34b85122a3d7e629073b34930b3d477585100b"} +{"corpus":"simpleText","level":1,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":13.431345,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":2,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.566808,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":3,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.444055,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":4,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.395777,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":5,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.374281,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":6,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.466406,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":7,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.383139,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":8,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.342119,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"simpleText","level":9,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.681733,"sha256":"dfc6c55d533c49b70bb5f358f19c146ad3443a985e6f76144b1ee4e16286e499"} +{"corpus":"text","level":0,"originalSizeBytes":1232923,"compressedSizeBytes":1233018,"singleRunMs":4.866912,"sha256":"cfab8f8c4f21f3b8b8b3cd60a85b7e1243a5aad2a06fa863e9eec73187488605"} +{"corpus":"text","level":1,"originalSizeBytes":1232923,"compressedSizeBytes":599813,"singleRunMs":62.066091,"sha256":"143d81705ac095d278fb6e1fa3932a500916369bc4596da6885cfe2e7d81f756"} +{"corpus":"text","level":2,"originalSizeBytes":1232923,"compressedSizeBytes":581500,"singleRunMs":38.353949,"sha256":"d309954b10d6212cb715af9742703208ffbdca960152e56bdd53d7af8f8c53bc"} +{"corpus":"text","level":3,"originalSizeBytes":1232923,"compressedSizeBytes":565230,"singleRunMs":38.484875,"sha256":"7dfb49061a127b4cf765982ec95b313e934e2efd8996396ffde5afcf066a5a5a"} +{"corpus":"text","level":4,"originalSizeBytes":1232923,"compressedSizeBytes":531403,"singleRunMs":53.150142,"sha256":"1ff247f3e3cd94224c79319c81c0f8640b22a2594dd7734b35a629b796b4dd1a"} +{"corpus":"text","level":5,"originalSizeBytes":1232923,"compressedSizeBytes":521757,"singleRunMs":67.453000,"sha256":"f2b9721aa35cd45045cb0c772a4297c2de5c67a3b503fb5607c087768be5be8d"} +{"corpus":"text","level":6,"originalSizeBytes":1232923,"compressedSizeBytes":506307,"singleRunMs":78.404211,"sha256":"d601b10c5affda2eea13ca1edff8ce0e86ed883a299583bd6741f7d6f8ae4e4f"} +{"corpus":"text","level":7,"originalSizeBytes":1232923,"compressedSizeBytes":505949,"singleRunMs":79.517682,"sha256":"33470ea8d347b97ced1952896e211641c58cd355705911c311eb4cafe98d82de"} +{"corpus":"text","level":8,"originalSizeBytes":1232923,"compressedSizeBytes":513350,"singleRunMs":83.295074,"sha256":"c406862b31b6d9f6e0854ad91f778a1479d2890ff86a5c76ab6c953f64e17915"} +{"corpus":"text","level":9,"originalSizeBytes":1232923,"compressedSizeBytes":492432,"singleRunMs":230.581400,"sha256":"50e7f66f5b5f25afa6fa9bd258fb0dff54542e54489f05a09ab9f005c276542b"} +{"corpus":"model3D","level":0,"originalSizeBytes":2478,"compressedSizeBytes":2483,"singleRunMs":0.026061,"sha256":"7d1354e385451c35f23c9be4ce28245d554943a2219ff0cf3481d5c0a02a4919"} +{"corpus":"model3D","level":1,"originalSizeBytes":2478,"compressedSizeBytes":2169,"singleRunMs":0.857255,"sha256":"cc762b082d3527b26e531d8f8cd04e17467595b5ef1ea32a541b275663e3ef96"} +{"corpus":"model3D","level":2,"originalSizeBytes":2478,"compressedSizeBytes":2168,"singleRunMs":0.592924,"sha256":"7be0094d5c194b33c4337a5c971d25c3ed4e2c3d1d195a75225130310d911733"} +{"corpus":"model3D","level":3,"originalSizeBytes":2478,"compressedSizeBytes":2166,"singleRunMs":0.519855,"sha256":"fa0c3d7374a90b6f9d7f14095e59ecd375b976cd57423d40e18d26332bf768c6"} +{"corpus":"model3D","level":4,"originalSizeBytes":2478,"compressedSizeBytes":2165,"singleRunMs":0.618793,"sha256":"b8bb6e5b0de2dac94d69e676871bd5538d16c6fc909b4a926a9fb42ca33d4846"} +{"corpus":"model3D","level":5,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.380596,"sha256":"4b0cd916546275b9ae57fab7c6d88d94ed1e669669b30c158eb4c06e85e8f0ad"} +{"corpus":"model3D","level":6,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.389230,"sha256":"4b0cd916546275b9ae57fab7c6d88d94ed1e669669b30c158eb4c06e85e8f0ad"} +{"corpus":"model3D","level":7,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.407672,"sha256":"4b0cd916546275b9ae57fab7c6d88d94ed1e669669b30c158eb4c06e85e8f0ad"} +{"corpus":"model3D","level":8,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.389018,"sha256":"4b0cd916546275b9ae57fab7c6d88d94ed1e669669b30c158eb4c06e85e8f0ad"} +{"corpus":"model3D","level":9,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.503991,"sha256":"450f6437d13a19585d0df05da961baf30023451a17612fd84a683a2f2f450ea7"} +{"corpus":"Rainier.bmp","level":0,"originalSizeBytes":6220854,"compressedSizeBytes":6221329,"singleRunMs":8.698957,"sha256":"acf3201e43c503d2705565195eb3bfc176e674b00bc8eeaf55411409398ffb7e"} +{"corpus":"Rainier.bmp","level":1,"originalSizeBytes":6220854,"compressedSizeBytes":3387414,"singleRunMs":132.042771,"sha256":"d75ea146a44b16c2e1c7660bd29fa831b134ceed04c517c6bd06bd7028bed4d5"} +{"corpus":"Rainier.bmp","level":2,"originalSizeBytes":6220854,"compressedSizeBytes":3357187,"singleRunMs":113.726516,"sha256":"0c9d2499d5e104e7c456308b13f3e24519f9cf088b56b134636b468ddbd1e607"} +{"corpus":"Rainier.bmp","level":3,"originalSizeBytes":6220854,"compressedSizeBytes":3345498,"singleRunMs":124.843357,"sha256":"39ae2f5d1d98b1a0c1d42e9989786c806fc3a8a2356f870e65faf47402f655a5"} +{"corpus":"Rainier.bmp","level":4,"originalSizeBytes":6220854,"compressedSizeBytes":3325409,"singleRunMs":123.076157,"sha256":"55576c421a99b7dca9059578bd9a3777990b812c92fcf8ec894f57e5ed47f3b9"} +{"corpus":"Rainier.bmp","level":5,"originalSizeBytes":6220854,"compressedSizeBytes":3312975,"singleRunMs":129.981329,"sha256":"7d68c094940fff6b2a03e50a9d681f15fca4fd1fb7cee45eb69116505083564c"} +{"corpus":"Rainier.bmp","level":6,"originalSizeBytes":6220854,"compressedSizeBytes":3283045,"singleRunMs":133.347427,"sha256":"ba1d2c0e5904f0c20c198d6cfb5eba2da2ff44c414ff90ec6f8767b54c06635b"} +{"corpus":"Rainier.bmp","level":7,"originalSizeBytes":6220854,"compressedSizeBytes":3281668,"singleRunMs":137.901846,"sha256":"3cb182cc56605cbfefd432a345360e7b79fd92a9150ef67dc2487313a5693e11"} +{"corpus":"Rainier.bmp","level":8,"originalSizeBytes":6220854,"compressedSizeBytes":3274545,"singleRunMs":152.836415,"sha256":"eaec1bad0f1e154f7dfbe84e64ad24dbbd2a46dd63c9a3345942b0d7b85db647"} +{"corpus":"Rainier.bmp","level":9,"originalSizeBytes":6220854,"compressedSizeBytes":3258104,"singleRunMs":4092.135094,"sha256":"e44e4f2cae599a0ea7f8f46a7541c8f4342f2c1695158fb0cc1d8edb349eacbd"} +{"corpus":"Maltese.bmp","level":0,"originalSizeBytes":16427390,"compressedSizeBytes":16428645,"singleRunMs":6.313234,"sha256":"d872a98f975735016f16578b716b5c874f98e85abc101426d1142dd3a5c096d6"} +{"corpus":"Maltese.bmp","level":1,"originalSizeBytes":16427390,"compressedSizeBytes":8344362,"singleRunMs":312.057158,"sha256":"fbb73b9022879442d4e1f1ecb02baa6b1ac77119aafde5151b559e0b417dd98c"} +{"corpus":"Maltese.bmp","level":2,"originalSizeBytes":16427390,"compressedSizeBytes":7762252,"singleRunMs":350.027401,"sha256":"2d5cb45c1b49204343dcd50415e5af6fcfd951ea38aa712065f0a7c54168b74a"} +{"corpus":"Maltese.bmp","level":3,"originalSizeBytes":16427390,"compressedSizeBytes":7494431,"singleRunMs":412.948666,"sha256":"62ae774a06663c35fbd8f40d0502cb362d77300f6c82767a3f80219d0be7fce8"} +{"corpus":"Maltese.bmp","level":4,"originalSizeBytes":16427390,"compressedSizeBytes":7272431,"singleRunMs":442.123277,"sha256":"fe0b938777126ee2c70591697ba762e4af0199350bf6b45445f1e2a8f9abd8a4"} +{"corpus":"Maltese.bmp","level":5,"originalSizeBytes":16427390,"compressedSizeBytes":7265639,"singleRunMs":555.624177,"sha256":"5c15a7cb414658470aca6b950ce0a4adc1a05318d767fd7d546b0cf542c9324a"} +{"corpus":"Maltese.bmp","level":6,"originalSizeBytes":16427390,"compressedSizeBytes":7158287,"singleRunMs":564.047248,"sha256":"ed809c5eb42287e80a2e83cc7564c06e0b06cfd2b7742abb754c6e3565a059fe"} +{"corpus":"Maltese.bmp","level":7,"originalSizeBytes":16427390,"compressedSizeBytes":7157551,"singleRunMs":570.861324,"sha256":"3a40c4f957a1a1b90a69cb42578daa93260cb784ae97e3b168531909e7b850bb"} +{"corpus":"Maltese.bmp","level":8,"originalSizeBytes":16427390,"compressedSizeBytes":7125141,"singleRunMs":507.285394,"sha256":"755b2b84b7839b19e3e93889d306357bff1558716e237ae1a770069e51b0e5e1"} +{"corpus":"Maltese.bmp","level":9,"originalSizeBytes":16427390,"compressedSizeBytes":7033901,"singleRunMs":1350.045872,"sha256":"12e39578e782fd0386eb34d5996a9eea611db6536b8163811664bf89addc9b18"} +{"corpus":"Sunrise.bmp","level":0,"originalSizeBytes":52344054,"compressedSizeBytes":52348049,"singleRunMs":32.293748,"sha256":"4ab78a3e1ba97388ff3dd5e00d9e134bceeb1b5eb3edc7aaf7845c1377fea548"} +{"corpus":"Sunrise.bmp","level":1,"originalSizeBytes":52344054,"compressedSizeBytes":27792959,"singleRunMs":998.166647,"sha256":"4a9d1012899d8c8743846138e5ef12b8755676c0fe45ba296f450dd48a1b2506"} +{"corpus":"Sunrise.bmp","level":2,"originalSizeBytes":52344054,"compressedSizeBytes":27471882,"singleRunMs":1131.315735,"sha256":"7873cd19160226d7f64975e06b582232166f4052c7262cc7744335b856187c92"} +{"corpus":"Sunrise.bmp","level":3,"originalSizeBytes":52344054,"compressedSizeBytes":27404338,"singleRunMs":1215.194723,"sha256":"5b34a62370dafaae5a95ca5edefeffd853c4d16dcefa0b16e93e8438ca56ec5d"} +{"corpus":"Sunrise.bmp","level":4,"originalSizeBytes":52344054,"compressedSizeBytes":27107680,"singleRunMs":1279.925256,"sha256":"9b9ea3017764cb160f2cebcd118a941dfc146642c953c42d0487cb6c5c810d09"} +{"corpus":"Sunrise.bmp","level":5,"originalSizeBytes":52344054,"compressedSizeBytes":27096563,"singleRunMs":1671.400613,"sha256":"8ac324bcb66a3b1cae992a8a86f432ab77ce4d6d77e1987910f04b6584e351ec"} +{"corpus":"Sunrise.bmp","level":6,"originalSizeBytes":52344054,"compressedSizeBytes":26838626,"singleRunMs":1651.096699,"sha256":"9714ed39c0147062e6b60253c56451edb0bed69a76c207545c418159d4b98f9d"} +{"corpus":"Sunrise.bmp","level":7,"originalSizeBytes":52344054,"compressedSizeBytes":26827404,"singleRunMs":1871.586039,"sha256":"b911a7418c3a0d8fc78d95f7bfb837460c713957a1223ecd2ac0adfa908055c2"} +{"corpus":"Sunrise.bmp","level":8,"originalSizeBytes":52344054,"compressedSizeBytes":26728290,"singleRunMs":2067.570808,"sha256":"f16909a2fd033f6a563d7233f7c76d378be475a51a38866d7e786e205cfe65d6"} +{"corpus":"Sunrise.bmp","level":9,"originalSizeBytes":52344054,"compressedSizeBytes":26372060,"singleRunMs":12894.449924,"sha256":"b4db417d8f88e194d4728b03a62058dfbfc5b8afc70c5fdd3c0ecf415b75d8e9"} +{"corpus":"compressed_MVT.pbf","level":0,"originalSizeBytes":146224,"compressedSizeBytes":146239,"singleRunMs":0.051600,"sha256":"d8239f92d78f0d7fc3e9e9d8557e35365184e7d8c7f432339a8375c422a61c13"} +{"corpus":"compressed_MVT.pbf","level":1,"originalSizeBytes":146224,"compressedSizeBytes":95142,"singleRunMs":3.105319,"sha256":"ea8c671976b75ef3782f693f206fc460b0e620cd0232358378ae8ef28ba7513c"} +{"corpus":"compressed_MVT.pbf","level":2,"originalSizeBytes":146224,"compressedSizeBytes":93667,"singleRunMs":3.450205,"sha256":"05ae6886c46c1129d1683bd84a2ba44bbcba93d0a741b6ad58b54bf53e4dc508"} +{"corpus":"compressed_MVT.pbf","level":3,"originalSizeBytes":146224,"compressedSizeBytes":93309,"singleRunMs":3.979000,"sha256":"072bdef406fa98b62eedf8f239727e60229e41852f99eb6124949e5f47680fd8"} +{"corpus":"compressed_MVT.pbf","level":4,"originalSizeBytes":146224,"compressedSizeBytes":92222,"singleRunMs":3.785120,"sha256":"eb6de6ad8dbeb7ba3d0eda76c11454e7ee378cbbdbf5c1ce6b2703a46e4a628c"} +{"corpus":"compressed_MVT.pbf","level":5,"originalSizeBytes":146224,"compressedSizeBytes":91861,"singleRunMs":4.372477,"sha256":"82ee2efa2278b984b1a8408062bf570a1fa2c26e76e5861ce1613f75f3c404e2"} +{"corpus":"compressed_MVT.pbf","level":6,"originalSizeBytes":146224,"compressedSizeBytes":91178,"singleRunMs":4.441667,"sha256":"491938975eecd34305fe7ee107145af55e04c0ef1022e7bb701720ecf6303088"} +{"corpus":"compressed_MVT.pbf","level":7,"originalSizeBytes":146224,"compressedSizeBytes":91180,"singleRunMs":4.504469,"sha256":"e876ef549591df06220d97a1d4cd4c3d60c737b9f7a02e6b622bfbbb4051f31a"} +{"corpus":"compressed_MVT.pbf","level":8,"originalSizeBytes":146224,"compressedSizeBytes":91232,"singleRunMs":4.874952,"sha256":"d36cc5c74b96c3ba5d9d222170bc0d6ba1fcdb5d103dd884e25953c9a529137e"} +{"corpus":"compressed_MVT.pbf","level":9,"originalSizeBytes":146224,"compressedSizeBytes":89980,"singleRunMs":9.189882,"sha256":"9ad47e1792043d2f2e9892ca0c292925ff70e38ba684d2af10ca478a598012fa"} diff --git a/performance/2026-09-09-split/baseline-recheck-jmh.json b/performance/2026-09-09-split/baseline-recheck-jmh.json new file mode 100644 index 0000000..198a588 --- /dev/null +++ b/performance/2026-09-09-split/baseline-recheck-jmh.json @@ -0,0 +1,200 @@ +[ + { + "jmhVersion": "1.37", + "benchmark": "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode": "avgt", + "threads": 1, + "forks": 3, + "jvm": "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs": [], + "jdkVersion": "17.0.14", + "vmName": "OpenJDK 64-Bit Server VM", + "vmVersion": "17.0.14+1-b1367.22", + "warmupIterations": 3, + "warmupTime": "1 s", + "warmupBatchSize": 1, + "measurementIterations": 5, + "measurementTime": "1 s", + "measurementBatchSize": 1, + "params": { + "corpus": "simpleText" + }, + "primaryMetric": { + "score": 2.6968305442982996e-05, + "scoreError": 7.972825660060215e-07, + "scoreConfidence": [ + 2.6171022876976976e-05, + 2.7765588008989016e-05 + ], + "scorePercentiles": { + "0.0": 2.6064190561809466e-05, + "50.0": 2.713266691080729e-05, + "90.0": 2.8003960663271567e-05, + "95.0": 2.8266364931321012e-05, + "99.0": 2.8266364931321012e-05, + "99.9": 2.8266364931321012e-05, + "99.99": 2.8266364931321012e-05, + "99.999": 2.8266364931321012e-05, + "99.9999": 2.8266364931321012e-05, + "100.0": 2.8266364931321012e-05 + }, + "scoreUnit": "s/op", + "rawData": [ + [ + 2.7555488814194403e-05, + 2.8266364931321012e-05, + 2.7460792801449593e-05, + 2.6064190561809466e-05, + 2.608264600500678e-05 + ], + [ + 2.7647624087793012e-05, + 2.7570792910499186e-05, + 2.7829024484571936e-05, + 2.63618836056932e-05, + 2.6479099994705074e-05 + ], + [ + 2.7309787188728704e-05, + 2.713266691080729e-05, + 2.618046873445884e-05, + 2.6117664142666912e-05, + 2.6466086471039612e-05 + ] + ] + }, + "secondaryMetrics": {} + }, + { + "jmhVersion": "1.37", + "benchmark": "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode": "avgt", + "threads": 1, + "forks": 3, + "jvm": "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs": [], + "jdkVersion": "17.0.14", + "vmName": "OpenJDK 64-Bit Server VM", + "vmVersion": "17.0.14+1-b1367.22", + "warmupIterations": 3, + "warmupTime": "1 s", + "warmupBatchSize": 1, + "measurementIterations": 5, + "measurementTime": "1 s", + "measurementBatchSize": 1, + "params": { + "corpus": "text" + }, + "primaryMetric": { + "score": 0.06993304894514706, + "scoreError": 0.012428937382961722, + "scoreConfidence": [ + 0.05750411156218534, + 0.08236198632810877 + ], + "scorePercentiles": { + "0.0": 0.060155878882352944, + "50.0": 0.0634497960625, + "90.0": 0.08588725383333333, + "95.0": 0.08624586358333333, + "99.0": 0.08624586358333333, + "99.9": 0.08624586358333333, + "99.99": 0.08624586358333333, + "99.999": 0.08624586358333333, + "99.9999": 0.08624586358333333, + "100.0": 0.08624586358333333 + }, + "scoreUnit": "s/op", + "rawData": [ + [ + 0.060155878882352944, + 0.06047350752941177, + 0.06041102294117647, + 0.06046983752941176, + 0.060362842588235294 + ], + [ + 0.08493959983333334, + 0.08484457791666666, + 0.08564818066666667, + 0.08546072075, + 0.08624586358333333 + ], + [ + 0.06015902294117647, + 0.06055583835294118, + 0.0634497960625, + 0.06794135146666666, + 0.06787769313333333 + ] + ] + }, + "secondaryMetrics": {} + }, + { + "jmhVersion": "1.37", + "benchmark": "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode": "avgt", + "threads": 1, + "forks": 3, + "jvm": "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs": [], + "jdkVersion": "17.0.14", + "vmName": "OpenJDK 64-Bit Server VM", + "vmVersion": "17.0.14+1-b1367.22", + "warmupIterations": 3, + "warmupTime": "1 s", + "warmupBatchSize": 1, + "measurementIterations": 5, + "measurementTime": "1 s", + "measurementBatchSize": 1, + "params": { + "corpus": "model3D" + }, + "primaryMetric": { + "score": 6.769606591254372e-05, + "scoreError": 1.896985698416163e-06, + "scoreConfidence": [ + 6.579908021412755e-05, + 6.959305161095989e-05 + ], + "scorePercentiles": { + "0.0": 6.342663043753963e-05, + "50.0": 6.822538021828103e-05, + "90.0": 6.953005168173725e-05, + "95.0": 6.958730044531032e-05, + "99.0": 6.958730044531032e-05, + "99.9": 6.958730044531032e-05, + "99.99": 6.958730044531032e-05, + "99.999": 6.958730044531032e-05, + "99.9999": 6.958730044531032e-05, + "100.0": 6.958730044531032e-05 + }, + "scoreUnit": "s/op", + "rawData": [ + [ + 6.342663043753963e-05, + 6.413363150807899e-05, + 6.75012582669726e-05, + 6.743249794377402e-05, + 6.657319154742096e-05 + ], + [ + 6.958730044531032e-05, + 6.857982686505759e-05, + 6.836027286397813e-05, + 6.841834060747024e-05, + 6.800295553440304e-05 + ], + [ + 6.94918858393552e-05, + 6.832567481896433e-05, + 6.822538021828103e-05, + 6.918970121065376e-05, + 6.819244058089589e-05 + ] + ] + }, + "secondaryMetrics": {} + } +] diff --git a/performance/2026-09-09-split/before-jmh.json b/performance/2026-09-09-split/before-jmh.json new file mode 100644 index 0000000..7615387 --- /dev/null +++ b/performance/2026-09-09-split/before-jmh.json @@ -0,0 +1,380 @@ +[ + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "simpleText" + }, + "primaryMetric" : { + "score" : 2.7861177968744074E-5, + "scoreError" : 2.911922900505013E-6, + "scoreConfidence" : [ + 2.494925506823906E-5, + 3.077310086924909E-5 + ], + "scorePercentiles" : { + "0.0" : 2.6910095086907388E-5, + "50.0" : 2.7661463602168382E-5, + "90.0" : 2.881266400668357E-5, + "95.0" : 2.881266400668357E-5, + "99.0" : 2.881266400668357E-5, + "99.9" : 2.881266400668357E-5, + "99.99" : 2.881266400668357E-5, + "99.999" : 2.881266400668357E-5, + "99.9999" : 2.881266400668357E-5, + "100.0" : 2.881266400668357E-5 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 2.7661463602168382E-5, + 2.881266400668357E-5, + 2.8417902909586862E-5, + 2.6910095086907388E-5, + 2.750376423837417E-5 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "text" + }, + "primaryMetric" : { + "score" : 0.06041590843529412, + "scoreError" : 6.056857237638408E-4, + "scoreConfidence" : [ + 0.059810222711530275, + 0.06102159415905796 + ], + "scorePercentiles" : { + "0.0" : 0.06014622823529412, + "50.0" : 0.060454885588235294, + "90.0" : 0.060547520647058824, + "95.0" : 0.060547520647058824, + "99.0" : 0.060547520647058824, + "99.9" : 0.060547520647058824, + "99.99" : 0.060547520647058824, + "99.999" : 0.060547520647058824, + "99.9999" : 0.060547520647058824, + "100.0" : 0.060547520647058824 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.06014622823529412, + 0.060430179647058824, + 0.060454885588235294, + 0.060500728058823526, + 0.060547520647058824 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "model3D" + }, + "primaryMetric" : { + "score" : 6.686024726012978E-5, + "scoreError" : 4.189482418431631E-6, + "scoreConfidence" : [ + 6.267076484169815E-5, + 7.10497296785614E-5 + ], + "scorePercentiles" : { + "0.0" : 6.499838984923317E-5, + "50.0" : 6.746798745277928E-5, + "90.0" : 6.757348270503986E-5, + "95.0" : 6.757348270503986E-5, + "99.0" : 6.757348270503986E-5, + "99.9" : 6.757348270503986E-5, + "99.99" : 6.757348270503986E-5, + "99.999" : 6.757348270503986E-5, + "99.9999" : 6.757348270503986E-5, + "100.0" : 6.757348270503986E-5 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 6.757348270503986E-5, + 6.748003926595602E-5, + 6.746798745277928E-5, + 6.678133702764053E-5, + 6.499838984923317E-5 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Rainier.bmp" + }, + "primaryMetric" : { + "score" : 0.10832889366000001, + "scoreError" : 0.0016506001920435149, + "scoreConfidence" : [ + 0.1066782934679565, + 0.10997949385204352 + ], + "scorePercentiles" : { + "0.0" : 0.1079189768, + "50.0" : 0.108242564, + "90.0" : 0.1090366517, + "95.0" : 0.1090366517, + "99.0" : 0.1090366517, + "99.9" : 0.1090366517, + "99.99" : 0.1090366517, + "99.999" : 0.1090366517, + "99.9999" : 0.1090366517, + "100.0" : 0.1090366517 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.1090366517, + 0.108242564, + 0.1079189768, + 0.1083576889, + 0.1080885869 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Maltese.bmp" + }, + "primaryMetric" : { + "score" : 0.4779739153333333, + "scoreError" : 0.007987030924469857, + "scoreConfidence" : [ + 0.46998688440886344, + 0.4859609462578031 + ], + "scorePercentiles" : { + "0.0" : 0.476138847, + "50.0" : 0.477748715, + "90.0" : 0.481469577, + "95.0" : 0.481469577, + "99.0" : 0.481469577, + "99.9" : 0.481469577, + "99.99" : 0.481469577, + "99.999" : 0.481469577, + "99.9999" : 0.481469577, + "100.0" : 0.481469577 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.476735972, + 0.481469577, + 0.47777646566666665, + 0.477748715, + 0.476138847 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "Sunrise.bmp" + }, + "primaryMetric" : { + "score" : 1.6730243644000002, + "scoreError" : 0.07340701551445698, + "scoreConfidence" : [ + 1.5996173488855432, + 1.7464313799144573 + ], + "scorePercentiles" : { + "0.0" : 1.644701101, + "50.0" : 1.67681155, + "90.0" : 1.697225124, + "95.0" : 1.697225124, + "99.0" : 1.697225124, + "99.9" : 1.697225124, + "99.99" : 1.697225124, + "99.999" : 1.697225124, + "99.9999" : 1.697225124, + "100.0" : 1.697225124 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 1.697225124, + 1.678261324, + 1.668122723, + 1.67681155, + 1.644701101 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "com.rafambn.kflate.benchmark.CompressionBenchmarks.rawDeflateCompression", + "mode" : "avgt", + "threads" : 1, + "forks" : 1, + "jvm" : "/home/rafael/.jdks/jbr-17.0.14/bin/java", + "jvmArgs" : [ + ], + "jdkVersion" : "17.0.14", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.14+1-b1367.22", + "warmupIterations" : 3, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 5, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "corpus" : "compressed_MVT.pbf" + }, + "primaryMetric" : { + "score" : 0.00450055958947036, + "scoreError" : 5.6866598483374845E-5, + "scoreConfidence" : [ + 0.004443692990986985, + 0.004557426187953735 + ], + "scorePercentiles" : { + "0.0" : 0.004483004799107143, + "50.0" : 0.004503572950672646, + "90.0" : 0.004517674288288288, + "95.0" : 0.004517674288288288, + "99.0" : 0.004517674288288288, + "99.9" : 0.004517674288288288, + "99.99" : 0.004517674288288288, + "99.999" : 0.004517674288288288, + "99.9999" : 0.004517674288288288, + "100.0" : 0.004517674288288288 + }, + "scoreUnit" : "s/op", + "rawData" : [ + [ + 0.004503572950672646, + 0.004487895139013453, + 0.004483004799107143, + 0.00451065077027027, + 0.004517674288288288 + ] + ] + }, + "secondaryMetrics" : { + } + } +] diff --git a/performance/2026-09-09-split/before-sizes.jsonl b/performance/2026-09-09-split/before-sizes.jsonl new file mode 100644 index 0000000..6b0a261 --- /dev/null +++ b/performance/2026-09-09-split/before-sizes.jsonl @@ -0,0 +1,70 @@ +{"corpus":"simpleText","level":0,"originalSizeBytes":100,"compressedSizeBytes":105,"singleRunMs":18.721607} +{"corpus":"simpleText","level":1,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":10.375607} +{"corpus":"simpleText","level":2,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.346866} +{"corpus":"simpleText","level":3,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.426491} +{"corpus":"simpleText","level":4,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.288781} +{"corpus":"simpleText","level":5,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.294199} +{"corpus":"simpleText","level":6,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.256687} +{"corpus":"simpleText","level":7,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.217747} +{"corpus":"simpleText","level":8,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.213022} +{"corpus":"simpleText","level":9,"originalSizeBytes":100,"compressedSizeBytes":84,"singleRunMs":0.510794} +{"corpus":"text","level":0,"originalSizeBytes":1232923,"compressedSizeBytes":1233018,"singleRunMs":4.634060} +{"corpus":"text","level":1,"originalSizeBytes":1232923,"compressedSizeBytes":599813,"singleRunMs":54.476717} +{"corpus":"text","level":2,"originalSizeBytes":1232923,"compressedSizeBytes":581519,"singleRunMs":33.454095} +{"corpus":"text","level":3,"originalSizeBytes":1232923,"compressedSizeBytes":565252,"singleRunMs":34.883134} +{"corpus":"text","level":4,"originalSizeBytes":1232923,"compressedSizeBytes":531466,"singleRunMs":59.907435} +{"corpus":"text","level":5,"originalSizeBytes":1232923,"compressedSizeBytes":521837,"singleRunMs":61.611316} +{"corpus":"text","level":6,"originalSizeBytes":1232923,"compressedSizeBytes":506455,"singleRunMs":76.163221} +{"corpus":"text","level":7,"originalSizeBytes":1232923,"compressedSizeBytes":506097,"singleRunMs":78.619466} +{"corpus":"text","level":8,"originalSizeBytes":1232923,"compressedSizeBytes":513489,"singleRunMs":83.872443} +{"corpus":"text","level":9,"originalSizeBytes":1232923,"compressedSizeBytes":492523,"singleRunMs":220.517381} +{"corpus":"model3D","level":0,"originalSizeBytes":2478,"compressedSizeBytes":2483,"singleRunMs":0.054391} +{"corpus":"model3D","level":1,"originalSizeBytes":2478,"compressedSizeBytes":2169,"singleRunMs":0.236631} +{"corpus":"model3D","level":2,"originalSizeBytes":2478,"compressedSizeBytes":2168,"singleRunMs":0.159924} +{"corpus":"model3D","level":3,"originalSizeBytes":2478,"compressedSizeBytes":2166,"singleRunMs":0.143489} +{"corpus":"model3D","level":4,"originalSizeBytes":2478,"compressedSizeBytes":2165,"singleRunMs":0.133548} +{"corpus":"model3D","level":5,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.726203} +{"corpus":"model3D","level":6,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.277670} +{"corpus":"model3D","level":7,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.243557} +{"corpus":"model3D","level":8,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.184029} +{"corpus":"model3D","level":9,"originalSizeBytes":2478,"compressedSizeBytes":2153,"singleRunMs":0.404074} +{"corpus":"Rainier.bmp","level":0,"originalSizeBytes":6220854,"compressedSizeBytes":6221329,"singleRunMs":4.097972} +{"corpus":"Rainier.bmp","level":1,"originalSizeBytes":6220854,"compressedSizeBytes":3387685,"singleRunMs":127.041113} +{"corpus":"Rainier.bmp","level":2,"originalSizeBytes":6220854,"compressedSizeBytes":3357387,"singleRunMs":101.260474} +{"corpus":"Rainier.bmp","level":3,"originalSizeBytes":6220854,"compressedSizeBytes":3345679,"singleRunMs":106.363578} +{"corpus":"Rainier.bmp","level":4,"originalSizeBytes":6220854,"compressedSizeBytes":3325593,"singleRunMs":100.669595} +{"corpus":"Rainier.bmp","level":5,"originalSizeBytes":6220854,"compressedSizeBytes":3313388,"singleRunMs":105.538266} +{"corpus":"Rainier.bmp","level":6,"originalSizeBytes":6220854,"compressedSizeBytes":3283450,"singleRunMs":113.997284} +{"corpus":"Rainier.bmp","level":7,"originalSizeBytes":6220854,"compressedSizeBytes":3282047,"singleRunMs":120.376039} +{"corpus":"Rainier.bmp","level":8,"originalSizeBytes":6220854,"compressedSizeBytes":3274905,"singleRunMs":118.865381} +{"corpus":"Rainier.bmp","level":9,"originalSizeBytes":6220854,"compressedSizeBytes":3258570,"singleRunMs":3625.149389} +{"corpus":"Maltese.bmp","level":0,"originalSizeBytes":16427390,"compressedSizeBytes":16428645,"singleRunMs":6.704834} +{"corpus":"Maltese.bmp","level":1,"originalSizeBytes":16427390,"compressedSizeBytes":8344362,"singleRunMs":287.839174} +{"corpus":"Maltese.bmp","level":2,"originalSizeBytes":16427390,"compressedSizeBytes":7762283,"singleRunMs":334.470960} +{"corpus":"Maltese.bmp","level":3,"originalSizeBytes":16427390,"compressedSizeBytes":7494606,"singleRunMs":371.202987} +{"corpus":"Maltese.bmp","level":4,"originalSizeBytes":16427390,"compressedSizeBytes":7272625,"singleRunMs":395.378098} +{"corpus":"Maltese.bmp","level":5,"originalSizeBytes":16427390,"compressedSizeBytes":7265831,"singleRunMs":509.528066} +{"corpus":"Maltese.bmp","level":6,"originalSizeBytes":16427390,"compressedSizeBytes":7158472,"singleRunMs":523.034253} +{"corpus":"Maltese.bmp","level":7,"originalSizeBytes":16427390,"compressedSizeBytes":7157735,"singleRunMs":526.984351} +{"corpus":"Maltese.bmp","level":8,"originalSizeBytes":16427390,"compressedSizeBytes":7125327,"singleRunMs":524.641283} +{"corpus":"Maltese.bmp","level":9,"originalSizeBytes":16427390,"compressedSizeBytes":7034092,"singleRunMs":1436.734506} +{"corpus":"Sunrise.bmp","level":0,"originalSizeBytes":52344054,"compressedSizeBytes":52348049,"singleRunMs":31.612486} +{"corpus":"Sunrise.bmp","level":1,"originalSizeBytes":52344054,"compressedSizeBytes":27793016,"singleRunMs":887.391514} +{"corpus":"Sunrise.bmp","level":2,"originalSizeBytes":52344054,"compressedSizeBytes":27471887,"singleRunMs":1064.576845} +{"corpus":"Sunrise.bmp","level":3,"originalSizeBytes":52344054,"compressedSizeBytes":27404419,"singleRunMs":1166.086238} +{"corpus":"Sunrise.bmp","level":4,"originalSizeBytes":52344054,"compressedSizeBytes":27107867,"singleRunMs":1250.581032} +{"corpus":"Sunrise.bmp","level":5,"originalSizeBytes":52344054,"compressedSizeBytes":27096750,"singleRunMs":1511.964986} +{"corpus":"Sunrise.bmp","level":6,"originalSizeBytes":52344054,"compressedSizeBytes":26838825,"singleRunMs":1479.025315} +{"corpus":"Sunrise.bmp","level":7,"originalSizeBytes":52344054,"compressedSizeBytes":26827604,"singleRunMs":1687.182227} +{"corpus":"Sunrise.bmp","level":8,"originalSizeBytes":52344054,"compressedSizeBytes":26728454,"singleRunMs":1866.351752} +{"corpus":"Sunrise.bmp","level":9,"originalSizeBytes":52344054,"compressedSizeBytes":26372269,"singleRunMs":12143.996321} +{"corpus":"compressed_MVT.pbf","level":0,"originalSizeBytes":146224,"compressedSizeBytes":146239,"singleRunMs":0.056884} +{"corpus":"compressed_MVT.pbf","level":1,"originalSizeBytes":146224,"compressedSizeBytes":95812,"singleRunMs":2.498114} +{"corpus":"compressed_MVT.pbf","level":2,"originalSizeBytes":146224,"compressedSizeBytes":94608,"singleRunMs":2.921274} +{"corpus":"compressed_MVT.pbf","level":3,"originalSizeBytes":146224,"compressedSizeBytes":94345,"singleRunMs":3.426504} +{"corpus":"compressed_MVT.pbf","level":4,"originalSizeBytes":146224,"compressedSizeBytes":93369,"singleRunMs":3.443246} +{"corpus":"compressed_MVT.pbf","level":5,"originalSizeBytes":146224,"compressedSizeBytes":93229,"singleRunMs":3.893521} +{"corpus":"compressed_MVT.pbf","level":6,"originalSizeBytes":146224,"compressedSizeBytes":92605,"singleRunMs":4.154555} +{"corpus":"compressed_MVT.pbf","level":7,"originalSizeBytes":146224,"compressedSizeBytes":92610,"singleRunMs":4.015288} +{"corpus":"compressed_MVT.pbf","level":8,"originalSizeBytes":146224,"compressedSizeBytes":92631,"singleRunMs":4.439951} +{"corpus":"compressed_MVT.pbf","level":9,"originalSizeBytes":146224,"compressedSizeBytes":91727,"singleRunMs":8.610094} diff --git a/performance/2026-09-09-split/native_bench.py b/performance/2026-09-09-split/native_bench.py new file mode 100644 index 0000000..7a66ddc --- /dev/null +++ b/performance/2026-09-09-split/native_bench.py @@ -0,0 +1,38 @@ +import json +from pathlib import Path +import subprocess +import sys + +binary = str(Path(sys.argv[1]).resolve()) +directory = Path(sys.argv[2]).resolve() +directory.mkdir(parents=True, exist_ok=True) +config = directory / 'runner.cfg' +config.write_text('\n'.join([ + 'name:ratio', f'reportFile:{directory}/benchmark.json', + 'traceFormat:text', 'reportFormat:json', + 'include:' + (sys.argv[5] if len(sys.argv) > 5 else '.*CompressionBenchmarks.rawDeflateCompression'), + f'warmups:{sys.argv[3] if len(sys.argv) > 3 else 3}', + f'iterations:{sys.argv[4] if len(sys.argv) > 4 else 5}', + 'iterationTime:1', 'iterationTimeUnit:sec', + 'outputTimeUnit:sec', 'mode:avgt', +])) + +def run(action, *args): + subprocess.run([binary, str(config), action, str(directory / 'trace.xml'), *map(str, args)], check=True) + +run('--list', directory) +configs = sorted(directory.glob('*.txt')) +expected = 14 if len(sys.argv) > 5 and 'Decompression' in sys.argv[5] else 7 +assert len(configs) == expected, configs +results = [] +for benchmark_config in configs: + samples = benchmark_config.with_suffix('.samples') + run('--benchmark', benchmark_config, samples) + values = samples.read_text().strip() + assert values and 'null' not in values + results.append(f'{benchmark_config}: {values}') +combined = directory / 'results' +combined.write_text('\n'.join(results)) +run('--store-results', combined) +rows = json.loads((directory / 'benchmark.json').read_text()) +assert len(rows) == expected