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

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

このコミットは、Go言語のビルドツールである cmd/dist において、x86アーキテクチャ(特に32ビット版のGOARCH=386)向けにSSE (Streaming SIMD Extensions) 命令セットのサポートを自動検出する機能を追加するものです。これにより、コンパイル時にSSE2命令が利用可能なCPUであれば、Goコンパイラがより最適化されたコードを生成できるようになります。

コミット

commit a4e08183d56aa3e6c524cadb71671c40495f45a1
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jan 4 10:59:10 2013 -0500

    cmd/dist: sse auto-detect
    
    R=golang-dev, dsymonds, minux.ma, iant, alex.brainman
    CC=golang-dev
    https://golang.org/cl/7035055

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

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

元コミット内容

このコミットは、Goのビルドシステムの一部である cmd/dist に、SSE命令セットの自動検出機能を追加します。具体的には、GO386 環境変数のデフォルト値を決定する際に、実行環境のCPUがSSE2をサポートしているかどうかをチェックし、サポートしていれば "sse" を、そうでなければ従来の "387" を設定するように変更します。

変更されたファイルは以下の通りです。

  • src/cmd/dist/a.h: cansse() 関数のプロトタイプ宣言を追加。
  • src/cmd/dist/build.c: init() 関数内で GO386 の設定ロジックを変更し、cansse() の結果に基づいて "sse" または "387" を選択するように修正。
  • src/cmd/dist/plan9.c: Plan 9環境向けの cansse() 実装を追加。ここでは常にSSE非対応として 0 を返す。
  • src/cmd/dist/unix.c: Unix系OS向けの cansse() 実装を追加。cpuid 命令を用いてSSE2のサポートを検出。
  • src/cmd/dist/windows.c: Windows向けの cansse() 実装を追加。cpuid 命令を用いてSSE2のサポートを検出。

変更の背景

Go言語は、様々なアーキテクチャで動作するように設計されています。x86アーキテクチャにおいては、初期のIntel 80387コプロセッサから始まり、Pentium IIIで導入されたSSE、Pentium 4で導入されたSSE2など、時代とともに新しい命令セットが追加されてきました。これらの新しい命令セットは、浮動小数点演算やSIMD (Single Instruction, Multiple Data) 処理において、より高いパフォーマンスを提供します。

Goのビルドプロセスでは、GO386 環境変数(またはそれに相当する内部設定)が、32ビットx86アーキテクチャ向けのコンパイル時にどの命令セットをターゲットにするかを決定していました。以前は、この設定が明示的に行われない場合、デフォルトで「387」という、SSE命令を考慮しない古い命令セットをターゲットにしていました。

しかし、2013年当時、ほとんどの現代的なx86 CPUはSSE2をサポートしており、Goがこれらの命令を活用できれば、特に数値計算やメディア処理などにおいて、より高速な実行が可能になります。このコミットの背景には、Goのコンパイル済みバイナリが、実行環境のCPU能力を最大限に活用できるように、SSE2のサポートを自動的に検出し、適切なコンパイルオプションを選択するという目的があります。これにより、ユーザーが手動で GO386 を設定する必要がなくなり、利便性とパフォーマンスが向上します。

前提知識の解説

1. cmd/dist

cmd/dist は、Go言語のソースコードからGoツールチェイン自体をビルドするためのプログラムです。Goのコンパイラ、リンカ、アセンブラなどのツール群を構築する際に使用されます。これはGoのブートストラッププロセスの一部であり、Goのバージョンアップやクロスコンパイル環境の構築において重要な役割を担います。

2. SSE (Streaming SIMD Extensions) と SSE2

SSEは、Intelが開発したSIMD命令セットの拡張機能です。SIMDは、単一の命令で複数のデータ要素に対して同じ操作を同時に実行できるため、特に浮動小数点演算やマルチメディア処理、科学技術計算などで高いパフォーマンスを発揮します。

  • SSE (SSE1): Pentium IIIで導入され、主に単精度浮動小数点数(32ビット)のSIMD演算をサポートします。8つの128ビットXMMレジスタを使用します。
  • SSE2: Pentium 4で導入され、SSEをさらに拡張したものです。単精度だけでなく、倍精度浮動小数点数(64ビット)のSIMD演算もサポートし、さらに整数演算のSIMD機能も強化されました。現代のほとんどのx86-64 CPUはSSE2をサポートしています。

Go言語のコンパイラがSSE/SSE2命令を利用できるようにコンパイルされると、Goプログラム内でこれらの命令が活用され、特定の処理が高速化される可能性があります。

3. GO386 環境変数

GO386 は、Go言語のビルド時に32ビットx86アーキテクチャ(GOARCH=386)向けのコード生成オプションを指定するための環境変数です。この変数の値によって、コンパイラが生成するアセンブリコードの種類が変わります。

  • GO386=387: これは、Intel 80387コプロセッサ(またはそれ以降のCPUに内蔵されたFPU)が提供する浮動小数点演算命令のみを使用することを意味します。SSE命令は使用されません。これは最も互換性の高いモードですが、パフォーマンスは劣ります。
  • GO386=sse: これは、SSE2命令セットが利用可能であることを前提とし、コンパイラがSSE2命令を活用してコードを生成することを意味します。これにより、浮動小数点演算やSIMD処理が高速化されます。

