テテのつぶやき
[ページ: 1 2 3 4 5 6 7 ... ]
テテの不定期日記です。
Firefox 145.0.2独自ビルドTEST2などで独自のLLVMパス(TeInliner)を適用した結果、パフォーマンス(Speedometer 3.1のスコアの中央値)を維持させつつ、xul.dllのファイルサイズを約1.4MiB削減できましたようです (たぶん)。
clang-clとlld-linkに独自のパスプラグイン(TeInlinerなど)を使わせたかったので、LLVM 21.1.6を改造してLLVM 22(開発中)のソースコードの一部を取り込みました。
なお、LLVM 21.1.6 (clang-cl)でWindows x64ターゲットのFirefoxをビルドすると、libopusというライブラリ内で不正なコードが生成され、動画再生時にRDDプロセスがクラッシュする不具合が確認されました。可変長引数を持つ関数がインライン展開される際に、不正なコードが生成される場合があるようです。恐らくLLVM 21.1.6のバグではないかと思います。他の場所でも同様の不具合が発生する可能性は捨てきれないので、可変長引数を持つ関数にnoinline属性を付けるパス(TeBugFix)も適用しました。
TeInlinerパスは、呼び出し元が1箇所だけの関数を検索し、特定の条件を満たした場合にその関数をインライン展開します。そうすることで、TeInlinerパスが実行された後の処理によって呼び出し先の関数が削除され、バイナリサイズが削減されることを狙いました。インターネット上で見つけた、メタ社の人たちが書いた資料を参考にしましたが、ソースコードは公開されていないようなので高度な処理は実装できませんでした。
各種FirefoxビルドのSpeedometer 3.1のスコアとxul.dllのファイルサイズを比較しました。グラフの黒くて太い横棒は中央値で、緑の三角形は平均値。外れ値が多かったので、平均値よりも中央値の方が正確な比較ができるかもしれません。またPGOを適用するたびに、xul.dllのファイルサイズは数百KiB程度変動することがあります。ベンチマークに使用したPCは、Windows 11 Pro 25H2、Ryzen 5 5600、GeForce GTX 750 Ti。
| Firefox 145.0.2 (LLVM 19) | Tete009 145.0.2 (LLVM 19) | Tete009 145.0.2 TEST0 (LLVM 21) | Tete009 145.0.2 TEST1 (LLVM 21) | Tete009 145.0.2 TEST2 (LLVM 21) | |
|---|---|---|---|---|---|
| スコア (中央値) | 19.0 | 19.5 | 19.8 | 19.8 | 19.9 |
| スコア (平均値) | 19.2 | 19.75 | 20.16 | 19.99 | 20.04 |
| ファイルサイズ (MiB) | 155.99 | 155.46 | 155.60 | 154.18 | 154.24 |

関数の並び替え方法を変えたFirefox 144.0独自ビルドを複数作り、幾つかのベンチマークテストを実行して比較しました。私のブラウザの使い方にあったワークロード(一般的なニュースサイトとかの閲覧)を考慮した結果、独自ビルド144.0 TEST2を採用してみることにしました。すなわち、現行のFirefoxが採用している方法(Temporal PGOとorderfileによる並び替え)ではなく、現行のChromiumが採用している方法(PGOとコールグラフによる並び替え)です。独自ビルド144.0とTEST2のどちらを採用するか迷っています。もう少し考えてみます。追記 (16:40頃): TEST2の方法を採用してみることにしました。独自ビルド144.0.2に適用します。
以下に各種ベンチマークテストの結果を載せます。今のところ理由は不明ですが、いつの間にか公式FirefoxのSpeedometer 3.1のスコアが伸びていて、私の独自ビルドを少し上回っていました。MotionMark等のベンチマークでは、私のビルドの方がスコアが良かったですが。
グラフの黒くて太い横棒は中央値で、緑の三角形は平均値。ベンチマークに使用したPCは、Windows 11 Pro 25H2、Ryzen 5 5600、GeForce GTX 750 Ti。
| Tete009 144.0 | Tete009 144.0 TEST1 | Tete009 144.0 TEST2 | Tete009 144.0 TEST3 | Firefox 144.0 | |
|---|---|---|---|---|---|
| スコア (中央値) | 21.8 | 21.8 | 21.85 | 21.7 | 21.95 |

