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

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

このコミットは、Go言語のコンパイラであるcmd/5g (ARM), cmd/6g (x86-64), cmd/8g (x86) における内部データ構造であるProgAddrのサイズを削減することを目的としています。具体的には、Addr構造体内のフィールドをunionでまとめることと、Prog構造体のフィールドの順序を変更することで、メモリ使用量を最適化し、コンパイラのパフォーマンス向上に寄与しています。

コミット

commit b2797f2ae09680eafe4359fc7c014ef35d27ccdc
Author: Dave Cheney <dave@cheney.net>
Date:   Fri Dec 14 06:20:24 2012 +1100

    cmd/{5,6,8}g: reduce size of Prog and Addr
    
    5g: Prog went from 128 bytes to 88 bytes
    6g: Prog went from 174 bytes to 144 bytes
    8g: Prog went from 124 bytes to 92 bytes
    
    There may be a little more that can be squeezed out of Addr, but alignment will be a factor.
    
    All: remove the unused pun field from Addr
    
    R=rsc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6922048

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

https://github.com/golang/go/commit/b2797f2ae09680eafe4359fc7c014ef35d27ccdc

元コミット内容

cmd/{5,6,8}g: reduce size of Prog and Addr

5g: Prog went from 128 bytes to 88 bytes
6g: Prog went from 174 bytes to 144 bytes
8g: Prog went from 124 bytes to 92 bytes

There may be a little more that can be squeezed out of Addr, but alignment will be a factor.

All: remove the unused pun field from Addr

変更の背景

Go言語のコンパイラは、コンパイル時にプログラムの抽象構文木 (AST) を中間表現に変換し、最終的に機械語コードを生成します。この過程で、Prog (プログラム命令) と Addr (アドレス/オペランド) といったデータ構造が頻繁に生成され、メモリ上に大量に存在します。これらの構造体のサイズが大きいと、メモリ使用量が増加し、コンパイル時間やコンパイラ自体の実行速度に悪影響を及ぼします。

このコミットの主な目的は、これらのコアデータ構造のサイズを削減し、コンパイラのメモリフットプリントを減らすことで、全体的なパフォーマンスを向上させることです。特に、ProgAddrのサイズ削減は、コンパイラの実行効率に直接的な影響を与えます。コミットメッセージに記載されているように、各アーキテクチャのコンパイラでProg構造体のサイズが大幅に削減されています(例: 5gで128バイトから88バイトへ)。

また、Addr構造体から未使用のpunフィールドを削除することも、サイズ削減の一環として行われています。

前提知識の解説

Goコンパイラ (cmd/{5,6,8}g)

Go言語の初期のコンパイラは、Plan 9 Cコンパイラツールチェーンをベースにしていました。5g, 6g, 8gはそれぞれ異なるアーキテクチャをターゲットとするコンパイラを指します。

  • 5g: ARMアーキテクチャ向け
  • 6g: x86-64 (AMD64) アーキテクチャ向け
  • 8g: x86 (32-bit Intel) アーキテクチャ向け

これらのコンパイラは、Goのソースコードを解析し、中間表現を生成し、最終的にターゲットアーキテクチャの機械語コードに変換します。

Prog構造体

Prog構造体は、コンパイラの中間表現における単一の「プログラム命令」または「操作」を表します。これは、アセンブリ言語の命令に似た抽象的な表現であり、実際の機械語命令に変換される前の段階のものです。Progには、操作の種類(asフィールド)、ソースアドレス(fromフィールド)、デスティネーションアドレス(toフィールド)、次の命令へのリンク(linkフィールド)などが含まれます。

Addr構造体

Addr構造体は、Prog構造体内で使用される「アドレス」または「オペランド」を表します。これは、命令が操作するデータやメモリ位置、あるいはジャンプ先の情報などを保持します。Addrには、オフセット(offset)、シンボル(sym)、型(type)、レジスタ(reg)などの情報が含まれます。また、定数値(浮動小数点数dval、文字列sval)や、分岐命令のターゲットとなるProgへのポインタ(branch)など、様々な種類のデータを保持する可能性があります。

C言語のunion

union(共用体)はC言語の機能の一つで、複数の異なる型のメンバーが同じメモリ領域を共有することを可能にします。unionのサイズは、そのメンバーの中で最も大きい型のサイズによって決まります。これにより、ある時点でいずれか一つのメンバーしか使用されない場合に、メモリを節約することができます。例えば、Addr構造体があるときは浮動小数点数を保持し、別のときは文字列を保持するといった場合にunionが有効です。

構造体のアライメントとパディング