このコミット以前は、GO386 が設定されていない場合、デフォルトで 387 が使用されていました。このコミットにより、自動的に sse が選択される可能性が生まれました。

4. CPUID 命令

CPUID命令は、x86アーキテクチャのプロセッサが提供する特殊な命令で、CPUの機能や情報をプログラムから問い合わせるために使用されます。この命令を実行すると、CPUのモデル、ブランド、サポートする命令セット(SSE, AVXなど)、キャッシュ情報などがレジスタに格納されます。

CPUID命令は、特定の入力値(EAXレジスタに設定)に応じて異なる情報を返します。例えば、EAX=1 を指定してCPUIDを実行すると、プロセッサのバージョン情報と機能情報(EDXレジスタのビットフラグ)が返されます。SSE2のサポートは、この機能情報ビットの特定の場所(EDXレジスタのビット26)で示されます。

技術的詳細

このコミットの核となる技術的変更は、cansse() という新しい関数の導入と、その関数が cpuid 命令を使用してCPUのSSE2サポートを検出するロジックです。

  1. cansse() 関数の導入: src/cmd/dist/a.hbool cansse(void); が宣言され、各OS(Unix系、Windows、Plan 9)固有のファイルで実装されます。この関数は、現在のCPUがSSE2命令セットをサポートしている場合に true を、そうでない場合に false を返します。

  2. build.c での GO386 の自動設定: src/cmd/dist/build.cinit() 関数内で、GO386 環境変数が設定されていない場合に、cansse() の結果に基づいてデフォルト値を決定するロジックが追加されました。

    if(b.len == 0) {
        if(cansse())
            bwritestr(&b, "sse");
        else
            bwritestr(&b, "387");
    }
    

    これにより、GO386 が明示的に指定されていない場合でも、システムがSSE2をサポートしていれば自動的に "sse" が選択され、GoコンパイラがSSE2命令を活用するようになります。

  3. OSごとの cansse() 実装:

    • src/cmd/dist/unix.c および src/cmd/dist/windows.c: これらのファイルでは、__cpuid (または cpuid) というヘルパー関数が定義されています。これはインラインアセンブリ (asm volatile("cpuid" ...) ) を使用して、CPUの CPUID 命令を実行します。 cansse() 関数内では、__cpuid(info, 1) を呼び出して、EAX=1 のCPUID情報を取得します。この情報のうち、info[3] (EDXレジスタに対応) のビット26がSSE2のサポートを示すフラグです。 return (info[3] & (1<<26)) != 0; この行は、EDXレジスタのビット26がセットされているかどうかをチェックし、セットされていればSSE2がサポートされていると判断します。

    • src/cmd/dist/plan9.c: Plan 9はGoの主要なターゲットOSの一つですが、この環境ではCPUID命令への直接的なアクセスが困難であるか、あるいはSSEの利用が一般的ではないため、cansse() は常に 0 (false) を返します。

      bool
      cansse(void)
      {
          // if we had access to cpuid, could answer this question
          // less conservatively.
          return 0;
      }
      

      これは、Plan 9環境ではSSE自動検出を無効にするという設計判断を示しています。

この自動検出機能は、Goのビルドシステムが実行される環境のCPU能力を動的に判断し、それに基づいて最適なコンパイルオプションを選択することを可能にします。これにより、Goバイナリのパフォーマンスと互換性のバランスが向上します。

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

src/cmd/dist/a.h

--- a/src/cmd/dist/a.h
+++ b/src/cmd/dist/a.h
@@ -123,6 +123,7 @@ void	runv(Buf *b, char *dir, int mode, Vec *argv);
 void	bgrunv(char *dir, int mode, Vec *argv);
 void	bgwait(void);
 bool	streq(char*, char*);
+bool	cansse(void);
 void	writefile(Buf*, char*, int);
 void	xatexit(void (*f)(void));
 void	xexit(int);

src/cmd/dist/build.c

--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -104,8 +104,12 @@ init(void)
 	goarm = btake(&b);
 
 	xgetenv(&b, "GO386");
-	if(b.len == 0)
-		bwritestr(&b, "387");
+	if(b.len == 0) {
+		if(cansse())
+			bwritestr(&b, "sse");
+		else
+			bwritestr(&b, "387");
+	}
 	go386 = btake(&b);
 
 	p = bpathf(&b, "%s/include/u.h", goroot);

src/cmd/dist/plan9.c

--- a/src/cmd/dist/plan9.c
+++ b/src/cmd/dist/plan9.c
@@ -758,4 +758,12 @@ xtryexecfunc(void (*f)(void))\n 	return 0; // suffice for now\n }\n \n+bool\n+cansse(void)\n+{\n+\t// if we had access to cpuid, could answer this question\n+\t// less conservatively.\n+\treturn 0;\n+}\n+\n #endif // PLAN9

src/cmd/dist/unix.c

