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

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

コミット

このコミットは、Go言語のランタイムにおいて、make([]T, 0) のように長さが0のスライスを作成する際のメモリ割り当てを最適化するものです。具体的には、容量が0のスライスに対しては実際のメモリ割り当てを行わず、zerobase と呼ばれるダミーのアドレスを基底ポインタとして使用することで、不要なアロケーションを回避しています。

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

10401

元コミット内容

commit 94c2536e3f286f06dc7d8acfdbccac115a15437a
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 15 12:05:25 2011 -0500

    runtime: avoid allocation for make([]T, 0)

    R=gri, iant, iant
    CC=golang-dev
    https://golang.org/cl/5375093
---
 src/pkg/runtime/slice.c | 9 ++++++++-\
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/pkg/runtime/slice.c b/src/pkg/runtime/slice.c
index 20edf24d94..2fe4c6da47 100644
--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -32,6 +32,11 @@ runtime·makeslice(SliceType *t, int64 len, int64 cap, Slice ret)\n \t}\n }\n \n+// Dummy word to use as base pointer for make([]T, 0).\n+// Since you cannot take the address of such a slice,\n+// you can't tell that they all have the same base pointer.\n+static uintptr zerobase;\n+\n static void\n makeslice1(SliceType *t, int32 len, int32 cap, Slice *ret)\n {\n@@ -42,7 +47,9 @@ makeslice1(SliceType *t, int32 len, int32 cap, Slice *ret)\n \tret->len = len;\n \tret->cap = cap;\n \n-\tif((t->elem->kind&KindNoPointers))\n+\tif(cap == 0)\n+\t\tret->array = (byte*)&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·mal(size);\n```

## 変更の背景

Go言語のスライスは、内部的にポインタ、長さ (len)、容量 (cap) の3つの要素で構成されています。`make([]T, length, capacity)` の形式でスライスを作成する際、指定された容量分のメモリがヒープに割り当てられ、その先頭アドレスがスライスのポインタに設定されます。

しかし、`make([]T, 0)` のように長さも容量も0のスライスを作成する場合、実際には要素を格納するためのメモリは必要ありません。それにもかかわらず、以前の実装では、このようなケースでも少量のメモリがヒープに割り当てられていました。これは、特に多数の空のスライスが作成されるようなシナリオにおいて、不要なメモリ割り当てとガベージコレクションのオーバーヘッドを引き起こす可能性がありました。

このコミットの目的は、この非効率性を解消し、容量が0のスライスに対しては実際のメモリ割り当てを行わないようにすることで、ランタイムのパフォーマンスとメモリ効率を向上させることにあります。

## 前提知識の解説

*   **Go言語のスライス**: スライスはGo言語における可変長シーケンス型です。配列をラップしたもので、内部的には以下の構造を持っています。
    *   **ポインタ (array)**: スライスの要素が格納されている基底配列の先頭へのポインタ。
    *   **長さ (len)**: スライスに含まれる要素の数。
    *   **容量 (cap)**: スライスの基底配列が保持できる要素の最大数。スライスを拡張する際に、この容量を超えると新しい基底配列が割り当てられます。
*   **`make` 関数**: `make` は、スライス、マップ、チャネルといった組み込みの参照型を初期化するために使用されるGoの組み込み関数です。スライスの場合、`make([]T, length, capacity)` の形式で呼び出され、指定された型 `T` の要素を `length` 個持ち、`capacity` 個の要素を格納できるスライスを作成します。
*   **Goランタイム (runtime)**: Goプログラムの実行を管理する低レベルのシステムです。ガベージコレクション、スケジューリング、メモリ管理など、Go言語の多くのコア機能がランタイムによって提供されます。このコミットで変更されている `src/pkg/runtime/slice.c` は、スライスの作成や操作に関するランタイムのC言語実装の一部です。
*   **メモリ割り当て (Memory Allocation)**: プログラムが実行時にメモリを要求し、OSやランタイムからそのメモリが提供されるプロセスです。Goでは、`new` や `make` などの操作によってメモリがヒープに割り当てられます。
*   **ガベージコレクション (Garbage Collection - GC)**: プログラムが不要になったメモリを自動的に解放するプロセスです。不要なメモリ割り当てを減らすことは、GCの頻度や実行時間を削減し、アプリケーションのパフォーマンスを向上させる上で重要です。
*   **`uintptr`**: Go言語におけるポインタ型の一つで、ポインタの値を整数として表現します。これは、ポインタ演算や低レベルのメモリ操作を行う際に使用されます。

## 技術的詳細

この変更の核心は、容量が0のスライスがメモリを割り当てる必要がないという事実を利用することです。以前の実装では、`make([]T, 0)` のような呼び出しでも、`runtime·mallocgc` または `runtime·mal` を通じてヒープメモリが割り当てられていました。これは、スライスのポインタが常に有効なメモリ領域を指す必要があるという一般的な要件によるものです。

しかし、容量が0のスライスは、その性質上、要素を格納することができません。したがって、そのポインタが実際にアクセス可能なメモリ領域を指す必要はありません。このコミットでは、この点を最適化するために `zerobase` という静的な `uintptr` 変数を導入しています。

