画像圧縮ベンチマーク: ブラウザで測った10のフィクスチャ

10のフィクスチャ、各5回の測定、1つのブラウザ。サイトが実際に配信するワーカー経路でのエンコーダー測定で、都合の悪い結果も含みます。

tinyimg が公開しています。測定ハーネス、フィクスチャ、結果ファイルはすべてリポジトリにあり、方法と数値を検証できます。

何を、どう測ったか?

7つのファイルから得た10ケースの圧縮時間と出力サイズを、1つのブラウザと1台のマシンで各5回測ります。12 MP の写真を JPEG・WebP・AVIF に、2 MP のスクリーンショットを PNG・WebP にエンコードします。

ハーネスは実際に配信される経路を通ります。src/lib/pool.js と src/lib/worker.js を import します。アプリが JPEG・PNG・WebP・AVIF・JXL に使うのと同じプール、同じワーカーです。MotionPhoto では src/lib/motion-photo/pipeline.js を import します。アプリがこの形式で通る経路であり、MotionPhoto も本番ではワーカープールを通りません。ベンチマーク専用の圧縮器も、ずれる可能性のある第二の実装もありません。

各ケースはウォームアップを1回、その後に測定を5回行います。ケースは逐次実行され、4ワーカーのプールを取り合いません。表の時間は5サンプルの中央値です。p95 は最近接順位で計算するため、5サンプルでは最も遅い実行になります。補間せず、5サンプルで裾を語れるふりもしません。

ブラウザはヘッドレス Chrome 155 で、実行ごとに新しい一時プロファイルを使います。拡張機能とバックグラウンド通信は無効です。localhost から配信し、ワーカープールと WASM モジュールの準備ができてから計時を始めます。クロスオリジン分離は意図的に有効にしません。有効にすると wasm-feature-detect がスレッド対応を報告し、oxipng がスレッド版ビルドに切り替わりますが、それは本番の経路ではありません。

マシンは Apple M4、10コア、16 GB メモリ、macOS arm64 です。ブラウザはハードウェアスレッドを10と報告し、アプリはプールを4ワーカーに制限します。

どのファイルを試したか?

0.5 MP の JPEG から 24 MP の写真まで7つのファイル、さらに 12 MP の MotionPhoto 1つです。

ファイルピクセル入力何を表すか
photo-12mp.jpg4000×30004.92 MB細部の多い写真
shot-2mp.png1920×10801.36 MB鋭いエッジと文字風のバーがある UI スクリーンショット
flat-1mp.png1000×1000118.1 KB平坦で低詳細のグラフィック
alpha-4mp.png2000×20008.26 MB実際のアルファチャンネルを持つ RGBA 画像
large-24mp.jpg6000×40009.66 MB24 MP の写真
tiny-0.5mp.jpg800×600219.6 KB小さい画像。呼び出しあたりのコストが見える
motion-12mp.jpg4000×3000 の静止画 + 6秒 1280×720 の動画9.98 MBリポジトリのライターで組み立てた合成 MotionPhoto

すべてのフィクスチャは ffmpeg が内蔵ソースから生成します。各ファイルの SHA-256 は bench/fixtures/manifest.json に記録され、ブラウザが読み込む前に検証されます。フィクスチャを変えれば、黙って基準が動くのではなく失敗します。

結果はどうなったか?

10ケースで比率は 1.5% から 69.3%、中央値は 42.0 ms から 1,983.2 ms でした。比率は出力 ÷ 入力で、低いほど小さい。中央値と p95 はワーカー往復を含むケース全体の実時間です。ms/MP は中央値をメガピクセル数で割った値です。

表と2つのグラフは同じ bench/published.json(実行 ID・日付・環境)を読みます。食い違う場合はファイルが優先です。

