Skip to content
Image docs1.0

Image processing benchmarks

Latest stable Based on Image release 1.0.0

Performance benchmark

This non-published module keeps reproducible evidence for Scrimage and libvips decisions. It measures geometry, encoding, filter chains, IO boundaries, concurrent compressed-file IO, managed allocation, and complete large-file load-transform-write pipelines using kotlinx-benchmark on its JVM JMH backend.

Use it before claiming that a backend or IO boundary is faster, when checking a performance regression, or when choosing Scrimage versus libvips for a measured workload. Do not use one headline ratio as a production capacity estimate.

The benchmark is repository-only and publishes no artifact. Its project dependencies are aligned from the same bluetape4k-dependencies release catalog used by the 1.0.0 source.

  • AverageTime is lower-is-better; throughput is higher-is-better.
  • Fixture, host, JVM, backend, warmups, iterations, fork count, command, and raw JSON belong to every comparison.
  • The benchmark runs on JDK 25. -Pvips.impl=java25 selects FFM, while -Pvips.impl=java21 selects the legacy JVips JNI backend; the property is a backend name and does not select a JDK 21 toolchain.
  • GC-profiler results cover managed heap only, not libvips native memory.
  • Full pipelines provide stronger application evidence than isolated, potentially lazy geometry operations.

For the Java 25 FFM lane, install JDK 25 and system libvips (brew install vips on macOS).

Terminal window
./gradlew :bluetape4k-images-benchmark:benchmarkBenchmark \
-Pvips.impl=java25 --console=plain

Focused large-file evidence:

Terminal window
./gradlew :bluetape4k-images-benchmark:benchmarkLargeStreamingBenchmark \
-Pvips.impl=java25 --console=plain

Run the JDK 25 backends sequentially on the same compatible host. Historical JDK 21 JNI rows remain frozen benchmark evidence and are not the current runtime baseline.

QuestionBenchmark
Resize/encode backend latencyImageResizeBenchmark, ImageEncodeBenchmark
Scrimage filter costImageFilterBenchmark
Chained transform allocationImagePipelineBenchmark
Path/stream/Okio/suspended boundaryImageIoBoundaryBenchmark
Concurrent compressed-file throughputImageFileIoThroughputBenchmark
End-to-end large-file behaviorImageLargeStreamingBenchmark
JNI versus FFM wrapperVipsBackendBenchmark, VipsBackendEncodeBenchmark

Start with the production-shaped scenario, then isolate a suspected boundary. Preserve raw JSON and environment metadata. Re-run both candidates on one host, one JVM policy, and equivalent fixtures. Use native profiling in addition to JMH GC data for libvips lifetime questions.

The suite compares bluetape4k-images with the binding-neutral vips API and the JDK 25 JVips JNI or JDK 25 FFM runtime.

The complete benchmark uses three warmups, five measurements, one fork, average-time mode, and milliseconds. Focused configurations use one warmup and three one-second measurements. The build uses the JDK 25 toolchain; vips.impl selects the backend and adds --enable-native-access=ALL-UNNAMED for FFM benchmark forks.

  • Near-zero vips rows: verify native availability; unavailable methods consume null and return immediately.
  • Historical Java 21 JNI row skips on macOS arm64: the recorded 1.0.0 host found an incompatible x86_64 JVips dylib; use a compatible host for reproductions and label the result as historical.
  • Large variance: avoid parallel benchmark processes, check thermal/load state, and keep fixtures and forks identical.
  • Native load failure: verify system libvips and the FFM/JNI library path before interpreting results.

Representative 1.0.0 evidence on macOS arm64, GraalVM Java 25.0.3 found the full libvips Path pipeline at 7.13 ms/op for large-photo and 5.47 ms/op for ocr-document; Scrimage Path measured 223.19 and 145.13 ms/op. These are local comparable snapshots, not a universal ranking. Scrimage suspended boundaries were slower in both boundary and many-file tests, so they are documented as lifecycle/integration APIs rather than performance optimizations.

Validate task wiring without paying for a full run:

Terminal window
./gradlew :bluetape4k-images-benchmark:benchmarkBenchmark \
-Pvips.impl=java25 --dry-run --console=plain

For publishable evidence, retain generated JSON, record the exact command/environment, update the dated report, and regenerate matching SVG/PNG charts.

  1. Run basic-processing to understand the user-facing operations.
  2. Read the natural-photo result and benchmark source together.
  3. Study IO-boundary and allocation reports before interpreting coroutine APIs.
  4. Use the large-streaming report for the strongest end-to-end backend comparison.
  5. Re-run the focused scenario on deployment-like hardware before deciding.
  • The 1.0.0 reports mix a fresh macOS Java 25 run with explicitly historical Linux rows; they are not one experiment.
  • The macOS arm64 run did not produce compatible historical Java 21 JNI measurements; this does not change the current JDK 25 requirement.
  • vips_resize does not encode its result; libvips lazy evaluation means geometry-only speedups do not equal completed pixel-pipeline speedups.
  • GC allocation excludes native memory.
  • IO API convenience, latency, throughput, and backend selection are separate questions.
  • No benchmark proves production behavior under different data, concurrency, hardware, JVM, codecs, or storage.

These diagrams are loaded directly from README assets published with the 1.0.0 release and pinned to its immutable commit. They describe this manual’s released structure and runtime flows, not later Snapshot changes. Select a preview to open the SVG at the same release commit.

images benchmark Architecture diagram

Release README: benchmark/images-benchmark/README.md