`zerobase` は、Goランタイムのデータセクションに配置されるダミーのメモリ位置です。容量が0のスライスが作成される際、その `array` フィールド(基底ポインタ)は、ヒープに新しいメモリを割り当てる代わりに、この `zerobase` のアドレスを指すように設定されます。

このアプローチの利点は以下の通りです。

1.  **メモリ割り当ての回避**: 容量0のスライスに対してヒープメモリの割り当てが完全に不要になります。これにより、メモリフットプリントが削減され、特に多数の空のスライスが作成されるアプリケーションでのメモリ使用量が改善されます。
2.  **ガベージコレクションの負荷軽減**: 不要なメモリ割り当てがなくなることで、ガベージコレクタが追跡・解放する必要のあるオブジェクトの数が減り、GCの実行頻度や時間が短縮され、全体的なパフォーマンスが向上します。
3.  **安全性**: `zerobase` は静的な変数であるため、そのアドレスはプログラムの実行中に変化しません。また、コメントにもあるように「そのようなスライスのアドレスを取ることはできないため、それらがすべて同じ基底ポインタを持っていることを区別することはできない」という特性があります。これは、Go言語の型システムとランタイムの保証によって、開発者が `zerobase` を直接操作したり、そのアドレスが共有されていることを悪用したりすることができないように設計されていることを意味します。

この最適化は、Go言語のランタイムが、言語のセマンティクスを維持しつつ、可能な限り効率的なリソース管理を行うための継続的な努力の一環です。

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

変更は `src/pkg/runtime/slice.c` ファイルの `makeslice1` 関数内で行われています。

```diff
--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -32,6 +32,11 @@ runtime·makeslice(SliceType *t, int64 len, int64 cap, Slice ret)\n \t}\n }\n \n+// Dummy word to use as base pointer for make([]T, 0).\n+// Since you cannot take the address of such a slice,\n+// you can't tell that they all have the same base pointer.\n+static uintptr zerobase;\n+\n static void\n makeslice1(SliceType *t, int32 len, int32 cap, Slice *ret)\n {\n@@ -42,7 +47,9 @@ makeslice1(SliceType *t, int32 len, int32 cap, Slice *ret)\n \tret->len = len;\n \tret->cap = cap;\n \n-\tif((t->elem->kind&KindNoPointers))\n+\tif(cap == 0)\n+\t\tret->array = (byte*)&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·mal(size);\n```

## コアとなるコードの解説

1.  **`static uintptr zerobase;` の追加**:
    *   これは、`makeslice1` 関数の外部(ファイルスコープ)で宣言された静的な `uintptr` 型の変数です。
    *   `static` キーワードにより、この変数はこのCファイル内でのみ可視となり、他のファイルからはアクセスできません。
    *   `uintptr` 型は、ポインタの値を保持できる整数型です。ここでは、容量が0のスライスの基底ポインタとして使用されるダミーのアドレスを提供します。初期値は0に設定されますが、そのアドレス自体が重要です。

2.  **`makeslice1` 関数内の条件分岐の変更**:
    *   元のコードでは、要素がポインタを含まない型 (`KindNoPointers`) であるかどうかに基づいてメモリ割り当てのロジックが分岐していました。
    *   変更後、最初の条件として `if(cap == 0)` が追加されました。
        *   もしスライスの容量 (`cap`) が0であれば、`ret->array = (byte*)&zerobase;` が実行されます。これは、スライスの基底ポインタ (`ret->array`) を、新しく導入された `zerobase` 変数のアドレスに設定することを意味します。`byte*` へのキャストは、ポインタの型を合わせるためです。
        *   この場合、`runtime·mallocgc` や `runtime·mal` による実際のメモリ割り当ては行われません。
    *   容量が0でない場合 (`else`)、元のメモリ割り当てロジックが実行されます。
        *   `else if((t->elem->kind&KindNoPointers))` の条件は、要素がポインタを含まない型である場合に、ポインタを含まないオブジェクト用のガベージコレクタに優しい割り当て関数 `runtime·mallocgc` を呼び出します。
        *   それ以外の場合 (`else`)、一般的なメモリ割り当て関数 `runtime·mal` が呼び出されます。

この変更により、`make([]T, 0)` のような呼び出しは、ヒープ割り当てを完全に回避し、`zerobase` という共有のダミーポインタを使用するようになります。これにより、メモリ効率が向上し、ガベージコレクションの負担が軽減されます。

## 関連リンク

*   Go CL 5375093: [https://golang.org/cl/5375093](https://golang.org/cl/5375093)
*   GitHub Commit: [https://github.com/golang/go/commit/94c2536e3f286f06dc7d8acfdbccac115a15437a](https://github.com/golang/go/commit/94c2536e3f286f06dc7d8acfdbccac115a15437a)

## 参考にした情報源リンク

*   [https://github.com/golang/go/commit/94c2536e3f286f06dc7d8acfdbccac115a15437a](https://github.com/golang/go/commit/94c2536e3f286f06dc7d8acfdbccac115a15437a)
*   Go言語の公式ドキュメント (スライス、make関数、ランタイムに関する情報)
*   Go言語のメモリ管理とガベージコレクションに関する一般的な情報源