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

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

このコミットは、GoランタイムがFreeBSDシステム上で利用可能なCPUコア数を正確に検出できるようにするための変更を導入しています。具体的には、FreeBSDのsysctlシステムコールを利用してhw.ncpuの値を読み取り、Goランタイムの内部変数runtime.ncpuに設定する機能を追加しています。これにより、GoプログラムがFreeBSD上でより効率的にリソースを利用できるようになります。

コミット

commit 12bf00054e7bab156edd793fed97c61ba212389a
Author: Devon H. O'Dell <devon.odell@gmail.com>
Date:   Tue Jan 10 17:39:17 2012 +1100

    runtime: enable runtime.ncpu on FreeBSD
    
    R=adg, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/5528062

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

https://github.com/golang/go/commit/12bf00054e7bab156edd793fed97c61ba212389a

元コミット内容

    runtime: enable runtime.ncpu on FreeBSD
    
    R=adg, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/5528062

変更の背景

Goランタイムは、プログラムの並行処理を最適化するために、システムが利用可能なCPUコア数(論理プロセッサ数)を認識する必要があります。この情報は、Goのスケジューラがゴルーチンを効率的にM(OSスレッド)にマッピングし、GOMAXPROCS環境変数のデフォルト値を決定する際に重要となります。

FreeBSDシステムでは、以前のGoランタイムはCPUコア数を正しく取得できていませんでした。その結果、GoプログラムがFreeBSD上で実行される際に、利用可能なCPUリソースを最大限に活用できない可能性がありました。このコミットは、FreeBSD固有のシステムコールであるsysctlを使用してCPUコア数を取得するメカニズムを導入することで、この問題を解決し、FreeBSD上でのGoプログラムのパフォーマンスとリソース利用効率を向上させることを目的としています。

前提知識の解説

Goランタイムとスケジューラ

Go言語は、独自のランタイムとスケジューラを持っています。これは、GoプログラムがOSスレッド(M)上でゴルーチン(G)を効率的に実行するためのものです。スケジューラは、P(プロセッサ、論理CPU)という抽象概念を用いて、ゴルーチンをMに割り当てます。runtime.ncpuは、このPの数を決定する上で重要な役割を果たします。デフォルトでは、GOMAXPROCSruntime.ncpuの値に設定されます。

sysctlシステムコール

sysctlは、Unix系オペレーティングシステム(FreeBSD、Linux、macOSなど)でカーネルのパラメータを動的に読み書きするためのシステムコールです。システムに関する様々な情報(ハードウェア情報、ネットワーク設定、メモリ情報など)を、実行中のカーネルから取得したり、設定したりするために使用されます。

sysctlは通常、名前空間によって階層的に整理された変数にアクセスします。例えば、FreeBSDではCPUコア数はhw.ncpuという名前でアクセスできます。この名前は、CTL_HW(ハードウェア関連)とHW_NCPU(CPU数)という2つの整数値の配列(MIB: Management Information Base)として表現されます。

アセンブリ言語とシステムコール

Goランタイムは、OS固有の低レベルな処理(システムコールなど)を実行するために、アセンブリ言語を使用することがあります。これは、C言語などの高水準言語では直接アクセスできないOSの機能を利用するため、またはパフォーマンス上の理由からです。システムコールは、ユーザー空間のプログラムがカーネル空間の機能にアクセスするための唯一の手段です。

runtime.ncpu

runtime.ncpuは、Goランタイムが認識するシステム上の論理CPUコア数を格納する内部変数です。この値は、Goのスケジューラが並行処理の度合いを決定する際に利用されます。

技術的詳細

