Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11417] ファイルの概要

このコミットは、Goコンパイラのインライン化(inlining)の基準を緩和し、より多くの関数がインライン化されるように変更を加えるものです。これにより、コンパイルされたコードのパフォーマンス向上が期待されます。具体的には、関数の「複雑さ(hairyness)」を評価するロジックが調整され、インライン化の予算(budget)が導入されました。

コミット

commit 93e547a0c2aec056027558bca5dcfa706d9f6eda
Author: Luuk van Dijk <lvd@golang.org>
Date:   Thu Jan 26 17:20:48 2012 +0100

    gc: softer criteria for inlinability.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5555072

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/93e547a0c2aec056027558bca5dcfa706d9f6eda

元コミット内容

gc: softer criteria for inlinability.

R=rsc
CC=golang-dev
https://golang.org/cl/5555072

変更の背景

Goコンパイラにおける関数インライン化は、プログラムの実行速度を向上させるための重要な最適化手法です。インライン化とは、関数呼び出しのオーバーヘッドを削減し、呼び出し元の関数内で呼び出される関数の本体を直接展開することで、さらなる最適化の機会を増やす技術です。しかし、過度なインライン化はバイナリサイズの増大やコンパイル時間の増加を招く可能性があります。

このコミット以前のGoコンパイラ(gc)では、関数のインライン化に関する基準が厳しすぎたため、インライン化できるはずの関数がインライン化されず、パフォーマンスの機会が失われている可能性がありました。特に、関数の「複雑さ(hairyness)」を判断するロジックが単純すぎたため、わずかに複雑な関数でもインライン化の対象から外れていました。

この変更の背景には、より多くの関数をインライン化できるようにインライン化の基準を緩和し、全体的なパフォーマンスを向上させたいという意図があります。特に、単一のステートメントを持つ関数や、特定の種類のASTノードを持つ関数に対する制限が厳しすぎたため、それらの制限を緩和することが目的でした。

前提知識の解説

  • 関数インライン化 (Function Inlining): コンパイラ最適化の一種で、関数呼び出しをその関数の本体のコードで置き換えること。これにより、関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、レジスタの保存・復元など)が削減され、キャッシュの局所性が向上し、さらに他の最適化(定数伝播、デッドコード削除など)が可能になる。
  • Goコンパイラ (gc): Go言語の公式コンパイラ。Goのソースコードを機械語に変換する役割を担う。
  • AST (Abstract Syntax Tree): 抽象構文木。ソースコードの構文構造を木構造で表現したもの。コンパイラはソースコードをASTに変換し、それに対して様々な解析や最適化を行う。Goコンパイラには独自の内部ASTノード定義がある。
    • OCALL, OCALLFUNC, OCALLINTER, OCALLMETH: 関数呼び出しを表すASTノード。
    • OCLOSURE: クロージャ(関数リテラル)を表すASTノード。
    • ODCL: 変数宣言を表すASTノード。
  • インライン化予算 (Inlining Budget): Goコンパイラが関数をインライン化するかどうかを決定する際に使用する「複雑さ」の閾値。関数のASTノード数に基づいて計算される「コスト」がこの予算内であれば、インライン化の候補となる。予算を超えるとインライン化されない。
  • debug['l'] フラグ: Goコンパイラのデバッグフラグの一つで、インライン化の積極性を制御する。
    • 0: インライン化無効
    • 1: デフォルト(40ノードのリーフ関数、ワンライナー、遅延型チェック)
    • 2: 全てのインポートされたボディの早期型チェック
    • 4: 非リーフ関数のインライン化を許可(runtime.Callerを壊す可能性あり)
    • 5: 推移的インライン化(Transitive Inlining)
  • debug['m'] フラグ: Goコンパイラのデバッグフラグの一つで、最適化の決定(インライン化やエスケープ解析など)に関する診断出力を表示する。

技術的詳細

