BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Go 1.16、レジスタベースの呼出規約をサポート

Go 1.16、レジスタベースの呼出規約をサポート

原文(投稿日:2020/08/25)へのリンク

GoチームはGo 1.16で、レジスタベースの呼出規約(calling convention)の実用最小限(minimum viable)実装に取り組んでいる。この変更により、GoogleのGoチームによれば、現行のスタックベース呼出規約を使用するGoコードとの後方互換性を維持しながら、5~10パーセントのスループット改善が可能になる。

Goがスタックベースの呼出規約を使用している理由は、そのルーツであるPlan 9、具体的にはPlan 9 ABIのコミットメントにまで遡ることができる。レジスタベースの呼出規約にスイッチすることによって、Goは今、多くのプラットフォームにおける現代的な言語実装の主流を外れているという、オリジナルデザインの欠点のひとつを克服しようとしているのだ。

スタックベースの呼出規約では、引数と結果をスタックを通じて受け渡しする。このアプローチは実装が極めて簡単である反面、パフォーマンスの面では大きなコストを強いられることになる。実際にGoの開発者らは、スタックアクセスに比較してレジスタアクセスが約40パーセント高速であると、自身のベンチマークに基づいて推定している。さらに、関数呼び出しによって生成されるメモリトラフィックも、パフォーマンス上のマイナスとなる。

Goで新たにレジスタベースの呼出規約を設計する上で、大きな懸念のひとつは、Goのコルーチンに関する適切な動作を実現することだ。

goroutineがスケール性に優れている理由のひとつに、Goのランタイムによるスタックサイズの動的変更があります。しかしながら、これによってABIに課される要件はGo以外の関数では満足されないため、外部コールでは動的スタック構造を使用しないようなランタイムの遷移が必要となります。もうひとつの理由は、goroutineがOSカーネルではなくGoランタイムによってスケジュールされていることです。このため、非Goコードへ、および非Goコードからの遷移においては、Goスケジューラとコミュニケーションする必要があります。

Goの呼出規約がサポート対象のプラットフォームのABIに依存せず、独自のABIを使用しているのは、このような必要性によるものだ。プラットフォームABIを採用すること自体にもデメリットがある。それぞれのABIの持つ広範囲かつ微妙な異質性は、実装の複雑性を増大させる原因になる。また、ABIの多くがCのABIを起源としており、Goとは相性がよくない、という事実もある。

そのため現在の提案では、すべてのプラットフォームで使用する共通ABIと、各プラットフォーム特有の整数および浮動小数点レジスタを定義して、ルールの共有セットを可能な限り使用する、という目標を掲げている。さらに呼出規約では、Goコードではごく一般的に使用されている、複数ワードの戻り値をサポートする必要がある。また、godeferといったステートメントやリフレクションが必要とする、ファーストクラスのコールフレーム表現も可能なものでなくてはならない。

これらの要件や、Goチームが実際に検討を進めている他の要件から、コンパイラやリンカ、ランタイムといったGoツールチェーンには多くの変更が必要になる。例えばコンパイラには、抽象引数レジスタ(abstract argument register)のサポート、遅延呼び出し(late call)の軽減、ABIブリッジなどの機能を追加する必要がある。ランタイムについては、ファーストクラスのコールフレーム表現や、スタックスペースが必要になった場合のスタック拡張を実現する責務を負うことになる。

今回の変更はGo-1との互換性維持を意図しているが、既存コードに支障をきたすケースがいくつかある。Goクロージャを呼び出すアセンブリコードや、引数へのポインタでunsafe.Pointer演算を行ってスタックの内容を参照するコードなどがその例だ。

前述のようにGo 1.16では、新たな呼出規約に関する最小限の実装のみが、amd64プラットフォームを対象として含まれる予定である。要件の全リストと詳細な設計に関しては、公式の提案資料を参照して頂きたい。

この記事に星をつける

おすすめ度
スタイル

BT