このコミットの主要な技術的変更点は、FreeBSD上でsysctlシステムコールを呼び出すためのGoランタイムのサポートを追加したことです。

  1. sysctlシステムコールのラッパー関数:

    • src/pkg/runtime/os_freebsd.hruntime·sysctl関数のプロトタイプが追加されました。これは、GoランタイムがC言語で書かれた部分からsysctlを呼び出すための宣言です。
    • src/pkg/runtime/sys_freebsd_386.ssrc/pkg/runtime/sys_freebsd_amd64.sに、それぞれ32ビット(i386)と64ビット(amd64)アーキテクチャ向けのアセンブリ言語によるruntime·sysctlの実装が追加されました。
      • これらのアセンブリコードは、sys___sysctlというFreeBSD固有のシステムコールを呼び出します。システムコール番号は202です。
      • 引数はスタックからレジスタにロードされ、システムコールが実行されます。
      • システムコールの戻り値(成功/失敗)は、Goランタイムの規約に従って処理されます。
  2. getncpu関数の追加:

    • src/pkg/runtime/thread_freebsd.cgetncpuというC言語関数が追加されました。
    • この関数は、CTL_HWHW_NCPUというMIB(Management Information Base)配列を定義し、runtime·sysctlを呼び出してhw.ncpuの値を問い合わせます。
    • sysctlの呼び出しは、mib(問い合わせる情報の識別子)、namelen(識別子の長さ)、oldp(結果を格納するバッファ)、oldlenp(バッファのサイズ)、newp(設定する新しい値、今回はNULL)、newlen(新しい値のサイズ、今回は0)を引数として取ります。
    • getncpuは、sysctlが成功した場合は取得したCPU数を返し、失敗した場合はデフォルト値として1を返します。
  3. runtime.ncpuの設定:

    • src/pkg/runtime/thread_freebsd.cruntime·osinit関数内で、getncpu()が呼び出され、その戻り値がruntime·ncpuに代入されます。
    • runtime·osinitは、Goランタイムが初期化される際にOS固有の初期化処理を行う関数です。これにより、Goプログラムが起動する際にFreeBSDのCPUコア数が正しく検出され、ランタイムに設定されるようになります。

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

src/pkg/runtime/os_freebsd.h

--- a/src/pkg/runtime/os_freebsd.h
+++ b/src/pkg/runtime/os_freebsd.h
@@ -1,12 +1,13 @@
 #define SIG_DFL ((void*)0)
 #define SIG_IGN ((void*)1)
 
-int32 runtime·thr_new(ThrParam*, int32);
-void runtime·sigpanic(void);
-void runtime·sigaltstack(Sigaltstack*, Sigaltstack*);
-struct sigaction;
-void runtime·sigaction(int32, struct sigaction*, struct sigaction*);
+int32	runtime·thr_new(ThrParam*, int32);
+void	runtime·sigpanic(void);
+void	runtime·sigaltstack(Sigaltstack*, Sigaltstack*);
+struct	sigaction;
+void	runtime·sigaction(int32, struct sigaction*, struct sigaction*);
 void	runtiem·setitimerval(int32, Itimerval*, Itimerval*);
 void	runtime·setitimer(int32, Itimerval*, Itimerval*);
+int32	runtime·sysctl(uint32*, uint32, byte*, uintptr*, byte*, uintptr);
 
 void	runtime·raisesigpipe(void);

runtime·sysctl関数のプロトタイプ宣言が追加されました。

src/pkg/runtime/sys_freebsd_386.s

--- a/src/pkg/runtime/sys_freebsd_386.s
+++ b/src/pkg/runtime/sys_freebsd_386.s
@@ -265,4 +265,23 @@ TEXT runtime·i386_set_ldt(SB),7,$16
 	INT	$3
 	RET
 
+TEXT runtime·sysctl(SB),7,$28
+	LEAL	arg0+0(FP), SI
+	LEAL	4(SP), DI
+	CLD
+	MOVSL			// arg 1 - name
+	MOVSL			// arg 2 - namelen
+	MOVSL			// arg 3 - oldp
+	MOVSL			// arg 4 - oldlenp
+	MOVSL			// arg 5 - newp
+	MOVSL			// arg 6 - newlen
+	MOVL	$202, AX		// sys___sysctl
+	INT	$0x80
+	JCC	3(PC)
+	NEGL	AX
+	RET
+	MOVL	$0, AX
+	RET
+
+
 GLOBL runtime·tlsoffset(SB),$4

i386アーキテクチャ向けにruntime·sysctlのアセンブリ実装が追加されました。システムコール番号202sys___sysctl)を呼び出しています。

src/pkg/runtime/sys_freebsd_amd64.s

--- a/src/pkg/runtime/sys_freebsd_amd64.s
+++ b/src/pkg/runtime/sys_freebsd_amd64.s
@@ -199,3 +199,19 @@ TEXT runtime·settls(SB),7,$8
 	JCC	2(PC)
 	CALL	runtime·notok(SB)
 	RET
+
+TEXT runtime·sysctl(SB),7,$0
+	MOVQ	8(SP), DI		// arg 1 - name
+	MOVL	16(SP), SI		// arg 2 - namelen
+	MOVQ	24(SP), DX		// arg 3 - oldp
+	MOVQ	32(SP), R10		// arg 4 - oldlenp
+	MOVQ	40(SP), R8		// arg 5 - newp
+	MOVQ	48(SP), R9		// arg 6 - newlen
+	MOVQ	$202, AX		// sys___sysctl
+	SYSCALL
+	JCC 3(PC)
+	NEGL	AX
+	RET
+	MOVL	$0, AX
+	RET
+

