Structural Similarity Benchmarks
Per-window image quality comparisons (SSIM family). Image decode is excluded.

Read each section against its own baseline, not against the other one. The two
metrics are not given the same amount of work: ssim follows MATLABβs ssim.m
and shrinks the image to ~256px on the short edge before it measures anything,
while hitchhikers-ssim always runs at full resolution. On the largest fixture
that is 264x573 pixels versus 3.8M.
That is also why the Rust port wins by so much more on one than the other (~15x vs ~4x). Nearly all of Hitchhikerβs time is the single full-resolution pass that builds its lookup tables, not the comparison itself β widening the window stride to compare 34x fewer positions moves it only 26.9ms -> 24.8ms. That pass is a running total, so it has to go pixel by pixel in order, whereas the convolutions it replaces vectorise. In JavaScript the trick is a large win because the math it avoids is slow there; in Rust it costs more than it saves.
Fast Original ( @blazediff/ssim using ssim vs ssim.js using fast algorithm) (image IO excluded)
25 iterations (3 warmup)
~31.5% performance improvement on average.
The percentage above is the JavaScript port. @blazediff/ssim-native is the
Rust one, wrapping the same algorithm β roughly 15x faster again than the JS
port, from the same decoded buffers.
| Benchmark | ssim.js | BlazeDiff | BlazeDiff (ssim-native) | BlazeDiff Saved | BlazeDiff % | BlazeDiff (ssim-native) Saved | BlazeDiff (ssim-native) % |
|---|---|---|---|---|---|---|---|
| blazediff/1 | 96.08ms | 66.90ms | 5.57ms | 29.18ms | 30.4% | 90.50ms | 94.2% |
| blazediff/1 (identical) | 97.84ms | 65.96ms | 5.53ms | 31.89ms | 32.6% | 92.32ms | 94.4% |
| blazediff/2 | 38.90ms | 23.56ms | 1.64ms | 15.34ms | 39.4% | 37.25ms | 95.8% |
| blazediff/2 (identical) | 38.33ms | 24.37ms | 1.61ms | 13.96ms | 36.4% | 36.72ms | 95.8% |
| blazediff/3 | 124.34ms | 95.98ms | 5.24ms | 28.36ms | 22.8% | 119.10ms | 95.8% |
| blazediff/3 (identical) | 112.10ms | 98.15ms | 5.56ms | 13.95ms | 12.4% | 106.54ms | 95.0% |
| blazediff/4 | 283.99ms | 228.75ms | 13.88ms | 55.25ms | 19.5% | 270.12ms | 95.1% |
| blazediff/4 (identical) | 261.89ms | 224.39ms | 12.56ms | 37.50ms | 14.3% | 249.33ms | 95.2% |
| pixelmatch/1 | 29.94ms | 19.88ms | 1.66ms | 10.06ms | 33.6% | 28.27ms | 94.5% |
| pixelmatch/1 (identical) | 29.22ms | 19.82ms | 1.61ms | 9.40ms | 32.2% | 27.61ms | 94.5% |
| pixelmatch/2 | 15.33ms | 9.82ms | 0.79ms | 5.51ms | 35.9% | 14.55ms | 94.9% |
| pixelmatch/2 (identical) | 14.75ms | 9.93ms | 0.84ms | 4.82ms | 32.7% | 13.91ms | 94.3% |
| pixelmatch/3 | 28.52ms | 23.06ms | 1.67ms | 5.46ms | 19.1% | 26.85ms | 94.1% |
| pixelmatch/3 (identical) | 29.82ms | 19.89ms | 1.65ms | 9.93ms | 33.3% | 28.17ms | 94.5% |
| pixelmatch/4 | 20.90ms | 11.85ms | 0.80ms | 9.05ms | 43.3% | 20.10ms | 96.2% |
| pixelmatch/4 (identical) | 20.70ms | 12.27ms | 0.83ms | 8.43ms | 40.7% | 19.88ms | 96.0% |
| pixelmatch/5 | 14.71ms | 10.79ms | 0.79ms | 3.92ms | 26.7% | 13.91ms | 94.6% |
| pixelmatch/5 (identical) | 15.50ms | 9.82ms | 0.77ms | 5.68ms | 36.6% | 14.73ms | 95.1% |
| pixelmatch/6 | 14.86ms | 9.82ms | 0.79ms | 5.04ms | 33.9% | 14.07ms | 94.7% |
| pixelmatch/6 (identical) | 14.72ms | 9.79ms | 0.77ms | 4.94ms | 33.5% | 13.95ms | 94.8% |
| pixelmatch/7 | 28.88ms | 16.56ms | 1.16ms | 12.32ms | 42.7% | 27.73ms | 96.0% |
| pixelmatch/7 (identical) | 28.21ms | 16.78ms | 1.12ms | 11.42ms | 40.5% | 27.09ms | 96.0% |
Hitchhikers SSIM SSIM (@blazediff/ssim using hitchhikers-ssim vs ssim.js using weber algorithm) (image IO excluded)
25 iterations (3 warmup)
~75.1% performance improvement on average.
The percentage above is the JavaScript port. @blazediff/ssim-native is the
Rust one, wrapping the same algorithm β roughly 4x faster again than the JS
port, from the same decoded buffers.
| Benchmark | ssim.js | BlazeDiff | BlazeDiff (ssim-native) | BlazeDiff Saved | BlazeDiff % | BlazeDiff (ssim-native) Saved | BlazeDiff (ssim-native) % |
|---|---|---|---|---|---|---|---|
| blazediff/1 | 63.19ms | 12.58ms | 2.85ms | 50.61ms | 80.1% | 60.34ms | 95.5% |
| blazediff/1 (identical) | 63.14ms | 12.59ms | 2.88ms | 50.55ms | 80.1% | 60.25ms | 95.4% |
| blazediff/2 | 34.91ms | 10.22ms | 2.26ms | 24.68ms | 70.7% | 32.64ms | 93.5% |
| blazediff/2 (identical) | 34.65ms | 10.02ms | 2.28ms | 24.64ms | 71.1% | 32.37ms | 93.4% |
| blazediff/3 | 134.85ms | 46.12ms | 10.96ms | 88.72ms | 65.8% | 123.89ms | 91.9% |
| blazediff/3 (identical) | 141.68ms | 47.06ms | 10.97ms | 94.62ms | 66.8% | 130.70ms | 92.3% |
| blazediff/4 | 389.62ms | 108.01ms | 25.77ms | 281.60ms | 72.3% | 363.85ms | 93.4% |
| blazediff/4 (identical) | 351.85ms | 108.45ms | 25.43ms | 243.40ms | 69.2% | 326.42ms | 92.8% |
| pixelmatch/1 | 19.03ms | 3.97ms | 0.84ms | 15.06ms | 79.1% | 18.19ms | 95.6% |
| pixelmatch/1 (identical) | 18.49ms | 3.93ms | 0.87ms | 14.57ms | 78.8% | 17.63ms | 95.3% |
| pixelmatch/2 | 9.33ms | 1.88ms | 0.44ms | 7.45ms | 79.9% | 8.90ms | 95.3% |
| pixelmatch/2 (identical) | 9.34ms | 1.86ms | 0.40ms | 7.48ms | 80.1% | 8.94ms | 95.7% |
| pixelmatch/3 | 19.14ms | 3.77ms | 0.84ms | 15.37ms | 80.3% | 18.30ms | 95.6% |
| pixelmatch/3 (identical) | 18.42ms | 3.82ms | 0.85ms | 14.60ms | 79.3% | 17.58ms | 95.4% |
| pixelmatch/4 | 18.59ms | 5.19ms | 1.20ms | 13.40ms | 72.1% | 17.39ms | 93.6% |
| pixelmatch/4 (identical) | 19.22ms | 5.21ms | 1.19ms | 14.01ms | 72.9% | 18.03ms | 93.8% |
| pixelmatch/5 | 9.51ms | 1.89ms | 0.40ms | 7.62ms | 80.1% | 9.10ms | 95.7% |
| pixelmatch/5 (identical) | 9.35ms | 2.07ms | 0.44ms | 7.28ms | 77.9% | 8.90ms | 95.2% |
| pixelmatch/6 | 9.28ms | 2.05ms | 0.41ms | 7.22ms | 77.9% | 8.86ms | 95.6% |
| pixelmatch/6 (identical) | 9.44ms | 1.94ms | 0.40ms | 7.49ms | 79.4% | 9.04ms | 95.8% |
| pixelmatch/7 | 24.82ms | 7.38ms | 1.72ms | 17.44ms | 70.3% | 23.10ms | 93.1% |
| pixelmatch/7 (identical) | 23.29ms | 7.25ms | 1.65ms | 16.04ms | 68.9% | 21.64ms | 92.9% |
Benchmarks run on MacBook Pro M1 Max, Node.js 22