C言語(およびGo言語のコンパイラが生成するコード)では、メモリ効率とCPUのアクセス効率を向上させるために、構造体のメンバーが特定のバイト境界に配置されることがあります。これを「アライメント」と呼びます。アライメント要件を満たすために、コンパイラは構造体のメンバー間に「パディング」と呼ばれる未使用のバイトを挿入することがあります。パディングは構造体のサイズを増加させる要因となります。フィールドの順序を適切に並べ替えることで、パディングを最小限に抑え、構造体の全体サイズを削減できる場合があります。

punフィールド

コミットメッセージに「remove the unused pun field from Addr」とあるように、punフィールドはAddr構造体から削除されました。このフィールドは、おそらく「pointer union」または「pseudo union」のような意味合いで、過去に何らかの目的で使われていたが、現在は不要になったものと推測されます。未使用のフィールドを削除することは、構造体サイズ削減の直接的な方法です。

技術的詳細

このコミットにおける主要な技術的変更点は以下の通りです。

  1. Addr構造体におけるunionの導入:

    • src/cmd/{5g,6g,8g}/gg.hにおいて、Addr構造体内のdval (double), branch (Prog*), sval (char[NSNAME]) の各フィールドがuという名前のunion内に移動されました。
    • さらに、vval (vlong) フィールドがこのunionに追加されました。
    • これにより、これらのフィールドはメモリ上で同じ領域を共有するため、Addr構造体のサイズが大幅に削減されます。例えば、dvalが使用される場合はその領域が浮動小数点数として解釈され、branchが使用される場合はポインタとして解釈されます。
    • この変更に伴い、これらのフィールドへのアクセスはa->dvalからa->u.dvalのように変更されました。

    変更前 (5gの例):

    struct Addr
    {
        int32   offset;
        int32   offset2;
        double  dval;
        Prog*   branch;
        char    sval[NSNAME];
        // ...
        char pun; // 削除されるフィールド
        // ...
    };
    

    変更後 (5gの例):

    struct Addr
    {
        int32   offset;
        int32   offset2;
        union {
            double  dval;
            vlong   vval;
            Prog*   branch;
            char    sval[NSNAME];
        } u;
        // ...
        // char pun; は削除
        // ...
    };
    
  2. Addr構造体からのpunフィールドの削除:

    • Addr構造体からuchar pun;フィールドが完全に削除されました。これは未使用であったため、直接的なサイズ削減に貢献します。
  3. Prog構造体のフィールド順序の変更:

    • src/cmd/{5g,6g,8g}/gg.hにおいて、Prog構造体のフィールドの宣言順序が変更されました。
    • 特に、short as; (opcode) フィールドが、uint32 lineno; の後から、void* regp; の後に移動されています。
    • この変更は、構造体のアライメントを最適化し、コンパイラが挿入するパディングバイトを削減することを目的としています。これにより、構造体の全体サイズが小さくなります。

    変更前 (5gの例):

    struct Prog
    {
        short   as;         // opcode
        uint32  loc;        // pc offset in this func
        uint32  lineno;     // source line that generated this
        Addr    from;       // src address
        Addr    to;         // dst address
        Prog*   link;       // next instruction in this func
        void*   regp;       // points to enclosing Reg struct
        uchar   reg;        // doubles as width in DATA op
        uchar   scond;
    };
    

    **変更後 (5gの例):

    struct Prog
    {
        uint32  loc;        // pc offset in this func
        uint32  lineno;     // source line that generated this
        Prog*   link;       // next instruction in this func
        void*   regp;       // points to enclosing Reg struct
        short   as;         // opcode
        uchar   reg;        // doubles as width in DATA op
        uchar   scond;
        Addr    from;       // src address
        Addr    to;         // dst address
    };
    

これらの変更により、各コンパイラにおけるProg構造体のサイズが以下のように削減されました。

  • 5g: 128バイト -> 88バイト
  • 6g: 174バイト -> 144バイト
  • 8g: 124バイト -> 92バイト

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