--- a/src/cmd/dist/unix.c
+++ b/src/cmd/dist/unix.c
@@ -741,6 +741,7 @@ xsamefile(char *f1, char *f2)\n \n sigjmp_buf sigill_jmpbuf;\n static void sigillhand(int);\n+\n // xtryexecfunc tries to execute function f, if any illegal instruction\n // signal received in the course of executing that function, it will\n // return 0, otherwise it will return 1.\n@@ -757,6 +758,7 @@ xtryexecfunc(void (*f)(void))\n \tsignal(SIGILL, SIG_DFL);\n \treturn r;\n }\n+\n // SIGILL handler helper\n static void\n sigillhand(int signum)\n@@ -765,5 +767,26 @@ sigillhand(int signum)\n \tsiglongjmp(sigill_jmpbuf, 1);\n }\n \n+static void\n+__cpuid(int dst[4], int ax)\n+{\n+#if defined(__i386__) || defined(__x86_64__)\n+\tasm volatile("cpuid"\n+\t\t: "=a" (dst[0]), "=b" (dst[1]), "=c" (dst[2]), "=d" (dst[3])\n+\t\t: "0" (ax));\n+#else\n+\tdst[0] = dst[1] = dst[2] = dst[3] = 0;\n+#endif\n+}\n+\n+bool\n+cansse(void)\n+{\n+\tint info[4];\n+\t\n+\t__cpuid(info, 1);\n+\treturn (info[3] & (1<<26)) != 0;\t// SSE2\n+}\n+\n #endif // PLAN9
 #endif // __WINDOWS__

src/cmd/dist/windows.c

--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -971,4 +971,29 @@ xtryexecfunc(void (*f)(void))\n \treturn 0; // suffice for now\n }\n \n+static void\n+cpuid(int dst[4], int ax)\n+{\n+\t// NOTE: This asm statement is for mingw.\n+\t// If we ever support MSVC, use __cpuid(dst, ax)\n+\t// to use the built-in.\n+#if defined(__i386__) || defined(__x86_64__)\n+\tasm volatile("cpuid"\n+\t\t: "=a" (dst[0]), "=b" (dst[1]), "=c" (dst[2]), "=d" (dst[3])\n+\t\t: "0" (ax));\n+#else\n+\tdst[0] = dst[1] = dst[2] = dst[3] = 0;\n+#endif\n+}\n+\n+bool\n+cansse(void)\n+{\n+\tint info[4];\n+\t\n+\tcpuid(info, 1);\n+\treturn (info[3] & (1<<26)) != 0;\t// SSE2\n+}\n+\n+\n #endif // __WINDOWS__

コアとなるコードの解説

このコミットの核心は、src/cmd/dist/build.c における GO386 環境変数の設定ロジックの変更と、それを支える cansse() 関数の実装です。

  1. src/cmd/dist/build.c の変更: init() 関数は、Goのビルドプロセス初期化時に様々な環境変数を設定します。この中で GO386 の設定部分が変更されました。 変更前: GO386 が設定されていなければ、無条件に "387" をデフォルトとしていました。 変更後: GO386 が設定されていない場合、新しく追加された cansse() 関数を呼び出します。

    • cansse()true を返した場合(つまり、SSE2がサポートされている場合)、GO386"sse" を設定します。
    • cansse()false を返した場合(SSE2がサポートされていないか、検出できない場合)、従来通り "387" を設定します。 この変更により、Goのビルドシステムは、実行環境のCPU能力を自動的に判断し、最適なコンパイルオプションを選択できるようになりました。
  2. cansse() 関数の実装 (Unix系および Windows): src/cmd/dist/unix.csrc/cmd/dist/windows.c に実装された cansse() 関数は、CPUの機能を検出するために CPUID 命令を利用します。

    • __cpuid (または cpuid) ヘルパー関数は、インラインアセンブリを使用して CPUID 命令を実行します。CPUID 命令は、EAXレジスタに特定の値を設定して呼び出すことで、CPUに関する様々な情報を取得できます。ここでは ax (EAX) に 1 を設定して呼び出しています。EAX=1 の CPUID 呼び出しは、プロセッサのバージョン情報と機能情報を返します。
    • 返された情報(info 配列に格納される)のうち、info[3] はEDXレジスタに対応します。EDXレジスタのビット26は、SSE2命令セットのサポートを示すフラグです。
    • return (info[3] & (1<<26)) != 0; という式は、EDXレジスタのビット26がセットされているかどうかをチェックしています。ビット26がセットされていれば、SSE2がサポートされていると判断し true を返します。

このメカニズムにより、Goのビルドシステムは、ユーザーが明示的に GO386 を設定しなくても、現代のCPUの性能を最大限に引き出すためのコンパイルオプションを自動的に選択できるようになりました。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/
  • Goのソースコードリポジトリ: https://github.com/golang/go
  • Goのコードレビューシステム (Gerrit): https://golang.org/cl/ (コミットメッセージに記載されている https://golang.org/cl/7035055 はこのコミットのGerritチェンジリストへのリンクです)

参考にした情報源リンク