ディスカッション (11件)
Firefoxのクラッシュデータを詳細に分析した結果、全体の約1割が「ビットフリップ(メモリ内の0と1が予期せず入れ替わる現象)」によって引き起こされていることが明らかになりました。これはソフトウェア側のバグではなく、ハードウェアの劣化や宇宙線の影響といった物理的な要因が、ブラウザの安定性に無視できない影響を与えていることを示しています。エンジニアにとっても、デバッグでは追いきれないハードウェア由来の課題として注目を集めています。
5回にわたるスレッドで「クラッシュの原因がビットフリップであると100%確信した」と言っているけれど、「メモリを解析している」という以外に、どうやってビットフリップを検出しているのかについては一言も触れられていないのが気になる。
非常に興味深い。Goのツールチェーンには(デフォルトではオフだが)テレメトリシステムがあるんだ。Go 1.23では、runtime.SetCrashOutput関数を追加して、実行中のgoroutineでクラッシュした際のスタックトレースを含むレポートを収集できるようにした。これを1年以上前にLSPサーバーであるgoplsで有効にして以来、数百ものバグを発見できている。
テレメトリを有効にしているユーザーは全体の1000分の1程度だが、それでもクラッシュに関する貴重な情報源になっている。ほとんどの場合、問題を再現するテストケースを簡単に作成できるので、1時間以内にバグを修正できる。この方法で何十個ものバグを直してきたよ。原因がはっきりしない場合は、if文やアサーションを追加してクラッシュを「絞り込み」、次のリリースでスタックトレースから実行状態に関する情報を少しずつ得られるようにしている。
とはいえ、どうしても説明がつかないレポートも残っていた。壊れたスタックポインタや、gレジスタ(現在のgoroutineを指すスレッドローカルなポインタ)の破損、nilチェックを通ったはずのポインタをデリファレンスして発生するパニックなどだ。これらはすべてメモリ破損を示唆している。
理論上はunsafeを乱用したりデータレースがあれば何でもあり得るけれど、実行ファイル内のunsafeの使用箇所はすべて監査したし、それらが安全であることは確信している。データレースの不在を証明するのは難しいが、レースなら変数の書き換えに何らかの局所性が見られるはずなのに、今回はそうではなかった。
メモリ関連ではない命令(例えばMOV ZR, R1など)でクラッシュが発生することもあり、これは誤実行、つまりCPUの故障(あるいはテレメトリの記録処理のバグかもしれないが)を疑わせるものだった。
プログラマーとして、自分のコードのミスをコンパイラやランタイムのせいにしすぎて痛い目を見た経験が何度もあるから、今回のように基盤を疑うのにはかなりの確信が必要だった。でも最近軽く計算してみたところ(https://github.com/golang/go/issues/71425#issuecomment-39685... を参照)、説明のつかないレポートが週に10件程度という驚くべき数は、ハードウェアの故障の範疇に十分収まるという結論に至った。特に、私たちのユーザーの大多数はパリティメモリを搭載していないラップトップを使っているからね。
とはいえ、確定的な証拠が欲しいところだ。Firefoxチームがクラッシュレポートソフトでどんなメモリテストを実行しているのか気になる。
Googleの最初の従業員であるCraig Silversteinが「最大の後悔は何か?」と聞かれた際、「ECCメモリを強く推奨しなかったこと」と答えたというのは、HNでよく噂されている話だね。
HNで以前にも話したことがあるけれど、ArenaNetの共同創業者Mike O'Brien(battle.netの作成者)は2004年頃、『Guild Wars』のバグトリアージプロセスの一環としてビットフリップを検出するシステムを書いたんだ。当時、ゲームクライアントから意味不明なバグ報告が頻繁に来ていたからね。
『Guild Wars』は毎フレーム(約60FPS)ランダムなメモリを確保して重い計算を行い、結果を既知の値のテーブルと比較していた。なんと1000台に1台のコンピューターがこのテストでエラーを出したんだ!
テスト結果はレジストリに保存して、自動化されたバグ報告に含めるようにしたよ。
そこで判明した主な原因は以下の通りだ:
- CPUのオーバークロック
- メモリのウェイトステート設定の不備
- 電源ユニットの電力不足
- 冷却ファンの性能不足や吸気口の埃によるオーバーヒート
こうした問題が起きたのは、『Guild Wars』が屋外の地形をレンダリングしていて、当時の他の3Dゲームに比べてポリゴン数が多かったからだ(当時のゲームはBSPなどを使って効率的にクリッピングしていたが、屋外だとそれがあまりうまく機能しなかった)。その結果、コンピューターが熱を持ってしまったんだ。
数年後、Dellのコンピューターは最も安い部品を調達していたせいでアナログコンポーネントの不具合が多かったと知った。それも原因の一つだったんだろう。
さらに数年後、RowHammer攻撃のことを知った。おそらくこれも原因の一つだろう。私たちが使っていた計算は、メモリー行をかなり頻繁に叩くように設計されていたからね。
時々、コンピューターが動いていること自体が不思議でならないよ。
ちなみに、この件に関する私の貢献は、テストに失敗した時にブラウザを立ち上げて、ファンの清掃を促すWebページを表示させるコードを書いたことだ。
つまりFirefoxユーザーが見るクラッシュの最大10%はソフトウェアバグではなく、ハードウェアの欠陥によるものだ!
大胆な主張だな。個人的な直感としてはこれは誤りだと思う。ThoriumのようなChromiumベースのブラウザを使っていても、それと同じくらいの頻度でクラッシュしているようには思えないし。
DDR4とDDR5でのビットフリップの違いが見てみたい。理解している限りだと、DDR5は一定レベルのECCが必須になっているはずだけどね [1]。
これだけ高い確率だと、mallocやページテーブル管理における稀なレース条件のような、システムソフトウェア側の問題も混ざっているんじゃないかと疑ってしまう。
メモリが1GBを超えたあたりでECCが標準になるべきだったんだよ。
ECCメモリを入手するのが困難で高価な一方で、役に立たないLEDがついたメモリが安く売られているのは本当に腹立たしい。
反感を買うかもしれないけど、これはデタラメだと思う。ビットフリップは頻繁に起こるし(ECCは改善にはなるけど解決策ではない)、確かに起きる。だけど、そこまで頻繁ではないはずだ。テレメトリを有効にしているユーザーが単に不安定なハードウェアを使っているのか、あるいは(メッセージが示唆するように)実装がビットフリップではなく、無数の問題を検出しているだけなんじゃないか。特定の構造体の処理・解析・保存が10%の確率で失敗するなら、画像編集からCADまで、あらゆるシナリオで深刻な影響が出るはず。それに、不安定なハードウェアでのビットフリップは保護リングを選り好みしない。OSのデバイス読み書きや、メモリに触れるすべてのものに影響を及ぼすはずだ。そう、私も不具合のあるRAMシステムをたくさん見てきたけど(WinMEのクラッシュの多くは、W98では問題なく動くはずの欠陥RAMが原因だった)、特定のブラウザやアプリだけを狙い撃ちするなんてことはないよ。
これは15年前のPCを使っている3人のユーザーのせいで統計が大きく歪んでしまうような類の指標に見える。
ちゃんと正規化して、外れ値を一貫したやり方で排除しないといけない。