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

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

このコミットは、GoランタイムにおけるCgoコールバックの内部実装に関する変更です。具体的には、以前のコミットで導入されたreflect.callの使用を一時的に元に戻し、runtime.newstackcallを使用するように変更しています。これは、bug 6051として報告された問題を修正するための部分的なロールバックです。

コミット

commit 034d5fcc30ff97350d16df65d403cfcc8ac5d22b
Author: Keith Randall <khr@golang.org>
Date:   Mon Aug 5 17:53:08 2013 -0700

    runtime: Use old reflect.call implementation from cgo.
    
    Basically a partial rollback of 12053043 until I can
    figure out what is really going on.
    Fixes bug 6051.
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/12496043

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

https://github.com/golang/go/commit/034d5fcc30ff97350d16df65d403cfcc8ac5d22b

元コミット内容

このコミットは、コミットハッシュ12053043(完全なハッシュは不明ですが、このコミットメッセージで参照されています)の部分的なロールバックです。元のコミット12053043では、src/pkg/runtime/cgocall.c内のruntime·cgocallbackg1関数において、Cgoコールバックの呼び出しメカニズムがruntime·newstackcallからreflect·callに変更されたと推測されます。また、misc/cgo/test/callback.goのテストコードから"runtime.call16"という文字列が削除されたことから、runtime.call16という内部関数がreflect.callに置き換えられたか、あるいはその関連で不要になった可能性も示唆されます。

変更の背景

このコミットの主な背景は、以前のコミット12053043によって導入された変更が原因で発生したbug 6051を修正することです。12053043では、Cgoコールバックの処理において、より汎用的なreflect.callを使用するように変更されました。しかし、この変更が予期せぬ問題(パフォーマンスの低下、クラッシュ、またはその他のランタイムの不安定性など)を引き起こしたため、一時的な対策として、以前のruntime.newstackcallを使用する実装に戻すことになりました。コミットメッセージにある「Basically a partial rollback of 12053043 until I can figure out what is really going on.」という記述から、この変更が根本的な原因究明と解決までの暫定的な措置であることがわかります。

前提知識の解説

Goのreflectパッケージとreflect.Call()

Go言語のreflectパッケージは、プログラムの実行時に型情報を検査したり、変数の値を動的に操作したり、メソッドを呼び出したりするための機能を提供します。reflect.Call()は、このパッケージの主要な機能の一つで、reflect.Valueとして表現された関数やメソッドを動的に呼び出すために使用されます。

  • 動的呼び出し: 通常のGoの関数呼び出しはコンパイル時に解決されますが、reflect.Call()を使用すると、実行時にどの関数を呼び出すかを決定できます。これは、汎用的なシリアライゼーション/デシリアライゼーション、ORM、依存性注入フレームワークなど、高度に汎用的なコードを記述する際に非常に強力な機能です。
  • オーバーヘッド: reflect.Call()は、動的な型チェックやメソッドルックアップを伴うため、直接的な関数呼び出しに比べてかなりのオーバーヘッドが発生します。これは、パフォーマンスがクリティカルなパスではボトルネックとなる可能性があります。

GoのCgo

Cgoは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリをGoから利用したり、Goの機能をCのアプリケーションに組み込んだりすることが可能になります。

  • Cgoコールバック: Cgoの重要な機能の一つに、CコードからGoの関数を呼び出す「コールバック」があります。これは、Cライブラリが特定のイベント発生時にGoのハンドラを呼び出すようなシナリオで利用されます。このコールバックメカニズムは、GoランタイムとCランタイム間のスタックやコンテキストの切り替えを伴うため、非常に複雑でパフォーマンスに敏感な領域です。

Goランタイムのスタック管理とnewstackcall

Goのgoroutineは、動的にサイズが変更されるスタックを持っています。goroutineが関数呼び出しなどによってスタック領域を使い果たしそうになると、Goランタイムは自動的にスタックを拡張します。

  • newstackcall: newstackcall(またはmorestacknewstackなどの関連する内部関数)は、Goランタイムの内部関数であり、goroutineのスタック管理、特にスタックの拡張プロセスに関連しています。これはユーザーが直接呼び出す関数ではなく、Goスケジューラとメモリ管理システムの一部として、アセンブリコードなどで実装されることが多い低レベルなコンポーネントです。スタックの拡張は、新しいより大きなスタックセグメントを割り当て、古いスタックの内容をコピーするプロセスを伴います。

runtime.cgocallbackg1

runtime.cgocallbackg1は、Goランタイムのsrc/pkg/runtime/cgocall.cファイルに存在する内部関数です。この関数は、CコードからGoの関数がコールバックとして呼び出された際に、そのGo関数を実行するための重要な役割を担っています。CとGoのスタックフレームの切り替えや、引数の受け渡しなど、Cgoコールバックの複雑な処理の中心となる部分です。

技術的詳細

