1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • x86-32エミュレータは、別のプロセッサでx86-32コードを実行するためにバイナリ変換でネイティブコードを生成し、インタプリタ方式より大きな性能向上を提供していた
  • このエミュレータはx86-32をバイトコードのように見なし、エミュレータをJITコンパイラのように動作させる構造として理解できる
  • あるプログラムはスタック上に約64KBのメモリを割り当てて初期化する必要があり、一般的な方法はスタックプローブ後にスタックポインタを減らし、小さなループでメモリを初期化するものだった
  • そのコードのコンパイラはループの代わりに65,536個の個別のバイト書き込み命令を生成し、各命令が4バイトだったため、64KBのデータを初期化するのに256KBのコードが必要だった
  • エミュレータチームはこの関数を検出する特殊なコードをトランスレータに追加し、等価な短いループに置き換えるようにした

背景: x86-32エミュレータとバイナリ変換

  • Windowsには、x86-32以外のプロセッサで動作するシステム向けに、x86-32プロセッサエミュレータが含まれていたことがあった
  • この事例がどのプロセッサに適用されたかは、原文では特定されていない
  • このエミュレータはバイナリ変換を使って、元のx86-32コードと等価な動作を行うネイティブコードを生成していた
  • この方式はインタプリタベースのエミュレーションより大幅な性能向上をもたらした
  • x86-32をバイトコードとして見なし、エミュレータをJITコンパイラとして捉えるような理解が可能である

問題のコード: 64KBのスタックメモリ初期化

  • あるプログラムはスタック上に約64KBのメモリを割り当て、これを初期化する必要があった
  • 標準的な方法は、まずスタックプローブを実行して64KBのメモリを使用できるか確認する手順だった
  • その後、スタックポインタから65,536を引き、小さくタイトなループでメモリを初期化するのが一般的だった

コンパイラの過剰なループアンローリング

  • そのコードをコンパイルしたコンパイラは、各バイトを初期化するループを生成しなかった
  • 代わりに、ループを65,536個の個別の**「メモリへのバイト書き込み」命令**として展開して生成した
  • 各命令の長さは4バイトだった
  • 結果として、64KBのデータを初期化するために256KBのコードが必要になった

エミュレータチームの対応

  • エミュレータチームはこの関数を検出する特殊なコードをトランスレータに追加した
  • 検出された関数は、等価な動作を行う短いループに置き換えられた
  • この処理は、元のプログラムコードをそのまま翻訳する代わりに、エミュレーション中に非効率なコードパターンをより簡潔な形に変える方式だった

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rsの意見
  • Raymond Chenにループ展開(loop unrolling) を説明していたコメントがかなり気に入った

    • あのコメントはブログ筆者だけでなく、一般読者のために書かれたものだったのかもしれない
      こうした記事を読む人の中には、背景知識をすべて知っているわけではないので、さらに学べる手がかりをありがたく思う人も多い
    • そのコメントは見ていないが、たぶん削除されたようだ。それでもRaymond Chenといえば、まさに生ける伝説だ
      https://joelonsoftware.com/2004/06/…
    • 何十年も前にSlashdotで、ある人がlarry@wall.orgにPerlの話題を説明しようとしていたことを思い出した
  • これはAlphaでのことだった気がする。そのプラットフォーム向けのx86エミュレータには非常に多くの作業が投入されていたからだ