M1とRosetta 2が速い理由の考察という名目の妄想

公開:2020/11/21
更新:2020/11/21
4 min読了の目安(約3700字IDEAアイデア記事

Apple SiliconのM1が速いと話題だ。単に速いというだけでなくRosetta 2を用いてx86_64バイナリをARMに変換して実行した時にIntel CPUで直接実行した時より速くなる場合があるというのだから驚きだ。その要因を考察するにつれ一つの仮説に思い至ったのでここに記しておく。

その要因とはRISCとCISCの違いだ。殴り書きなので詳細は省くが、CISCのほうがやってることが複雑で単純な実行速度という意味ではRISCに敵わない。特にRISCの固定長命令という特徴がカギを握る。

CISCの代表がIntelのx86である。しかし2000年ごろにはCISCはもう駄目だ的なことが声高に叫ばれていたが、気が付けばx86はそのまま栄華を極め2020年にまで至ってしまった。そこまで持ちこたえた理由の1つがRISCとCISCの境目がなくなる Pentium Proの逆襲に書かれているのだが、要はCISCをRISCに逐次変換=デコードして中身はRISCで実行しているということになる。もちろんそれだけが理由ではないだろうけれど、CISCに分類されるx86アーキテクチャが長く性能を発揮できた1つの要因になったことは想像に難くない。

ただしこの記事にはこのアーキテクチャの発案者の言葉として次のようにも書かれていた。

純粋なRISCに比べてエンジニアリング作業の増大やダイサイズの大型化によるコスト増の問題はあるが、これは量産効果で吸収できると考えた

「エンジニアリング作業の増大やダイサイズの大型化によるコスト増」が現代のCPUに求められる高クロック化、メニーコア化、製造プロセスの微細化…そういう異なる方面からの要求とコンフリクトし遂に耐えられなくなったのではなかろうか。

CISC的な可変長命令のデコーダはとにかく大きくて遅くなる。最近筆者はZ80というCPUのエミュレーターを遊びで書いたのだが、その際にチューニングのためのプロファイルを取りながらデコーダー部分を速度を求めて何度か書き直し最終的に今の形に落ち着いた。これ以上速くはできないというところまで来たのだが、それでも実行時間の少なくない部分がデコーダーで占められてしまう。しかもコード量としてもかなり大きい。ハードウェアとそのソフトウェアエミュレーションではそのコスト構造は異なるが、デコーダーの実装は厄介だと実感した経験だった。

現代的なCPUは前述の記事にある通りスーパースカラーと呼ばれる1コアでの複数命令の同時実行技術と、それを補助するアウト・オブ・オーダー実行と呼ばれる同時実行をしやすい形に実行順序を入れ替える、2つの技術が実行速度のための重要な要素の1つとなっている。同時にいくつの命令を実行できるかをIPC=サイクルあたりの命令実行数と言ったりする。

可変長命令のデコードは特にコレと相性が悪い。現代のCPUはメモリを読むときに1バイトずつではなく、8バイトとか16バイトというブロック単位で読み込む。可変長命令ではブロック内にいくつの命令が入っているかはデコードしてみないとわからない。一方でRISCの固定長命令ではいくつ入ってるかのかわかる。IPCを上げるために前述のスーパースカラーを前提とした構成を計画しやすい。

さてここでRosetta 2に考えを馳せよう。Rosetta 2は言ってしまえばCISCからRISCへのコンパイラだ。一方でx86でやっているのはCISCからRISCへのインタープリター。この2語を見るだけでちょっと経験のあるプログラマならRosetta 2のほうが速くなりうることを理解できるだろう。もちろんコンパイルの時間は余計にかかるだろう。だがコンパイル結果はキャッシュすれば良い。

しかもM1はARM ISAを利用しているもののAppleの自社設計だ。どのような命令をスーパースカラーで組み合わせられるのか、アウト・オブ・オーダー実行はどうなっているのか全部制御下にある。IPCを上げるためにRosetta 2のを通す時点でそれらを考慮した最適化もできるだろう。

特にC/C++コンパイラが出力するような「よくあるx86のコード」には、より最適なM1コードに変換するイディオムやテンプレートを用意するくらいのことはしていても不思議はない一方で、アセンブラを用いて人手で書かれたx86コード(x86でのIPCが高いがレアなコード)はそこから外れるのでリテラルに変換せざるをえず、M1ではIPCを上げられない可能性がでてくる。

またそういうイディオム・テンプレートはボトルネックとなるx86コードが発見されるたびにRosetta 2に追加される可能性がないだろうか。

Rosetta 2が吐き出すコードに一般的なARMでは使われないイディオムのコードが含まれていないか、気になるところだ。それが見つかればM1の高速動作のある種の理由を見せてくれるだろう。

まとめ

M1とRosetta 2が速い理由は以下ではないかと考えた。

  • RISCはIPCを増やしやすい=速くさせやすい
  • CISC to RISCのデコーダはIPCが増えるのを妨げる方向に機能する
  • デコードをRosetta 2で事前に済ませてしまえばIPCを上げられる

またここから発展すると以下が妄想される。

  • Rosetta 2は将来的にさらに速いコードを生成する可能性がある

とはいえM1のIPCが実際どんなもんかわからない(いまそれへの言及記事を探している)のでこの記事としての信用度はゼロですw

追記: M1のIPCの大きさ

この記事Apple’s New M1 SoC Looks Great, Is Not Faster Than 98 Percent of PC Laptopsに、M1のIPCの大きさをうかがわせる情報が書いてあった。

  • L1命令キャッシュ192KB (IntelもAMDも32KBらしい)
  • デコーダー8機 (Zen3は4つ)
  • Reorder-Bufferが最大630命令分(アウト・オブ・オーダー実行に使う)
    • Intelは352命令分
    • AMDは256命令分
    • M1以外のARMコアは最大224命令分

なんだこのスケール感の違いw

参考資料