見出し画像

M1でやってるらしい(Apple用の)最適化

Apple M1、命令実行数とかL1キャッシュどうなってんのとかはAnandTechが詳しいと思う。

Apple M1速いね、ってことで、それはいいとして、それ以外にも色々Appleの用途に最適化している点があるらしいというツイートがあった。ちょっと読んでてマジで?となったのでここにメモしておこう。

私はというとこんなCPUレベルの話が効いてくるようなプログラムは書いたことないので、誤解もあると思う。ゆるして

BlackICEを作ったとか書いてあるからセキュリティソフト関連の人なのかな?Robert Grahamという人の…このスレッドが興味深かった。

メモリオーダリング

この方の指摘するところによれば、Armでx86のコードをエミュレーションする場合、問題となるのは命令セットそのものではなく"memory-ordering"なのだと言う。私はよく知らなかったのでバイトオーダーのことかと思ったら違った。メモリオーダリングというのは、CPUの命令実行順序の並び替えをどの程度許容するか、ということなのだそうだ。

つまり私がプログラムを書いて、それをコンパイルしてCPUが実行するとき、命令の実行順序というのは元のプログラム通りにはならない。同時に実行しても問題ない命令は同時に実行したり、こっちの命令で読み込むのに時間がかかるからその間にあっちの命令を実行しよう、とか効率の良いように並び替える。ここで、何の制限もなしに命令を並び替えると当然実行結果が変わっちゃうので、こういう場合は並び替えない、といった制限をかける。この制限がArmとx86で言うとArmの方がより「緩い(weak)」。よりホイホイ命令を並び替えちゃうので、それが問題になる場合はソフトウェアの方でなんとかしろ派。

ということは、もしx86のある命令をArmの命令に一対一で変換できたとしても、それをArmが実行するときにx86では想定していない並び替えが起こる可能性がある。これじゃ実行結果が変わってしまうので、命令を変換するときにはそこを考慮して変換するひと手間がかかり、その分遅くなるのだろう。じゃあAppleのRosetta 2はどうしたのか?

この方の言うところによればAppleはx86のコードを変換した時用のモードをM1に追加しているらしい。マジで?このモードであれば、x86のある命令をArmの命令に一対一で変換できれば、x86で想定した範囲の並び替えしか起こらない。シンプル。

実際には命令が一対一対応しているわけではないので変換はゼロコストではないにせよ、オーバーヘッドが30%というのは確かに十分速い。

JavaScriptへの最適化

私には確かめようがないんだけど、JavaScriptを効率的に実行するための命令の追加、L1キャッシュの増加、などなどをしていると言う。マジで?JavaScriptってインタプリタ言語でしょ?と思うけど現代ではJITコンパイルで実行されるし、そこに効く最適化というのもあるのかな…AppleはWebKitも作ってるわけだから、WebKitでどこがボトルネックになっているかも分かるはず。

M1の発表イベントではBig Surとの組み合わせでJavaScriptの実行が1.5倍速くなったと言っていたので、何らかの最適化があったのは確かなのだろう。

参照カウント

iOSおよびMacのソフトウェア開発に使われる言語、Objective-CとSwiftでは、一般的なガベージコレクションによるメモリ管理を採用していない(正確にはMacで一時採用したが、即辞めた)。どうしているのかというと、参照カウントを使う。

つまり、aという変数に何かオブジェクトを代入すると、そのオブジェクトにくっついているカウンタを+1する。aにnullとか他のオブジェクトを代入すると、カウンタは-1される。こうして、どこからも参照されていないオブジェクトはカウンタが0になると解放される。コードを書くとき循環参照に注意する必要があるけれど、「ガベージコレクタが動く時間」というものがない。必要なくなったオブジェクトは即解放されるので。

その代わり、参照しただけでこのカウンタを増やしたり減らしたりする処理が挟まる。アプリ全体ではこの部分だけでも馬鹿にならない回数あるはずだし、これはスレッドセーフでないといけない。

ということで、AppleはM1採用によってこのカウンタ処理を最適化したらしい。どれくらい速くなったのかはAppleのエンジニアもツイートしている。

エッ、マジで?さっき言った参照カウントの増減にIntelでは最大30 nsかかっていたところをM1では最大6.5 ns、Rosetta 2で実行時でさえ最大14 nsなのだという。これはCやC++でゴリゴリ書いた処理のところではなくて、Objective-CやSwiftで書く部分特有のことなので、おそらく世のベンチマークソフトの数字には現れないけれど、アプリのUI部分では馬鹿にならないはず。

どうやって最適化したのか、というとさっきのメモリオーダリングの話らしい。参照カウントの整合性をとりつつ速度を出せる程度にArmは「緩い」。一方でIntelはこの用途には不必要なくらい「厳しい」のだが、そのおかげで顕在化しなかったバグがArmでは出てきてしまう場合があるらしい。

まあこれはiOSでは元々そうだったんじゃないかな…

64bit移行

MacがCPUを代えるのはこれで3度めと言われている。68K → PowerPC → Intel → Arm。でも細かいことを言えば、同じアーキテクチャの中でも命令セットは変わっていくし、64bit移行というでかい変更があった。IntelでもArmでも、32bit命令セットと64bit命令セットというのは異なる命令セットだが、64bitプロセッサでは32bit命令も実行できるようにして互換性を保ってきた。

ところが、慈愛に満ちたMicrosoft Windowsのお世話になっている人には信じられないかもしれないが、MacのArm移行前に、既にMac (Intel) とiOS (Arm)は32bitのアプリは切り捨て、64bitのみサポートしている状態になっていた。つまり、M1 + Rosetta 2のエミュレーションは64bit (x86-64 → AArch64)だけサポートすればよく、32bit (IA-32)のことは考えなくていい。

MicrosoftのArm版Windowsが最初IA-32エミュレーションのみ対応しててx86-64はこれから、というのと比べると手間が半分になるわけだ。ずっこい。

さらに言えば、MacもiOSももう32bitコードをサポートしないので…Apple SiliconからはAArch32も切っちゃっていいのでは?Intelのプロセッサは汎用なので、これからもしばらくは互換性のために32bit命令をサポートするだろう。でもAppleは自社のApple Siliconから32bit対応を即座に切れる。だって自社のOS用に作っていて、そのOSは既に32bit対応を切ってるから。ずっこい!

Appleのあんこくどくさい体制ずっこいねという話よ…

この記事が気に入ったらサポートをしてみませんか?