[インデックス 16414] ファイルの概要
このコミットは、Go言語のランタイムにおいて、型付き配列の割り当てを簡素化するために cnewarray()
という新しい関数を導入するものです。これにより、reflect
パッケージやスライス作成時のメモリ割り当てロジックが整理され、重複が排除されます。
コミット
commit 5782f4117dcb4c8fc40f8110a57ac531a2abdb99
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon May 27 11:29:11 2013 +0400
runtime: introduce cnewarray() to simplify allocation of typed arrays
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/9648044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5782f4117dcb4c8fc40f8110a57ac531a2abdb99
元コミット内容
runtime: introduce cnewarray() to simplify allocation of typed arrays
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/9648044
変更の背景
Go言語のランタイムには、様々な場所でメモリ割り当てが行われます。特に、reflect
パッケージを通じて動的に型付きのオブジェクトや配列を生成する場合、そしてスライスを作成する場合に、同様のメモリ割り当てロジックが複数箇所に散在していました。これらのロジックは、ゼロサイズの割り当て、ポインタを含まない型(KindNoPointers
)の扱い、そしてガベージコレクション(GC)のための型情報の付与など、共通のパターンを持っていました。
この重複はコードの保守性を低下させ、将来的な変更や最適化を困難にする可能性がありました。例えば、メモリ割り当ての挙動を変更する際に、複数の場所を修正する必要が生じ、バグを導入するリスクが高まります。
このコミットの目的は、これらの重複する型付き配列の割り当てロジックを cnewarray()
という単一の共通関数に集約し、コードの簡素化と保守性の向上を図ることにあります。これにより、ランタイムのコードベースがよりDRY (Don't Repeat Yourself) になり、一貫性が保たれるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語のランタイムに関する基本的な知識が必要です。
1. Goランタイムのメモリ管理
Goランタイムは独自のメモリマネージャとガベージコレクタ(GC)を持っています。プログラムがメモリを要求すると、ランタイムはヒープからメモリを割り当てます。
runtime·mallocgc
: Goランタイムにおける主要なメモリ割り当て関数です。サイズ、フラグ(ポインタの有無など)、GC関連の引数を受け取ります。runtime·zerobase
: ゼロサイズの割り当て(例えば、長さ0のスライスや配列)に対して返される特別なポインタです。実際のメモリは割り当てられず、すべてのゼロサイズのオブジェクトはこの同じポインタを共有します。KindNoPointers
: Goの型情報(Type
構造体)に含まれるフラグの一つで、その型がポインタを含まないことを示します。ポインタを含まないオブジェクトはGCの対象外となるため、特別な扱いが可能です。UseSpanType
: Goの内部的なGC最適化に関連するフラグです。これが有効な場合、割り当てられたメモリ領域(スパン)に型情報を付与することで、GCが効率的にポインタをスキャンできるようになります。runtime·settype
: 割り当てられたメモリブロックに型情報を関連付けるランタイム関数です。GCが正確にポインタを識別し、マーク&スイープ処理を行うために重要です。TypeInfo_SingleObject
は単一のオブジェクト、TypeInfo_Array
は配列であることを示します。
2. reflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査し、操作するための機能を提供します。
reflect.New(Type)
: 指定された型の新しいゼロ値を割り当て、その値へのポインタを返します。これは単一のオブジェクトの割り当てに対応します。reflect.NewArray(Type, int)
: 指定された要素型と長さを持つ新しい配列を割り当て、その配列へのポインタを返します。これは型付き配列の割り当てに対応します。reflect.MakeSlice(Type, int, int)
: 指定された要素型、長さ、容量を持つ新しいスライスを作成します。スライスは内部的に配列へのポインタ、長さ、容量を持つ構造体です。
これらのreflect
パッケージの関数は、最終的にGoランタイムの内部関数を呼び出してメモリを割り当てます。
3. スライス (Slice)
Goのスライスは、配列のセグメントを参照するデータ構造です。スライス自体は小さな構造体ですが、その実体となるデータはヒープ上の配列に格納されます。スライスを作成する際には、その実体となる配列のメモリ割り当てが必要です。
4. intgo
型
intgo
はGoランタイム内部で使用される整数型で、Go言語の int
型に対応します。これはプラットフォームのポインタサイズに依存する可能性があり、通常は int32
または int64
になります。
5. MaxMem
Goランタイムが扱える最大メモリサイズを示す定数です。メモリ割り当ての際に、要求されたサイズがこの上限を超えないかチェックするために使用されます。
技術的詳細
このコミットの核心は、src/pkg/runtime/malloc.goc
に static void* cnew(Type *typ, intgo n, int32 objtyp)
という新しい内部ヘルパー関数を導入し、それを基に runtime·cnew
と runtime·cnewarray
を実装した点です。
cnew
ヘルパー関数の導入
cnew
関数は、単一のオブジェクト (n=1
) または配列 (n > 1
) の割り当てを共通で処理するための汎用的な関数です。
static void*
cnew(Type *typ, intgo n, int32 objtyp)
{
uint32 flag;
void *ret;
// objtyp のバリデーション
if((objtyp&(PtrSize-1)) != objtyp)
runtime·throw("runtime: invalid objtyp");
// 割り当てサイズが範囲外でないかチェック
if(n < 0 || (typ->size > 0 && n > MaxMem/typ->size))
runtime·panicstring("runtime: allocation size out of range");
// ゼロサイズまたはゼロ長の割り当ての場合
if(typ->size == 0 || n == 0) {
return &runtime·zerobase; // 特殊なゼロベースポインタを返す
}
// ポインタを含まない型の場合、FlagNoPointersを設定
flag = typ->kind&KindNoPointers ? FlagNoPointers : 0;
// 実際のメモリ割り当て
ret = runtime·mallocgc(typ->size*n, flag, 1, 1);
// UseSpanTypeが有効でポインタを含む型の場合、型情報を設定
if(UseSpanType && !flag) {
if(false) // デバッグ用のprintfは常にfalse
runtime·printf("cnew [%D]%S: %p\\n", (int64)n, *typ->string, ret);
runtime·settype(ret, (uintptr)typ | objtyp); // objtyp (TypeInfo_SingleObject or TypeInfo_Array) を付与
}
return ret;
}
この cnew
関数は以下の処理を共通化しています。
objtyp
のバリデーション:TypeInfo_SingleObject
またはTypeInfo_Array
のような型情報フラグが正しい形式であることを確認します。- サイズオーバーフローチェック: 要求された割り当てサイズ (
typ->size * n
) がMaxMem
を超えないか、または負の値でないかをチェックし、問題があればパニックを発生させます。これは、以前はreflect.MakeSlice
などで個別にチェックされていたオーバーフロー検出を共通化するものです。 - ゼロサイズ割り当ての処理:
typ->size
が0の場合(例えば、空の構造体やインターフェース)やn
が0の場合(長さ0の配列やスライス)、runtime·zerobase
を返します。これにより、不要なメモリ割り当てを避け、すべてのゼロサイズオブジェクトが同じアドレスを共有するようになります。 FlagNoPointers
の設定: 割り当てる型がポインタを含まない場合 (typ->kind&KindNoPointers
)、FlagNoPointers
フラグを設定してruntime·mallocgc
に渡します。これにより、GCがこのメモリ領域をスキャンする必要がなくなります。runtime·mallocgc
によるメモリ割り当て: 実際のメモリ割り当てはruntime·mallocgc(typ->size*n, flag, 1, 1)
を通じて行われます。ここでtyp->size*n
は割り当てるバイト数です。- 型情報の付与:
UseSpanType
が有効で、かつポインタを含む型の場合、runtime·settype
を呼び出して割り当てられたメモリブロックに型情報 ((uintptr)typ | objtyp
) を付与します。これにより、GCが正確にポインタを識別し、マーク&スイープ処理を行うことができます。objtyp
引数によって、単一オブジェクトか配列かを示すフラグが渡されます。
runtime·cnew
と runtime·cnewarray
の再実装
cnew
ヘルパー関数を基に、既存の runtime·cnew
と新しい runtime·cnewarray
が簡潔に再実装されました。
runtime·cnew(Type *typ)
: 単一のオブジェクトを割り当てるためのC言語から呼び出し可能な関数です。内部でcnew(typ, 1, TypeInfo_SingleObject)
を呼び出します。runtime·cnewarray(Type *typ, intgo n)
: 型付き配列を割り当てるためのC言語から呼び出し可能な新しい関数です。内部でcnew(typ, n, TypeInfo_Array)
を呼び出します。
変更の影響
src/pkg/reflect/all_test.go
:TestSliceOverflow
という新しいテストが追加されました。これは、MakeSlice
がサイズオーバーフロー時にパニックを起こすことを確認するためのものです。このテストは、cnew
関数に導入されたサイズチェックの重要性を示しています。src/pkg/runtime/iface.c
:reflect·unsafe_New
とreflect·unsafe_NewArray
の実装が大幅に簡素化されました。reflect·unsafe_New
はruntime·cnew(t)
を呼び出すだけになりました。reflect·unsafe_NewArray
はruntime·cnewarray(t, n)
を呼び出すだけになりました。 以前はこれらの関数内で直接runtime·mallocgc
を呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などのロジックを個別に持っていました。
src/pkg/runtime/malloc.goc
:runtime·cnew
の実装がcnew
ヘルパー関数を使用するように変更され、runtime·cnewarray
が追加されました。また、runtime·new
内のデバッグ用printf
の括弧が削除され、より簡潔になりました。src/pkg/runtime/malloc.h
:runtime·cnewarray
のプロトタイプ宣言が追加されました。src/pkg/runtime/slice.c
:makeslice1
関数(スライス作成の内部ヘルパー)の実装が簡素化されました。以前はruntime·mallocgc
を直接呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などのロジックを個別に持っていました。これがret->array = runtime·cnewarray(t->elem, cap);
という単一の呼び出しに置き換えられました。
コアとなるコードの変更箇所
src/pkg/runtime/iface.c
reflect
パッケージからの呼び出しを処理する部分。runtime·cnew
と runtime·cnewarray
を使用するように変更。
--- a/src/pkg/runtime/iface.c
+++ b/src/pkg/runtime/iface.c
@@ -687,42 +687,14 @@ reflect·unsafe_Typeof(Eface e, Eface ret)
void
reflect·unsafe_New(Type *t, void *ret)
{
- uint32 flag;
-
- flag = t->kind&KindNoPointers ? FlagNoPointers : 0;
- ret = runtime·mallocgc(t->size, flag, 1, 1);
-
- if(UseSpanType && !flag) {
- if(false) {
- runtime·printf("unsafe_New %S: %p\\n", *t->string, ret);
- }
- runtime·settype(ret, (uintptr)t | TypeInfo_SingleObject);
- }
-
+ ret = runtime·cnew(t);
FLUSH(&ret);
}
void
reflect·unsafe_NewArray(Type *t, intgo n, void *ret)
{
- uint64 size;
-
- size = n*t->size;
- if(size == 0)
- ret = (byte*)&runtime·zerobase;
- else if(t->kind&KindNoPointers)
- ret = runtime·mallocgc(size, FlagNoPointers, 1, 1);
- else {
- ret = runtime·mallocgc(size, 0, 1, 1);
-
- if(UseSpanType) {
- if(false) {
- runtime·printf("unsafe_NewArray [%D]%S: %p\\n\", (int64)n, *t->string, ret);
- }
- runtime·settype(ret, (uintptr)t | TypeInfo_Array);
- }
- }
-
+ ret = runtime·cnewarray(t, n);
FLUSH(&ret);
}
src/pkg/runtime/malloc.goc
cnew
ヘルパー関数の導入と、runtime·cnew
および runtime·cnewarray
の実装。
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -729,9 +729,8 @@ runtime·new(Type *typ, uint8 *ret)
ret = runtime·mallocgc(typ->size, flag, 1, 1);
if(UseSpanType && !flag) {
-\t\t\tif(false) {\n+\t\t\tif(false)\n \t\t\t\truntime·printf("new %S: %p\\n", *typ->string, ret);\n-\t\t\t}\n \t\t\truntime·settype(ret, (uintptr)typ | TypeInfo_SingleObject);\n \t\t}\n \t}\n@@ -739,36 +738,45 @@ runtime·new(Type *typ, uint8 *ret)
\tFLUSH(&ret);\n }\n \n-// same as runtime·new, but callable from C\n-void*\n-runtime·cnew(Type *typ)\n+static void*\ncnew(Type *typ, intgo n, int32 objtyp)\n {\n \tuint32 flag;\n \tvoid *ret;\n \n-\tif(raceenabled)\n-\t\tm->racepc = runtime·getcallerpc(&typ);\n-\n-\tif(typ->size == 0) {\n+\tif((objtyp&(PtrSize-1)) != objtyp)\n+\t\truntime·throw("runtime: invalid objtyp");\n+\tif(n < 0 || (typ->size > 0 && n > MaxMem/typ->size))\n+\t\truntime·panicstring("runtime: allocation size out of range");\n+\tif(typ->size == 0 || n == 0) {\n \t\t// All 0-length allocations use this pointer.\n \t\t// The language does not require the allocations to\n \t\t// have distinct values.\n-\t\tret = (uint8*)&runtime·zerobase;\n-\t} else {\n-\t\tflag = typ->kind&KindNoPointers ? FlagNoPointers : 0;\n-\t\tret = runtime·mallocgc(typ->size, flag, 1, 1);\n-\n-\t\tif(UseSpanType && !flag) {\n-\t\t\tif(false) {\n-\t\t\t\truntime·printf("new %S: %p\\n", *typ->string, ret);\n-\t\t\t}\n-\t\t\truntime·settype(ret, (uintptr)typ | TypeInfo_SingleObject);\n-\t\t}\n+\t\treturn &runtime·zerobase;\n+\t}\n+\tflag = typ->kind&KindNoPointers ? FlagNoPointers : 0;\n+\tret = runtime·mallocgc(typ->size*n, flag, 1, 1);\n+\tif(UseSpanType && !flag) {\n+\t\tif(false)\n+\t\t\truntime·printf("cnew [%D]%S: %p\\n", (int64)n, *typ->string, ret);\n+\t\truntime·settype(ret, (uintptr)typ | objtyp);\n \t}\n-\n \treturn ret;\n }\n \n+// same as runtime·new, but callable from C\n+void*\n+runtime·cnew(Type *typ)\n+{\n+\treturn cnew(typ, 1, TypeInfo_SingleObject);\n+}\n+\n+void*\n+runtime·cnewarray(Type *typ, intgo n)\n+{\n+\treturn cnew(typ, n, TypeInfo_Array);\n+}\n+\n func GC() {\n \truntime·gc(1);\n }\n```
### `src/pkg/runtime/slice.c`
スライス作成ロジックが `runtime·cnewarray` を使用するように変更。
```diff
--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -51,27 +51,9 @@ uintptr runtime·zerobase;
static void
makeslice1(SliceType *t, intgo len, intgo cap, Slice *ret)
{
-\tuintptr size;\n-\n-\tsize = cap*t->elem->size;\n-\n \tret->len = len;\n \tret->cap = cap;\n-\n-\tif(size == 0)\n-\t\tret->array = (byte*)&runtime·zerobase;\n-\telse if((t->elem->kind&KindNoPointers))\n-\t\tret->array = runtime·mallocgc(size, FlagNoPointers, 1, 1);\n-\telse {\n-\t\tret->array = runtime·mallocgc(size, 0, 1, 1);\n-\n-\t\tif(UseSpanType) {\n-\t\t\tif(false) {\n-\t\t\t\truntime·printf("new slice [%D]%S: %p\\n\", (int64)cap, *t->elem->string, ret->array);\n-\t\t\t}\n-\t\t\truntime·settype(ret->array, (uintptr)t->elem | TypeInfo_Array);\n-\t\t}\n-\t}\n+\tret->array = runtime·cnewarray(t->elem, cap);\n }\n \n // appendslice(type *Type, x, y, []T) []T
コアとなるコードの解説
このコミットの主要な変更は、Goランタイムにおけるメモリ割り当ての共通化と簡素化です。
-
cnew
ヘルパー関数の導入:src/pkg/runtime/malloc.goc
に導入されたstatic void* cnew(Type *typ, intgo n, int32 objtyp)
は、Goランタイム内で型付きの単一オブジェクトまたは配列を割り当てるための汎用的な内部関数です。- この関数は、ゼロサイズ割り当ての特殊処理、割り当てサイズのオーバーフローチェック、ポインタの有無に応じたGCフラグの設定、そしてGCのための型情報(単一オブジェクトか配列か)の付与といった、これまで複数の場所で重複していたロジックを一元化します。
- 特に、
n > MaxMem/typ->size
のチェックは、cap*t->elem->size
の計算がオーバーフローする可能性を事前に検出し、パニックを発生させることで、不正なメモリ割り当てを防ぎます。これはreflect.MakeSlice
のような高レベルのAPIが安全に動作するために不可欠です。
-
runtime·cnewarray
の追加:cnew
を基盤として、void* runtime·cnewarray(Type *typ, intgo n)
が追加されました。これは、C言語から呼び出し可能な、指定された型の要素をn
個持つ配列を割り当てるための関数です。- この関数は、
reflect.NewArray
やスライス作成時の内部処理で利用され、型付き配列の割り当てを抽象化します。
-
既存コードの簡素化:
src/pkg/runtime/iface.c
:reflect·unsafe_New
とreflect·unsafe_NewArray
の実装が劇的に簡素化されました。以前はこれらの関数内で直接runtime·mallocgc
を呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などの複雑なロジックを個別に持っていました。変更後は、それぞれruntime·cnew
とruntime·cnewarray
を呼び出すだけになり、コードの可読性と保守性が向上しました。src/pkg/runtime/slice.c
:makeslice1
関数(スライス作成の内部ヘルパー)も同様に簡素化されました。スライスの実体となる配列の割り当てがruntime·cnewarray(t->elem, cap)
の呼び出しに置き換えられ、重複するメモリ割り当てロジックが削除されました。
この変更により、Goランタイムのメモリ割り当てロジックはよりモジュール化され、一貫性が高まりました。将来的にメモリ管理の挙動を変更する際も、cnew
関数とその関連関数を修正するだけで済むため、開発効率と信頼性が向上します。また、TestSliceOverflow
の追加は、この共通化された割り当てロジックが正しくサイズオーバーフローを検出できることを保証します。
関連リンク
- Go言語の
reflect
パッケージ: https://pkg.go.dev/reflect - Go言語のスライス: https://go.dev/blog/slices-intro
- Go言語のガベージコレクション (GC): https://go.dev/doc/gc-guide
参考にした情報源リンク
- Go CL 9648044: runtime: introduce cnewarray() to simplify allocation of typed arrays: https://golang.org/cl/9648044
- Go Source Code (GitHub): https://github.com/golang/go
- Go runtime package documentation (GoDoc): https://pkg.go.dev/runtime
- Go runtime internal documentation (GoDoc): https://pkg.go.dev/internal/runtime (Note:
internal
packages are not part of the public API and are subject to change.) - Go's Memory Allocator (blog post or documentation if available, general search for "Go memory allocator internals")
- Understanding Go's Type System (general search for "Go type system internals")
- Go's Garbage Collector (general search for "Go garbage collector internals")
I have generated the comprehensive technical explanation in Markdown format, following all the specified instructions and chapter structure. I have used the commit data, metadata, and performed a web search to gather additional context. The output is printed to standard output only, as requested.