Image processing benchmarks
Latest stable Based on Image release 0.3.0
Performance benchmark
Problem
Section titled “Problem”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.
When to use it
Section titled “When to use it”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.
Coordinates
Section titled “Coordinates”The benchmark is repository-only and publishes no artifact. Its project dependencies are aligned from the same bluetape4k-dependencies release catalog used by the 0.3.0 source.
Core concepts
Section titled “Core concepts”AverageTimeis lower-is-better; throughput is higher-is-better.- Fixture, host, JVM, backend, warmups, iterations, fork count, command, and raw JSON belong to every comparison.
- Java 25 selects the FFM backend;
-Pvips.impl=java21selects the Java 21 JNI backend. - GC-profiler results cover managed heap only, not libvips native memory.
- Full pipelines provide stronger application evidence than isolated, potentially lazy geometry operations.
Quick start
Section titled “Quick start”For the Java 25 FFM lane, install JDK 25 and system libvips (brew install vips on macOS).
./gradlew :bluetape4k-images-benchmark:benchmarkBenchmark \ -Pvips.impl=java25 --console=plainFocused large-file evidence:
./gradlew :bluetape4k-images-benchmark:benchmarkLargeStreamingBenchmark \ -Pvips.impl=java25 --console=plainRun backends sequentially on the same compatible host when comparing Java 21 JNI with Java 25 FFM.
API by task
Section titled “API by task”| Question | Benchmark |
|---|---|
| Resize/encode backend latency | ImageResizeBenchmark, ImageEncodeBenchmark |
| Scrimage filter cost | ImageFilterBenchmark |
| Chained transform allocation | ImagePipelineBenchmark |
| Path/stream/Okio/suspended boundary | ImageIoBoundaryBenchmark |
| Concurrent compressed-file throughput | ImageFileIoThroughputBenchmark |
| End-to-end large-file behavior | ImageLargeStreamingBenchmark |
| JNI versus FFM wrapper | VipsBackendBenchmark, VipsBackendEncodeBenchmark |
Recommended patterns
Section titled “Recommended patterns”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.
Integrations
Section titled “Integrations”The suite compares bluetape4k-images with the binding-neutral vips API and the Java 21 JNI or Java 25 FFM runtime.
Configuration
Section titled “Configuration”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 selects a JDK 21 or 25 toolchain from vips.impl and adds --enable-native-access=ALL-UNNAMED for benchmark forks.
Failure modes
Section titled “Failure modes”- Near-zero vips rows: verify native availability; unavailable methods consume
nulland return immediately. - Java 21 JNI skips on macOS arm64: the recorded 0.3.0 host found an incompatible x86_64 JVips dylib; use a compatible host.
- 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.
Operations
Section titled “Operations”Representative 0.3.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.
Testing
Section titled “Testing”Validate task wiring without paying for a full run:
./gradlew :bluetape4k-images-benchmark:benchmarkBenchmark \ -Pvips.impl=java25 --dry-run --console=plainFor publishable evidence, retain generated JSON, record the exact command/environment, update the dated report, and regenerate matching SVG/PNG charts.
Workshops and learning path
Section titled “Workshops and learning path”- Run
basic-processingto understand the user-facing operations. - Read the natural-photo result and benchmark source together.
- Study IO-boundary and allocation reports before interpreting coroutine APIs.
- Use the large-streaming report for the strongest end-to-end backend comparison.
- Re-run the focused scenario on deployment-like hardware before deciding.
Limitations
Section titled “Limitations”- The 0.3.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 Java 21 JNI measurements.
vips_resizedoes 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.
Release diagrams
Section titled “Release diagrams”These diagrams are loaded directly from README assets published with the 0.3.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
Section titled “images benchmark Architecture diagram”Release README: benchmark/images-benchmark/README.md
