Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e90be34
Infra: migrate to kotlinx-benchmark and update project identity
rafambn May 5, 2026
2cbb8f2
Refactor benchmarks to simplify raw compression testing
rafambn May 5, 2026
6cbee92
Docs: update documentation snippets and benchmarking instructions
rafambn May 5, 2026
9897535
Build: add automated benchmark comparison reporting
rafambn May 5, 2026
ba67a1f
Build: improve benchmark metadata handling and increase measurement p…
rafambn May 5, 2026
bddfc68
Separate compression APIs without type collisions
rafambn Sep 4, 2026
432848f
Reject reserved DEFLATE literal codes
rafambn Aug 31, 2026
f27e457
Clarify fflate attribution and package the license
rafambn Aug 31, 2026
c9577ef
Remove unsupported GZIP dictionaries (#5)
rafambn Sep 5, 2026
84f22cd
Handle partial ZLIB streaming headers (#6)
rafambn Sep 5, 2026
025226d
Reject empty raw compressed input (#7)
rafambn Sep 5, 2026
87a65ed
Validate GZIP metadata before encoding (#8)
rafambn Sep 5, 2026
2cf8a2a
Bound decompression output before allocation
rafambn Sep 5, 2026
bfd79b5
Simplify decompression output limits (#17)
rafambn Sep 5, 2026
a91961b
Reject sizes that overflow internal buffers (#12)
rafambn Sep 5, 2026
ee03df8
Decode DEFLATE streams across chunk boundaries (#13)
rafambn Sep 5, 2026
c54cd40
Keep the web demo responsive on worker failures (#14)
rafambn Sep 5, 2026
ed1a1da
Cap web demo decompression at 128 MiB (#16)
rafambn Sep 5, 2026
aee9b08
Keep the error factory internal (#19)
rafambn Sep 5, 2026
9f7e2ac
Give internal result types their own files (#20)
rafambn Sep 5, 2026
ba56d57
Stop repeating large fixtures across tests (#22)
rafambn Sep 5, 2026
331b183
Build every target on pull requests (#23)
rafambn Sep 6, 2026
9abb15c
Keep JVM coverage at 100%
rafambn Sep 6, 2026
766c95b
Throw FlateError directly
rafambn Sep 6, 2026
4af757e
Prepare the 1.1.0 release docs and artifacts
rafambn Sep 6, 2026
c993daf
Improve higher DEFLATE compression levels
rafambn Sep 6, 2026
91ad6c4
Let compression levels manage hash-table memory
rafambn Sep 6, 2026
71d4753
Cut level 9 output with cost-aware parsing
rafambn Sep 6, 2026
d0fec3b
Make benchmark results representative and reviewable
rafambn Sep 6, 2026
e5992df
Reject invalid measurements in benchmark reports
rafambn Sep 6, 2026
41dbe52
Record full Linux benchmark results for PR 30
rafambn Sep 7, 2026
26a7434
Merge pull request #30 from rafambn/fix/meaningful-benchmarks
rafambn Sep 7, 2026
3de10ca
Integrate benchmarks with automatic compression memory
rafambn Sep 7, 2026
aa841e7
Merge pull request #29 from rafambn/api/remove-memory-level
rafambn Sep 7, 2026
a82348b
Integrate automatic memory sizing with compression parsers
rafambn Sep 7, 2026
f1c5c43
Merge pull request #28 from rafambn/perf/make-compression-levels-earn…
rafambn Sep 7, 2026
279b287
Align release documentation with final compression API
rafambn Sep 7, 2026
d770186
Show the latest benchmark results on the Wasm demo
rafambn Sep 7, 2026
b995b5d
Make the compression demo easier to use and inspect
rafambn Sep 8, 2026
047692d
Merge pull request #27 from rafambn/release/prepare-1.1.0
rafambn Sep 8, 2026
36a54b1
cache benchmark baselines and publish level history
rafambn Sep 9, 2026
f980d13
remove trailing blank lines from benchmark report
rafambn Sep 9, 2026
db56184
Simplify benchmarks with library selection and retain collected data
rafambn Sep 9, 2026
38e1cd8
Keep archived benchmark commits usable in shallow checkouts
rafambn Sep 9, 2026
fe2ff51
Merge pull request #31 from rafambn/feat/cached-benchmark-baselines
rafambn Sep 9, 2026
5e34cfc
Choose stored blocks using their actual padding cost
rafambn Sep 9, 2026
374b326
Reuse fixed token prices in level 9 parsing
rafambn Sep 9, 2026
4c78b02
Expand inflate matches with bulk copies
rafambn Sep 9, 2026
4cc3f89
Cut scratch allocation for short compression inputs
rafambn Sep 9, 2026
ba01d65
Merge pull request #40 from rafambn/agent/deflate-scratch-sizing
rafambn Sep 9, 2026
7592ba5
Shorten benchmark runs with one JVM fork and ten measurements
rafambn Sep 9, 2026
060f957
Merge pull request #39 from rafambn/agent/inflate-copy-speed
rafambn Sep 9, 2026
8978d9f
Merge branch 'dev-1.1.0' into cost-parser-speed
rafambn Sep 9, 2026
c2ffe0d
Merge pull request #35 from rafambn/cost-parser-speed
rafambn Sep 9, 2026
611c594
Merge pull request #33 from rafambn/agent/huffman-ratio
rafambn Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: CI

on:
pull_request:

permissions:
contents: read

jobs:
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: '17'
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Test benchmark scripts
run: |
python3 scripts/benchmark_comparison_test.py
python3 scripts/validate_benchmark_smoke_test.py
- name: Build JVM, Android, web, Linux, and Android Native
run: >-
./gradlew
:kflate:koverVerifyJvm
:kflate:jvmBenchmarkSmokeBenchmark
:kflate:linuxX64BenchmarkSmokeBenchmark
:kflate:wasmJsBenchmarkSmokeBenchmark
:kflate:compileAndroidMain
:kflate:compileKotlinJs
:kflate:compileKotlinWasmJs
:web-demo:assembleWebDemo
:kflate:compileKotlinLinuxX64
:kflate:compileKotlinLinuxArm64
:kflate:compileKotlinAndroidNativeArm32
:kflate:compileKotlinAndroidNativeArm64
:kflate:compileKotlinAndroidNativeX64
:kflate:compileKotlinAndroidNativeX86

- name: Validate benchmark smoke reports
run: python3 scripts/validate_benchmark_smoke.py

apple:
runs-on: macos-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: '17'
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Build Apple targets
run: >-
./gradlew
:kflate:compileKotlinIosX64
:kflate:compileKotlinIosArm64
:kflate:compileKotlinIosSimulatorArm64
:kflate:compileKotlinMacosArm64
:kflate:compileKotlinTvosArm64
:kflate:compileKotlinTvosSimulatorArm64
:kflate:compileKotlinWatchosArm32
:kflate:compileKotlinWatchosArm64
:kflate:compileKotlinWatchosSimulatorArm64

windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: '17'
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Build Windows target
run: ./gradlew.bat :kflate:compileKotlinMingwX64
6 changes: 5 additions & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:

- uses: gradle/actions/setup-gradle@v4

- name: Validate published benchmark snapshot
run: python3 -m json.tool performance/latest.json > /dev/null

- name: Build WASM + assemble demo
run: |
chmod +x gradlew
Expand All @@ -36,7 +39,8 @@ jobs:
- name: Assemble deploy directory
run: |
mkdir -p deploy
cp web-demo/build/webDemo/* deploy/
cp -R web-demo/build/webDemo/. deploy/
cmp performance/latest.json deploy/benchmark-results.json

- name: Deploy to gh-pages branch
uses: peaceiris/actions-gh-pages@v4
Expand Down
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,12 @@ Pods/
kotlin-js-store
.claude
docs
CLAUDE.md
AGENTS.md
.impeccable/
DESIGN.md
design.md
performance/.benchmark.lock
performance/kompress-baseline/
performance/smoke-runs/
performance/kompress-baseline/*/smoke.json
performance/kompress-baseline/*/smoke-raw/
205 changes: 205 additions & 0 deletions BENCHMARKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Benchmarking

KFlate keeps correctness tests separate from performance benchmarks.

## What the suite measures

The suite measures the one-shot RAW DEFLATE APIs of KFlate and Kompress on JVM, Linux x64 Native, and Wasm/JS.
Both compressors use compression level 6. KFlate sizes its hash table automatically from the compression level and input size. Kompress maps to a different backend on each platform:

| Platform | Kompress backend |
| --- | --- |
| JVM | `java.util.zip` |
| Linux x64 Native | platform `zlib` |
| Wasm/JS | npm `fflate` |

These are end-to-end API comparisons. They include output allocation and any backend bridge cost.

Compare KFlate and Kompress only when the platform, corpus, operation, and decompression stream producer match.
Do not rank JVM, Native, and Wasm absolute times against one another. Their runtimes, code generation, and Kompress backends differ.

The active suite covers RAW DEFLATE only. KFlate also supports GZIP and ZLIB, but this suite makes no performance claim about them.

## Corpus

Every target reads the same tracked files from `kflate/src/jvmTest/resources`:

- `simpleText`
- `text`
- `model3D`
- `Rainier.bmp`
- `Maltese.bmp`
- `Sunrise.bmp`
- `compressed_MVT.pbf`

`BenchmarkCorpus` searches upward from the benchmark process working directory for the repository or `kflate` module.
Running a benchmark artifact outside the checkout fails instead of silently substituting generated data.
The report takes original sizes from benchmark metadata rather than a second hard-coded size table.

Do not rename corpus files, classes, or methods casually. Result history uses their names as stable identifiers.

## Measurement configuration

The main configuration uses average-time mode, JSON output, 8 warmup iterations, and 10 one-second measurement iterations.
JVM benchmarks use 1 fresh JVM fork. Native and Wasm use their runner's process model and do not inherit the JVM fork setting.
For release claims, repeat the full Native and Wasm commands in separate quiet system sessions and compare the retained raw samples.

The generated JSON summary preserves:

- average time and error
- confidence interval
- p50 and p95 of iteration-level average times
- fork and sample counts
- all raw iteration averages
- runtime and runner fields supplied by kotlinx-benchmark

The Markdown report shows the averages, errors, confidence intervals, and percentiles.
Use the raw JSON when investigating regressions or noisy results.

## Run benchmarks

Compile the configured benchmarks:

~~~bash
./gradlew :kflate:assembleBenchmarks
~~~

Run all configured targets and generate comparison reports:

~~~bash
./gradlew :kflate:benchmarkAll
~~~

Run benchmarkAll on a Linux x64 host. Other hosts cannot execute the Linux x64 Native target, and the comparison script rejects an incomplete release report.

Run one target:

~~~bash
./gradlew :kflate:jvmBenchmarkBenchmark
./gradlew :kflate:linuxX64BenchmarkBenchmark
./gradlew :kflate:wasmJsBenchmarkBenchmark
~~~

Wasm uses the Kotlin Wasm yarn lock. Update it if Gradle reports a changed lock:

~~~bash
./gradlew kotlinWasmUpgradeYarnLock
~~~

The benchmark plugin writes target reports under:

~~~text
kflate/build/reports/benchmarks/main/<timestamp>/
~~~

`benchmarkAll` clears prior reports and metadata first. It then merges platform metadata into
`kflate/performance/benchmark-metadata.jsonl` and runs `scripts/benchmark_comparison.py`.

The comparison script writes:

~~~text
performance/benchmark-comparison-<timestamp>.md
performance/benchmark-comparison-<timestamp>.json
~~~

Use `--output`, `--json-output`, `--metadata`, or `--run-dir` to override those paths.
Automatic report selection chooses the newest timestamp directory and rejects missing platforms or benchmark rows.
Reports also reject missing or empty sample forks and nonnumeric, nonfinite, or nonpositive scores and samples.
These measurement checks apply even with `--allow-partial`; invalid samples are never discarded.
It never mixes files from different directories or rejects a platform because another target took longer to finish.
`--allow-partial` and `--allow-missing-sizes` exist for local investigation, not release reports.

## Results on the demo page

`benchmarkAll` also publishes the complete comparison to `performance/latest.json`.
Commit this snapshot with the benchmark reports to update the demo's latest run.
Smoke benchmarks and partial comparisons never replace it.

The demo build copies this tracked file to `benchmark-results.json`. Both local
browser builds and `assembleWebDemo` use that same source. GitHub Pages deploys
the assembled snapshot and checks that it matches the tracked file; deployment
does not rerun the benchmark suite or search timestamp directories.

To publish an archived complete run, invoke `scripts/benchmark_comparison.py`
with `--publish-latest`, its `--run-dir` and `--metadata`, and
`--benchmark-commit <measured SHA>`. Abbreviated commits are resolved to their full SHA.
If the archived directory is named `raw` rather than a timestamp, also pass
`--run-id <measured date or timestamp>`. Publication rejects undated run IDs.
Otherwise publication records the current
Git HEAD. Keep the checkout unchanged while running `benchmarkAll`.
The page identifies the measured run and commit, which can predate the demo code.

## Benchmark matrix

Compression times each library's own level-6 compressor and records its compressed size:

| Operation | KFlate method | Kompress method |
| --- | --- | --- |
| RAW compression | `CompressionBenchmarks.rawDeflateCompression` | `KompressBaselineBenchmarks.rawDeflateCompression` |

Decompression tests both decoders against streams produced by both compressors:

| Stream producer | KFlate method | Kompress method |
| --- | --- | --- |
| KFlate | `CompressionBenchmarks.rawDeflateDecompressionFromKFlate` | `KompressBaselineBenchmarks.rawDeflateDecompressionFromKFlate` |
| Kompress | `CompressionBenchmarks.rawDeflateDecompressionFromKompress` | `KompressBaselineBenchmarks.rawDeflateDecompressionFromKompress` |

Setup verifies that each decoder reproduces the original corpus from both streams before timing begins.
This catches incompatible output without including validation work in measured time.

## Reading results

Treat a small difference as noise when confidence intervals are wide or raw samples drift.
The report keeps both compressed size and time because a faster compressor that produces materially larger output is a tradeoff, not an unconditional win.

Throughput can be calculated for a row from its original byte count and average-time score:

~~~text
throughputMiBPerSecond = originalSizeBytes / 1,048,576 / averageSeconds
~~~

Record the Git commit, machine, operating system, JDK, Node version, and system load with any published result.
The cleaned JSON retains environment fields present in the source reports, but it cannot detect thermal throttling or competing processes.

## Choose which library to measure

The default is `both`. Select one library while developing:

```sh
./gradlew :kflate:benchmarkAll -PbenchmarkLibrary=kflate
./gradlew :kflate:benchmarkAll -PbenchmarkLibrary=kompress
./gradlew :kflate:benchmarkAll -PbenchmarkLibrary=both
```

The same property applies to individual platform tasks and smoke tasks, for example
`:kflate:jvmBenchmarkSmokeBenchmark -PbenchmarkLibrary=kompress`.
Only the selected library is timed. Setup still creates both libraries' streams
and validates the selected decoder against them, outside the timed operation.
No saved binary fixtures are required.

Single-library runs produce the usual timestamped Markdown and JSON reports,
with the unmeasured library shown as unavailable. Reports record the selected
libraries, runner environment, measured commit, and report host. When reporting
an archived run, pass `--benchmark-commit`; the report host is the machine
generating the report, which may differ from the benchmark machine.
They validate every expected
case for the selected library and never replace `performance/latest.json`.
When generating a report manually, pass the matching `--library kflate` or
`--library kompress` to `scripts/benchmark_comparison.py`.

Retain the generated reports before starting another full run, which clears the
raw build reports. For reproducibility, also copy the raw report directory and
`kflate/performance/benchmark-metadata.jsonl` into an archive and record the
measured commit, machine/CPU, OS, JDK, Node, and benchmark configuration.
Use saved Kompress timings as a development reference. Rerun both libraries on
the same machine for published comparisons after runtime or hardware changes.

## Retained level benchmarks

`performance/history.json` and `performance/runs/2026-09-08-kflate-levels/`
are a static archive of the previous level 0–9 experiment. The history JSON
includes the Kompress measurements and their host, dependency, source, and
fixture hashes. The platform directories retain the KFlate raw reports and logs.
These files remain available for inspection; the current runner does not update
them or require the old fixture cache. Their level matrix differs from the
restored level-6 suite, so the comparison script does not ingest them.
Loading
Loading