ケース別の圧縮率横棒グラフ。圧縮率は出力サイズを入力サイズで割った値で、低いほど小さい。最小は shot-2mp-webp の 1.5%、最大は motion-12mp の 69.3%。ケース別の圧縮率0255075photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp8.8%6.5%2.4%43.2%1.5%51.7%4.4%8.6%9.3%69.3%出力 ÷ 入力(%)低いほど良い
最小: shot-2mp-webp(元の 1.5%)。最大: motion-12mp(69.3%)。
メガピクセルあたりのエンコード時間横棒グラフ。エンコード時間の中央値をメガピクセルあたりで示す。ケースの順序は圧縮率のグラフと同じ。最小は large-24mp-jpg の 51.2 ms/MP、最大は flat-1mp-png の 516.2 ms/MP。メガピクセルあたりのエンコード時間0200400600photo-12mp-jpgphoto-12mp-webpphoto-12mp-avifshot-2mp-pngshot-2mp-webpflat-1mp-pngalpha-4mp-aviflarge-24mp-jpgtiny-0.5mp-webpmotion-12mp56.483.1165.3296.273.1516.2387.951.287.5111.2ms / メガピクセル(5 回の中央値)ケースの順序は圧縮率のグラフと同じ
メガピクセルあたり最速: large-24mp-jpg(51.2 ms/MP)。最も遅い: flat-1mp-png(516.2 ms/MP)。
ケース形式MP入力出力比率中央値p95ms/MP
photo-12mp-jpgJPEG → JPEG124.92 MB444.4 KB8.8%677.0733.456.4
photo-12mp-webpJPEG → WebP124.92 MB325.2 KB6.5%997.51021.783.1
photo-12mp-avifJPEG → AVIF124.92 MB121.8 KB2.4%1983.22026.8165.3
shot-2mp-pngPNG → PNG2.071.36 MB600.6 KB43.2%613.1618.1296.2
shot-2mp-webpPNG → WebP2.071.36 MB21.0 KB1.5%151.3156.173.1
flat-1mp-pngPNG → PNG1118.1 KB61.0 KB51.7%516.2562.9516.2
alpha-4mp-avifPNG → AVIF48.26 MB370.1 KB4.4%1551.71626.3387.9
large-24mp-jpgJPEG → JPEG249.66 MB846.1 KB8.6%1228.11231.051.2
tiny-0.5mp-webpJPEG → WebP0.48219.6 KB20.4 KB9.3%42.043.487.5
motion-12mpMotionPhoto129.98 MB6.92 MB69.3%1334.01377.3111.2

数値は何を示すか?

12 MP の写真で AVIF は JPEG より 3.65倍小さく 2.93倍遅く、WebP はその中間、PNG は 296.2 ms/MP の可逆な例外です。

AVIF は JPEG よりどれだけ小さいか?

JPEG の約3分の1です。12 MP の写真で 2.4% 対 8.8%、つまり 121.8 KB 対 444.4 KB です。WebP は 325.2 KB でした。AVIF は JPEG より 3.65倍小さく、WebP より 2.67倍小さい。同時に最も遅く、中央値 1,983.2 ms は JPEG の 2.93倍、WebP の 1.99倍です。メガピクセルあたりでは 165.3 ms で、JPEG は 56.4、WebP は 83.1 です。

一行でいえば、JPEG のバイト数の4分の1強でエンコード時間が3倍です。数百枚のバッチではこの時間は現実的で、代表画像1枚なら問題にならないこともあります。

WebP は JPEG よりどれだけ小さく、遅いか?

26.8% 小さく、1.47倍遅い。同じ 12 MP のソースで 325.2 KB 対 444.4 KB、997.5 ms 対 677.0 ms です。AVIF と比べると 2.67倍大きく、1.99倍速い。どちらの端でも最良ではなく、両方向の中間です。

JPEG はどれくらい速いか?

12 MP の写真を 677.0 ms(56.4 ms/MP)、24 MP の写真を 1,228.1 ms(51.2 ms/MP)で、ここでは3つのコーデックで最速です。メガピクセルあたりのコストはほぼ一定で、速いブロックコーデックらしい姿です。出力は現代の2形式より大きいですが、時間で倒すべき相手は JPEG であり、このマシンでは誰も倒せませんでした。

PNG はなぜメガピクセルあたり最も遅いのか?

2.07 MP のスクリーンショットで 296.2 ms/MP、WebP の 73.1 ms/MP の 4.05倍です。可逆最適化はすべてのピクセルについてフィルターと DEFLATE 戦略を探索しなければならないからです。同じスクリーンショットは PNG で 613.1 ms、WebP で 151.3 ms でした。平坦な 1 MP の PNG はピクセルあたりさらに悪く、516.2 ms でした。最適化器の固定コストが1メガピクセルにしか分散されないためです。

PNG は可逆です。すべてのピクセルを保ち、oxipng はより小さいストリームのためにフィルターと DEFLATE 戦略を探索します。探索が目的であり、同時にコストです。非可逆コーデックは品質目標に達したら止まれます。可逆最適化器は、これ以上削れるものが無いことを示さなければならず、組み合わせの数は画像とともに増えます。

サイズ差も同じくらい率直です。同じスクリーンショットは PNG で 600.6 KB、WebP で 21.0 KB でした。これは等価な出力ではありません。片方は可逆で片方は非可逆です。しかし、これから編集する図には PNG が正しく、配信する写真には間違っている理由を説明します。

oxipng のレベルを上げると必ず PNG は小さくなるか?

いいえ。レベル3はスクリーンショット3つで 9.6〜13.6% 小さくなりましたが、8.29 MP の写真系 PNG では 8.2倍の時間でわずか4バイトしか減りませんでした。PNG のレベルは bench/png-levels.mjs で別途測りました。同じフィクスチャで、各レベルにつきウォームアップ1回と5回の実行です。3つのスクリーンショット(2.07、8.29、15.73 MP)では、レベル3はレベル1の 7.8〜8.5倍の時間がかかり、ファイルは 9.6〜13.6% 小さくなりました。8.29 MP の写真系 PNG ではレベル1よりわずか4バイト小さく(15,474,918 対 15,474,922)、時間は 8.2倍でした。RGBA 画像では 2.0% 節約し、時間は 7.1倍でした。すでに写真である画像では、追加の探索はほとんど何も買いません。