amd64アーキテクチャ向けにruntime·sysctlのアセンブリ実装が追加されました。こちらもシステムコール番号202sys___sysctl)を呼び出しています。

src/pkg/runtime/thread_freebsd.c

--- a/src/pkg/runtime/thread_freebsd.c
+++ b/src/pkg/runtime/thread_freebsd.c
@@ -9,6 +9,30 @@
 extern SigTab runtime·sigtab[];
 extern int32 runtime·sys_umtx_op(uint32*, int32, uint32, void*, void*);
 
+// From FreeBSD's <sys/sysctl.h>
+#define	CTL_HW	6
+#define	HW_NCPU	3
+
+static int32
+getncpu(void)
+{
+	uint32 mib[2];
+	uint32 out;
+	int32 ret;
+	uintptr nout;
+
+	// Fetch hw.ncpu via sysctl.
+	mib[0] = CTL_HW;
+	mib[1] = HW_NCPU;
+	nout = sizeof out;
+	out = 0;
+	ret = runtime·sysctl(mib, 2, (byte*)&out, &nout, nil, 0);
+	if(ret >= 0)
+		return out;
+	else
+		return 1;
+}
+
 // FreeBSD's umtx_op syscall is effectively the same as Linux's futex, and
 // thus the code is largely similar. See linux/thread.c and lock_futex.c for comments.
 
@@ -81,6 +105,7 @@ runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))\n void
 runtime·osinit(void)
 {
+	runtime·ncpu = getncpu();
 }
 
 void

CTL_HWHW_NCPUの定義、getncpu関数の実装、そしてruntime·osinit内でruntime·ncpugetncpu()の戻り値を設定する処理が追加されました。

コアとなるコードの解説

このコミットの核心は、FreeBSDのsysctlシステムコールをGoランタイムから呼び出し、システムのCPUコア数を取得するメカニズムを構築した点にあります。

  1. runtime·sysctlアセンブリ関数:

    • Goランタイムは、OSの低レベル機能にアクセスするために、アセンブリ言語で書かれた関数を使用します。runtime·sysctlは、FreeBSDのsys___sysctlシステムコールを直接呼び出すためのラッパーです。
    • i386とamd64の両アーキテクチャで実装されており、それぞれがシステムコールを呼び出すための適切なレジスタ設定と命令(INT $0x80またはSYSCALL)を使用しています。
    • システムコールが成功した場合は0を返し、失敗した場合は負の値を返します。Goの規約では、システムコールエラーは負の値で表現されることが多いです。
  2. getncpu C関数:

    • この関数は、sysctlシステムコールを呼び出すための高レベルなインターフェースを提供します。
    • mib配列は、sysctlに問い合わせる情報のパスを指定します。CTL_HWはハードウェア関連の情報を、HW_NCPUはその中のCPU数を意味します。
    • runtime·sysctlを呼び出すことで、カーネルからhw.ncpuの値を取得し、out変数に格納します。
    • エラーハンドリングも含まれており、sysctl呼び出しが失敗した場合は、安全のためにデフォルト値の1を返します。これは、CPU数が取得できない場合でも、Goランタイムが少なくとも1つのCPUコアがあるものとして動作を継続できるようにするためです。
  3. runtime·osinitでの統合:

    • runtime·osinitは、Goプログラムが起動し、ランタイムが初期化される際に一度だけ呼び出される関数です。
    • この関数内でgetncpu()を呼び出し、その結果をグローバル変数runtime·ncpuに代入することで、GoランタイムはFreeBSDシステムが持つ実際のCPUコア数を認識できるようになります。
    • このruntime·ncpuの値は、Goのスケジューラがゴルーチンを効率的にOSスレッドに割り当て、並行処理の度合いを最適化するために利用されます。

この変更により、FreeBSD上でのGoプログラムは、システムのCPUリソースをより適切に利用し、パフォーマンスが向上することが期待されます。

関連リンク

  • Go言語のランタイムとスケジューラに関する公式ドキュメントやブログ記事
  • FreeBSDのsysctl(3)マニュアルページ
  • FreeBSDのsysctl(8)コマンドに関する情報

参考にした情報源リンク