このコミットは、Goコンパイラのインライン化ロジックが実装されている src/cmd/gc/inl.c ファイルに大きな変更を加えています。主な変更点は以下の通りです。

  1. インライン化基準の緩和:

    • 以前は、関数がインライン化されるためには「正確に1つのステートメント」しか持てず、そのステートメントもRETURNASAS2EMPTYのいずれかに限定されていました。このコミットでは、この厳格な制限が削除されました。これにより、より複雑な関数もインライン化の候補となる道が開かれました。
    • caninl関数から、fn->nbody == nil || fn->nbody->next != nilswitch(fn->nbody->n->op) による厳格なチェックが削除されています。代わりに、fn->nbody == nil (ボディがない関数はインライン化できない) のチェックのみが残されています。
  2. 「複雑さ(hairyness)」の評価に予算システムを導入:

    • ishairy関数とishairylist関数にbudgetという新しい引数が追加されました。これは、関数のASTノードをトラバースする際に消費される「予算」を表します。
    • ishairy関数が呼び出されるたびに(*budget)--が行われ、予算がゼロを下回ると、その関数は「複雑すぎる(hairy)」と判断され、インライン化の対象から外れます。デフォルトの予算は40に設定されています。
    • これにより、以前は単純なノード数で一律に判断されていた「複雑さ」が、より柔軟な予算ベースの評価に変わりました。
  3. インライン化を妨げる特定のASTノードの条件付き緩和:

    • ishairy関数内のswitch(n->op)ブロックが変更され、OCALL, OCALLFUNC, OCALLINTER, OCALLMETHといった関数呼び出しノードが、debug['l'] < 4の場合にのみインライン化を妨げるようになりました。これは、debug['l']が4以上(非リーフ関数のインライン化を許可するレベル)の場合には、これらのノードが存在してもインライン化が可能になることを意味します。
    • OCLOSURE, ORANGE, OFOR, OSELECT, OSWITCH, OPROC, ODEFERといった制御フローや特殊な構造を持つノードは引き続きインライン化を妨げます。
    • ODCL, ODCLTYPE, ODCLCONSTといった宣言ノードもインライン化を妨げるようになりました。これは、これらのノードがエクスポート時に適切に表現できない、または型チェックや印刷の問題があるためと考えられます。
    • OAS(代入)ノードについても、右辺がN(nil)の場合(ゼロ値初期化など)はインライン化を妨げるようになりました。
  4. 多値戻り値のインライン化の改善:

    • inlgluelistinlgluerlist関数が削除され、inlconv2listという新しい関数が導入されました。
    • inlconv2listは、インライン化された関数が複数の戻り値を持つ場合に、その戻り値を適切に処理し、インライン化されたステートメントを呼び出し元のコードに統合するためのロジックを提供します。これは、ORETURN, OCALLFUNC, OCALLMETH, OCALLINTER, OAS2FUNCといった多値戻り値を扱う可能性のある操作で利用されます。
  5. 推移的インライン化 (Transitive Inlining) の実験的サポート:

    • mkinlcall関数内に、debug['l'] >= 5の場合に推移的インライン化を試みる実験的なコードが追加されました。推移的インライン化とは、インライン化された関数がさらに別の関数を呼び出している場合に、その呼び出しもインライン化しようとするものです。
    • この機能は、fn->inl = nil;で無限再帰を防ぎつつ、inlnodelist(call->nbody)でインライン化されたボディ内の呼び出しを処理し、inlconv2stmtでステートメントに変換することで実現されています。

これらの変更により、Goコンパイラはより多くの関数をインライン化できるようになり、特に小さな関数やワンライナーのパフォーマンスが向上することが期待されます。

コアとなるコードの変更箇所

変更は主に src/cmd/gc/inl.c ファイルに集中しています。

  • caninl 関数:
    • 厳格なステートメント数と種類のチェックが削除されました。
    • ishairyの呼び出しがishairylistに変更され、budget引数が追加されました。
  • ishairylist 関数:
    • budgetポインタ引数が追加されました。
    • ishairyの呼び出しにbudgetが渡されるようになりました。
  • ishairy 関数:
    • budgetポインタ引数が追加されました。
    • (*budget)--による予算の消費ロジックが追加されました。
    • *budget < 0の場合にインライン化を妨げる条件が追加されました。
    • OCALL系のノードに対するdebug['l'] < 4の条件付きチェックが追加されました。
    • OCLOSURE, ODCL, ODCLTYPE, ODCLCONST, OAS (右辺がnilの場合) がインライン化を妨げるノードとして追加されました。
  • inlgluelist および inlgluerlist 関数:
    • これらの関数が削除されました。
  • inlconv2list 関数:
    • 新しい関数として追加されました。多値戻り値のインライン化を処理します。
  • inlnode 関数:
    • ORETURN, OCALLFUNC, OCALLMETH, OCALLINTER の処理で、多値戻り値の場合にinlconv2listを使用するように変更されました。
    • OAS2FUNC の処理で、inlconv2listを使用するように変更されました。
  • mkinlcall 関数:
    • fn == curfn || fn->defn == curfn (再帰呼び出しの防止) のチェックが追加されました。
    • debug['l'] >= 5の場合の推移的インライン化の実験的ロジックが追加されました。