私の場合、MotionMarkで重視したい項目はHTMLやCSS関連なので、Multiply、Leaves、Designあたりでしょうか。SVGやCanvas関連の項目は優先度が下がります。MotionMark 1.3.1の各テスト項目についての説明などは、About MotionMark 1.3.1に載っています。MotionMarkはスコアのブレが大きめなので、サンプル数を増やしたいところですが、実行に時間がかかるのが難点。
| Tete009 144.0 | Tete009 144.0 TEST1 | Tete009 144.0 TEST2 | Tete009 144.0 TEST3 | Firefox 144.0 | |
|---|---|---|---|---|---|
| Total | 1445.05 | 1435.14 | 1432.08 | 1435.74 | 1355.87 |
| Multiply | 1490.36 | 1486.38 | 1512.13 | 1487.98 | 1381.14 |
| Canvas Arcs | 1194.99 | 1196.88 | 1200.01 | 1193.44 | 1189.86 |
| Leaves | 1330.3 | 1335.0 | 1332.26 | 1322.44 | 1254.22 |
| Paths | 3683.35 | 3717.61 | 3673.65 | 3685.02 | 3654.28 |
| Canvas Lines | 18331.34 | 17874.38 | 18249.54 | 18108.6 | 14322.0 |
| Images | 316.33 | 317.33 | 307.64 | 309.36 | 300.83 |
| Design | 263.88 | 260.64 | 259.48 | 262.52 | 253.23 |
| Suits | 1394.52 | 1399.58 | 1397.06 | 1396.08 | 1348.89 |
| Tete009 144.0 | Tete009 144.0 TEST1 | Tete009 144.0 TEST2 | Tete009 144.0 TEST3 | Firefox 144.0 | |
|---|---|---|---|---|---|
| Amazon local | 179.0 | 180.0 | 179.0 | 178.5 | 186.5 |
| Reddit local | 222.0 | 224.0 | 221.0 | 224.0 | 227.0 |
プロセスを起動する時間の短さに関しては、Temporal PGOの効果が明確に出ているようです。スクリプトのループで繰り返し起動しているので、コールドスタートではないと思います。
| Tete009 144.0 | Tete009 144.0 TEST1 | Tete009 144.0 TEST2 | Tete009 144.0 TEST3 | Firefox 144.0 |
|---|---|---|---|---|
| 4797.0 | 4812.0 | 4828.0 | 4812.0 | 4781.5 |
MinGWのビルドエラーを解消するBug 1961213で行われた変更により、Windows版のFirefox 139以降において、関数の並び替えの方法が思いがけず変わってしまったように思うのですが、これはいわゆるリグレッションではないでしょうか?
具体的には、コールグラフを使用したデフォルトの並び替え方法(-call-graph-profile-sort)から、Temporal PGO (-mllvm -pgo-temporal-instrumentation)とorderfile.txtを使用した旧来の並び替え方法に戻ってしまったように思うのですが。私がなにか間違っているんでしょうか。
pgo_cg_sort関数のコメントには、「--call-graph-profile-sort is the default behavior for lld, and it proves to be more efficient than pgo-based orderfile.」と書かれており、Temporal PGOとorderfile.txtによる関数並び替えよりも、コールグラフによるデフォルトの並び替えの方が効率的だと書かれています。しかし実際には、clang-clとlld-linkを使うWindowsターゲットの場合にpgo_cg_sortはFalseを返すため、pgo_cg_sortのコメントと振る舞いが矛盾しているように見えます。
追記 (2025年10月25日 18:10): Bug 1961213の修正前と修正後のinstrumented-win64のビルドログを比較したところ、やはり修正後のコンパイラスイッチに-pgo-temporal-instrumentationが付加されていました。
独自ビルドに先日追加したconvolve_horizontally_avx2という関数について。私が普段やっている方法で集めたプロファイルデータを使ってPGO (Profile-Guided Optimization)をこの関数に適用すると、出力画像の画素数が中程度の場合に、処理速度が大幅に悪化することが分かりました。PGOを適用しなければ高速なのですが。
原因を調べたところ、PGOによって関数内の基本ブロック(分岐命令やジャンプ命令等で区切られたコードの領域)が並び替えられたことが一因ではないかという気がしました。理由は分かりませんが、AVX2命令を使った最も重視してほしいループのブロックが関数の後ろの方へ追いやられ、ループを抜けた後の余りの処理を行うブロックが関数の先頭付近に配置されてしまうようでした。その重要なループのブロックは、他のブロックよりも実行回数が多いくらいなのですが。おかしいだろ!(つっこみ)。命令の数が他のブロックに比べて少なめだったから軽視されたんでしょうか。よくわからない……。
convolve_horizontally_avx2関数をPGOから除外することも検討しましたが、関数内のアルゴリズムを変えてみたところ、PGOを適用しても問題が起きなくなったため、今回の独自ビルドに反映させました。
PGO適用後に遅くなったコードでは、AVX2命令を多用したループを抜けた後の剰余処理を、SSSE3系の命令や既存のMozillaの関数などで行っていました。今回のビルドでは、剰余処理もできるだけAVX2命令で行うようにしました (CPUがIntelまたはAMD Zen以降に限ります)。余りの数をインデックスにし、VPMASKMOVD用のマスクデータが入っているテーブルを参照するアイディアはAIが教えてくれました。頭いいですね、天才ですね、と褒めておきました。AIもなんか嬉しそうでした。
ただ困ったことにAMDのCPUの中には、VPMASKMOVD等をIntelのマニュアル通りに実装していないものがある恐れが。Intelのマニュアルには、VPMASKMOVDはマスクビットが0の位置に該当するメモリ位置を参照してもフォルトは発生させないと明記されています。ところがAMDのマニュアルには、そのようにして参照した場合にフォルトが発生するかどうかは実装依存だと書かれているのです。
おぃ。これってエラッタに相当するような、ひどい欠陥だと思うのですが。Intelの仕様書通りに作ったプログラムが、AMDのCPU上でクラッシュするとかシャレになりません。せめてどの世代のCPUがその問題を抱えているのか、マニュアルに書いてほしいです。
VPMASKMOVDの振る舞いについて更に調べたところ、Zen〜Zen3世代のCPUは、Intelのマニュアル通りにVPMASKMOVDを実装していることを示した論文を見つけました。手元のRyzen 5 5600 (Zen3世代)もそのように振る舞いました。少なくともZen以降のAMDのCPUは、VPMASKMOVD等をIntelのCPUと同じように振る舞うように実装していると考えてよさそう。たぶん。
話が脇に逸れました。PGO適用時、convolve_horizontally_avx2関数が妙な最適化を施されてしまう問題は、下記の改良型自作ベンチマークテストを実行して気が付きました。改良前は、普段のネットサーフィン(死語)でお目にかかれないような、バカでかい画素数の画像をリサイズするベンチマークテストでした。現実世界をまるで反映していません。反省し、DuckDuckGoの画像検索結果のサムネイルのような、中規模の画素数の画像を想定した設定値をデフォルトにしました。そして独断と偏見で選んだ各種ブラウザで、ベンチマークテストをやり直してみました。
| ブラウザ | 中央値 (ms) |
|---|---|
| Tete009 143.0.1 (AVX2パス) | 0.36 |
| Firefox 143.0.1 | 0.54 |
| Zen 1.15.5b | 0.54 |
| Floorp 11.30.0 | 0.58 |
| Mercury 129.0.2 | 0.60 |
| Waterfox 6.6.3 | 0.62 |
| Chrome 140.0.7339.186 | 0.94 |
| Edge 140.0.3485.81 | 1.10 |
私は、Firefox独自ビルドにPGO (Profile-Guided Optimization)を適用するためのプロファイルデータを集める際、ベンチマークテストの実行に偏るのではなく、実際のサイトを多めに閲覧するなどして、なるべく普段使いの振る舞いをシミュレートするように心がけています。
そのようにして集めたプロファイルデータの中身を見てみると、skia::convolve_horizontallyという関数が頻繁に呼び出されていることが分かります。興味深いことに、この関数はSpeedometer 3.1ではほとんど呼び出されていないようです。ベンチマークテストが現実世界のウェブを模倣しきれていない一例と言えるかもしれません。
このconvolve_horizontallyは、CPUを用いた画像リサイズ処理の一端を担っている関数です。公式Firefox x64版の場合、内部でconvolve_horizontally_sse2という関数を呼び出しています。私の独自ビルドでは以前から、convolve_horizontally_ssse3という独自関数を追加していて、SSSE3に対応したCPU上ではSSE2版比で2.1倍ほど高速に処理を行っています (たぶん)。
今回はこの関数を更に高速化しようと思い、AVX2を用いたconvolve_horizontally_avx2関数を新たに実装して追加してみました。AVX2に対応したCPU上ではこの関数が実行されます。しかし、既にSSSE3版でかなり最適化していたせいか(シャッフルや積和演算命令を使用)、パフォーマンスはSSE2版比で約2.6倍、SSSE3版比で約1.3倍に留まりました。残念。
で、独自ビルドで行ったこれらの最適化の効果を実感できるようなベンチマークテストを作りました。window.createImageBitmap()で画像のリサイズを行った時の時間を計測します。「Start Benchmark」ボタンを押して少々待つと、全ての処理にかかった時間と、各リサイズ処理にかかった時間の中央値が表示されます:
下記がそのベンチマークテストの結果です。いくつかのブラウザの中央値を比較しました。値が小さいほど高速です。テストを行った環境は、Windows 11 24H2、Ryzen 5 5600。テストの設定はデフォルト値を使用。
| ブラウザ | 中央値 (ms) |
|---|---|
| Tete009 142.0 TEST3 (AVX2パス) | 20.00 |
| Tete009 142.0 TEST2 (SSSE3パス) | 22.00 |
| Firefox 142.0公式 (SSE2パス) | 30.00 |
| Chrome 139.0.7258.139 | 32.20 |
| Edge 139.0.3405.119 | 33.10 |