このコミットは、主に以下のファイル群に影響を与えています。

  • src/cmd/{5g,6g,8g}/gg.h: AddrおよびProg構造体の定義が変更されています。
    • Addr構造体では、dval, branch, svalunion u内に移動し、vvalが追加され、punフィールドが削除されました。
    • Prog構造体では、フィールドの順序が変更されました。
  • src/cmd/{5g,6g,8g}/gobj.c: Addr構造体のメンバーへのアクセスがa->u.dval, a->u.branch, a->u.svalのように変更されています。zaddr関数やgdatacomplex関数などでこの変更が見られます。
  • src/cmd/{5g,6g,8g}/gsubr.c: gbranch, patch, unpatch, naddrなどの関数で、Addr構造体のbranchdvalフィールドへのアクセスがa->u.branch, a->u.dvalのように変更されています。
  • src/cmd/{5g,6g,8g}/list.c: Dconv関数など、Addr構造体の内容を文字列として表示する部分で、a->u.branch, a->u.dval, a->u.svalへのアクセスに変更されています。
  • src/cmd/{5g,6g,8g}/reg.c: regopt, chasejmp, mark, fixjmpなどの関数で、Addr構造体のbranchフィールドへのアクセスがp->to.u.branchのように変更されています。
  • src/cmd/{6g,8g}/peep.c: loop関数内で、p->from.dvalの比較がp->from.u.vvalの比較に変更されています。これは、dvalunion内に移動し、vvalが追加されたことによる影響です。

コアとなるコードの解説

Addr構造体の変更と影響

Addr構造体におけるunion uの導入は、コンパイラコード全体にわたるAddrメンバーへのアクセス方法の変更を必要としました。例えば、以前はa->branchと直接アクセスしていた箇所は、a->u.branchと変更する必要があります。これは、Addr構造体を使用するほぼ全てのファイルに影響を及ぼす大規模な変更です。

  • src/cmd/5g/gg.h (Addr構造体):

    --- a/src/cmd/5g/gg.h
    +++ b/src/cmd/5g/gg.h
    @@ -15,9 +15,13 @@ struct Addr
     {
      int32   offset;
      int32   offset2;
    - double  dval;
    - Prog*   branch;
    - char    sval[NSNAME];
    +
    + union {
    +   double  dval;
    +   vlong   vval;
    +   Prog*   branch;
    +   char    sval[NSNAME];
    + } u;
    
      Sym*    sym;
      Node*   node;
    @@ -25,7 +29,6 @@ struct Addr
      uchar   type;
      char    name;
      uchar   reg;
    - char pun; // 削除
      uchar   etype;
     };
    
  • src/cmd/5g/gobj.c (アクセス例):

    --- a/src/cmd/5g/gobj.c
    +++ b/src/cmd/5g/gobj.c
    @@ -128,9 +128,9 @@ zaddr(Biobuf *b, Addr *a, int s)
     		break;
     
     	case D_BRANCH:
    -		if(a->branch == nil)
    +		if(a->u.branch == nil)
     			fatal("unpatched branch");
    -		a->offset = a->branch->loc;
    +		a->offset = a->u.branch->loc;
     		l = a->offset;
     		Bputc(b, l);
     		Bputc(b, l>>8);
    

    この変更は、Addr構造体の内部表現が変わったため、そのメンバーにアクセスするすべてのコードパスで修正が必要であることを示しています。

Prog構造体のフィールド順序変更と影響

Prog構造体のフィールド順序の変更は、主にメモリレイアウトの最適化を目的としています。これは、C言語の構造体パディングの挙動を考慮したもので、コンパイラが自動的に挿入するパディングバイトを最小限に抑えることで、構造体全体のサイズを削減します。この変更自体は、Prog構造体のメンバーへのアクセス方法を変更するものではないため、Prog構造体を使用するコードの修正は最小限で済みます。しかし、構造体のサイズが小さくなることで、メモリキャッシュの効率が向上し、コンパイラの実行速度に良い影響を与える可能性があります。

  • src/cmd/5g/gg.h (Prog構造体):
    --- a/src/cmd/5g/gg.h
    +++ b/src/cmd/5g/gg.h
    @@ -29,12 +29,12 @@ struct Prog
     {
    - short   as;         // opcode
      uint32  loc;        // pc offset in this func
      uint32  lineno;     // source line that generated this
    - Addr    from;       // src address
    - Addr    to;         // dst address
      Prog*   link;       // next instruction in this func
      void*   regp;       // points to enclosing Reg struct
    + short   as;         // opcode
      uchar   reg;        // doubles as width in DATA op
      uchar   scond;
    + Addr    from;       // src address
    + Addr    to;         // dst address
     };
    
    asフィールドが移動し、fromtoフィールドが最後に移動していることがわかります。これは、より大きなアライメント要件を持つフィールド(ポインタやAddr構造体自体)をまとめて配置することで、パディングを減らす一般的な最適化手法です。

これらの変更は、Goコンパイラの内部構造の深い理解と、C言語のメモリ管理に関する知識に基づいています。コンパイラのパフォーマンスを向上させるための、低レベルかつ重要な最適化と言えます。

関連リンク

参考にした情報源リンク