コアとなるコードの解説

caninl 関数の変更

以前のcaninl関数は、インライン化の候補となる関数に対して非常に厳格な条件を課していました。特に、関数のボディが「正確に1つのステートメント」で構成され、それがRETURNAS(代入)、AS2(多重代入)、EMPTYのいずれかである必要がありました。このコミットでは、これらの厳しすぎる制約が削除されました。これにより、より複雑な関数でもインライン化の初期段階を通過できるようになります。

また、関数の「複雑さ」を判断するために、以前はishairy(fn)という単純な呼び出しでしたが、新しいishairylist(fn->nbody, &budget)という呼び出しに変わりました。これは、関数のボディ全体をリストとして渡し、インライン化の「予算」を考慮に入れることを意味します。

ishairy および ishairylist 関数の変更

これらの関数は、Goコンパイラが関数の「複雑さ」を判断するために使用されます。

  • 予算の導入: 最も重要な変更は、budgetという整数ポインタ引数の導入です。ishairy関数がASTノードをトラバースするたびに、(*budget)--によって予算が1ずつ減らされます。予算がゼロを下回ると、その関数はインライン化するには複雑すぎると判断され、1(hairyである)を返します。これにより、関数のASTノードの総数に基づいてインライン化の可否を判断する、より柔軟なメカニズムが提供されます。デフォルトの予算は40に設定されています。
  • 関数呼び出しの条件付き許可: 以前は、OCALLOCALLFUNCOCALLINTEROCALLMETHといった関数呼び出しノードが存在するだけで、その関数はインライン化できないと判断されていました。この変更では、debug['l'] < 4の場合にのみこれらのノードがインライン化を妨げるようになりました。これは、デバッグレベルが4以上(非リーフ関数のインライン化を許可するレベル)であれば、関数が他の関数を呼び出していてもインライン化が可能になることを示唆しています。
  • 新たなインライン化阻害要因: OCLOSURE(クロージャ)、ODCL(変数宣言)、ODCLTYPE(型宣言)、ODCLCONST(定数宣言)といったノードが、インライン化を妨げる要因として明示的に追加されました。これらは、インライン化時にセマンティクスを正しく保持するのが難しい、またはエクスポート時に問題が生じる可能性があるためと考えられます。また、右辺がnilのOAS(ゼロ値初期化の代入)もインライン化を妨げるようになりました。

多値戻り値と推移的インライン化の改善

  • inlconv2list: 以前のinlgluelistinlgluerlistに代わる新しいヘルパー関数です。インライン化された関数が複数の戻り値を持つ場合、その戻り値を適切に処理し、インライン化されたステートメント(ninitnbody)を呼び出し元のコードに統合するために使用されます。これにより、多値戻り値を持つ関数のインライン化がより正確かつ堅牢になります。
  • mkinlcallにおける推移的インライン化: debug['l'] >= 5の場合に、実験的に推移的インライン化が試みられるようになりました。これは、インライン化された関数がさらに別の関数を呼び出している場合に、その呼び出しもインライン化しようとするものです。fn->inl = nil;で無限再帰を防ぎつつ、インライン化されたボディ内の呼び出しを再帰的に処理することで実現されます。これは、より深いレベルでの最適化を可能にするための重要なステップです。

これらの変更は、Goコンパイラがより多くの関数をインライン化できるようにし、特に小さな関数やワンライナーのパフォーマンスを向上させることを目的としています。予算システムの導入により、インライン化の判断がより洗練され、コンパイラの最適化能力が向上しました。

関連リンク

参考にした情報源リンク