そのため配信ワーカーは現在、既定でレベル2を使い、4 MP を超えるとレベル1に下げます。16 MP ではレベル2はレベル1より 1.0% 大きく、約2倍遅かったので、高いレベルが自動的に小さいファイルになるわけではありません。変更のコストは平坦で低詳細な PNG に見えます。1 MP の平坦なフィクスチャはレベル2で 62,501 B、レベル3で 48,138 B と 29.8% 大きいものの、エンコードは 437.9 ms で 2,028.3 ms より 4.6倍速い。スクリーンショットの負担は小さく、2.07 MP ではレベル2のファイルはレベル3より 7.2% 大きく、4.1倍速い。サイトは速い既定値を選び、そのバイト数を受け入れました。

MotionPhoto は動画も圧縮するか?

いいえ。再エンコードするのは静止画だけで、動画はバイト単位でコピーされるため、合成 12 MP ファイルは半分を触らないまま 30.7% 縮みました。MotionPhoto は1つのファイルです。JPEG の静止画に動画が付き、機種によっては gain map も付きます。配信経路は静止画をデコードして再エンコードし、その周りにコンテナを再構築します。デコードも再エンコードもせず、出力先としても提示しません。gain map がある場合もコピーし、MPF 内の絶対オフセットを再指定して HDR 表示が壊れないようにします。

つまりファイル全体の比率は、動画がファイルのどれだけを占めるかに支配されます。合成 12 MP フィクスチャでは、静止画は入力 10,463,084 B のうち 5,156,418 B(49.3%)、動画は 5,306,666 B(50.7%)で、gain map はありません。静止画は 1,944,569 B となり 62.3% 小さく、ファイル全体は 9.98 MB から 6.92 MB へ 30.7% 小さくなりました。静止画自身の削減は大きいですが、ファイル全体の削減は触っていない半分に制限されます。

実サンプルの検証は OnePlus 13 の MotionPhoto を使います。静止画 7,140,978 B(44.7%)、動画 7,968,544 B(49.9%)、gain map 848,042 B(5.3%)、合計 15,957,564 B。静止画のみの経路の後、静止画は 1,862,140 B(73.9% 減)、動画と gain map はバイト単位で同一、合計 10,712,466 B — 15.96 MB から 10.71 MB へ 32.9% 減。再構築したファイルは端末で MotionPhoto として認識され、再生されました。gain map の経路を実機で検証したもので、合成フィクスチャからの推測ではありません。

限界は何か?

合成フィクスチャと1台のマシンに始まる6つの留保です。

この数値はどう再現するか?

npm ci から npm run bench までの5つのコマンドです。

npm ci
npm run build
npm run bench:fixtures
npm run bench:build
npm run bench

実行は bench/results/ に JSON を書き、上の表を stdout に出力します。PNG レベルの比較は npm run bench:png-levels です。フィクスチャはブラウザが読み込む前に bench/fixtures/manifest.json と照合されます。ファイルが変われば、静かな新しい基準ではなく明確な失敗になります。

よくある質問

どの形式が最も小さく圧縮するか?

AVIF です。12 MP の写真で 121.8 KB、入力の 2.4% で、WebP は 6.5%、JPEG は 8.8% でした。

どの形式が最も速くエンコードするか?

JPEG です。12 MP の写真で 677.0 ms、WebP は 997.5 ms、AVIF は 1,983.2 ms でした。

PNG はなぜメガピクセルあたり遅いのか?

可逆だからです。oxipng はすべてのピクセルについてフィルターと DEFLATE 戦略を探索します。2.07 MP のスクリーンショットは PNG で 296.2 ms/MP、WebP で 73.1 ms/MP と 4.05倍少ない。

MotionPhoto は動画を圧縮するか?

いいえ。動画はバイト単位でコピーされ、静止画だけが再エンコードされます。合成 12 MP サンプルでは静止画が 62.3%、ファイル全体が 30.7% 縮み、9.98 MB から 6.92 MB になりました。

レベル2とレベル3は PNG の出力サイズにどう影響するか?

レベル3のほうが小さいが大幅に遅い。1 MP の平坦な PNG はレベル2で 62,501 B、レベル3で 48,138 B と 29.8% 大きいものの、エンコードは 437.9 ms で 2,028.3 ms より速い。

各ケースは何回測るか?

ウォームアップ1回と測定5回です。表はその5回の中央値を示し、p95 は最も遅いサンプルです。

この記事について

tinyimg が公開しています。ハーネス、フィクスチャ生成器、結果ファイルはリポジトリにあり、この実行の生 JSON は bench/results/ に書かれます。署名は個人ではなくプロジェクトです。ここで主張するのは権威ではなく、方法が書かれていて数値を再現できるという点です。

← tinyimgに戻る

en · zh-CN · ja · ko · es · fr · ru · ar