このコミットの技術的詳細は、reflect.Call()が持つ動的な性質と、Cgoコールバックというパフォーマンスが重要なランタイムパスの特性との間のミスマッチに起因すると考えられます。

  1. reflect.Call()のオーバーヘッド: reflect.Call()は、実行時に呼び出す関数を解決し、引数をreflect.Valueにラップし、結果をreflect.Valueとして返すという一連の処理を行います。このプロセスは、通常のコンパイル時に解決される関数呼び出しに比べて、かなりのCPUサイクルを消費します。Cgoコールバックは、外部のCコードから頻繁に呼び出される可能性があるため、このオーバーヘッドがシステム全体のパフォーマンスに悪影響を与えたり、レイテンシを増加させたりする原因となった可能性があります。

  2. スタック管理との相互作用: reflect.Call()は、Goのスタックフレーム上で動作しますが、CgoコールバックはCスタックとGoスタックの間で頻繁にコンテキストを切り替えます。reflect.Call()が内部的にどのようにスタックを操作するか、あるいはスタックの成長メカニズム(newstackcallなど)とどのように相互作用するかが、特定の条件下で競合状態や不正なスタック状態を引き起こし、bug 6051のようなクラッシュやデッドロックの原因となった可能性も考えられます。

  3. bug 6051の性質: コミットメッセージからはbug 6051の具体的な内容は不明ですが、reflect.callの使用を元に戻すことで修正されたことから、以下のような問題が考えられます。

    • パフォーマンスの著しい劣化: Cgoコールバックが頻繁に発生するアプリケーションで、reflect.callのオーバーヘッドが許容できないレベルに達した。
    • ランタイムクラッシュ: reflect.callとCgoのスタック切り替えメカニズムの間の相互作用にバグがあり、不正なメモリアクセスやスタックオーバーフローを引き起こした。
    • デッドロックや競合状態: 複数のCgoコールバックが同時に発生した場合に、reflect.callの内部処理がランタイムのロック機構と競合し、デッドロックを引き起こした。
    • 不正確なスタックトレース: reflect.callがスタックトレースの生成に影響を与え、デバッグを困難にした。
  4. 「部分的なロールバック」の理由: コミットメッセージの「until I can figure out what is really going on.」という記述は、開発者が問題の根本原因をまだ特定できていないことを示唆しています。reflect.callをCgoコールバックに導入することには、おそらく何らかの設計上の利点(例えば、コードの汎用性向上や将来的な機能拡張の容易さ)があったと考えられます。そのため、一時的に安定した状態に戻しつつ、根本的な解決策を模索するための時間稼ぎとして、この部分的なロールバックが行われました。

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

このコミットによるコードの変更は以下の2つのファイルにわたります。

  1. misc/cgo/test/callback.go:

    --- a/misc/cgo/test/callback.go
    +++ b/misc/cgo/test/callback.go
    @@ -151,7 +151,6 @@ func testCallbackCallers(t *testing.T) {
     	n := 0
     	name := []string{
     		"test.goCallback",
    -		"runtime.call16",
     		"runtime.cgocallbackg1",
     		"runtime.cgocallbackg",
     		"runtime.cgocallback_gofunc",
    

    この変更は、テストコード内の期待されるスタックトレースのリストから"runtime.call16"という文字列を削除しています。これは、runtime.call16がもはやCgoコールバックのスタックトレースに現れないことを示唆しており、おそらくreflect.callへの変更によってその役割が置き換えられたか、あるいは不要になったためと考えられます。今回のロールバックによって、このテストの期待値も元に戻す必要があったのかもしれません。

  2. src/pkg/runtime/cgocall.c:

    --- a/src/pkg/runtime/cgocall.c
    +++ b/src/pkg/runtime/cgocall.c
    @@ -295,7 +295,7 @@ runtime·cgocallbackg1(void)
     
     	// Invoke callback.
     	cb = CBARGS;
    -	reflect·call(cb->fn, cb->arg, cb->argsize);\n
    +	runtime·newstackcall(cb->fn, cb->arg, cb->argsize);\n
     
     	if(raceenabled && !m->racecall)
     		runtime·racereleasemerge(&cgosync);
    

    この変更がこのコミットの核心です。runtime·cgocallbackg1関数内で、Cgoコールバックを呼び出す際に使用されていたreflect·callが、以前のruntime·newstackcallに戻されています。

コアとなるコードの解説

src/pkg/runtime/cgocall.cの変更は、Cgoコールバックの実行パスにおける重要な変更です。

  • reflect·call(cb->fn, cb->arg, cb->argsize); から runtime·newstackcall(cb->fn, cb->arg, cb->argsize); への変更:
    • cb->fnはコールバックとして呼び出されるGo関数のポインタ、cb->argはその引数、cb->argsizeは引数のサイズをそれぞれ表します。
    • 元のコミット12053043では、Go関数を動的に呼び出すためにreflect·callが使用されていました。これは、Goのreflectパッケージの内部的な実装に相当し、動的な関数呼び出しの柔軟性を提供します。
    • しかし、このコミットでは、その呼び出しをruntime·newstackcallに戻しています。runtime·newstackcallは、Goランタイムのより低レベルな関数であり、主にスタックの管理と、新しいスタックフレーム上での関数呼び出しを効率的に行うために使用されます。これは、reflect.Call()のような動的なオーバーヘッドを伴わず、より直接的な関数呼び出しに近い効率を提供します。

この変更は、Cgoコールバックの実行において、動的な柔軟性よりも安定性とパフォーマンスを優先したことを明確に示しています。reflect.callの導入がbug 6051を引き起こしたため、問題の根本原因が特定されるまでの間、より堅牢で実績のあるruntime.newstackcallのパスに戻すことで、システムの安定性を確保しようとしています。

misc/cgo/test/callback.goの変更は、このランタイムの変更に伴うテストの調整です。runtime.call16がスタックトレースから消えたことを反映しており、これはreflect.callの導入によってruntime.call16が不要になったか、あるいはその機能がreflect.callに統合されたことを示唆しています。今回のロールバックによっても、このテストの期待値は変更されていないため、runtime.call16が完全に廃止されたか、あるいはこのテストケースではその存在が重要でなくなったと考えられます。

関連リンク

参考にした情報源リンク