[インデックス 13287] ファイルの概要
このコミットは、Goランタイムのガベージコレクタの一部であるscanblock
関数とdebug_scanblock
関数において、メモリブロックの長さを表す型をint64
からuintptr
に変更するものです。この変更は、特に32ビットプラットフォーム、中でもARMアーキテクチャにおけるパフォーマンスの最適化を目的としています。
コミット
commit 09f48db3e17c71e2ab709efb760e2a305c67aace
Author: Dave Cheney <dave@cheney.net>
Date: Tue Jun 5 18:55:14 2012 +1000
runtime: use uintptr for block length in scanblock
Using an int64 for a block size doesn't make
sense on 32bit platforms but extracts a performance
penalty dealing with double word quantities on Arm.
linux/arm
benchmark old ns/op new ns/op delta
BenchmarkGobDecode 155401600 144589300 -6.96%
BenchmarkGobEncode 72772220 62460940 -14.17%
BenchmarkGzip 5822632 2604797 -55.26%
BenchmarkGunzip 326321 151721 -53.51%
benchmark old MB/s new MB/s speedup
BenchmarkGobDecode 4.94 5.31 1.07x
BenchmarkGobEncode 10.55 12.29 1.16x
R=golang-dev, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/6272047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/09f48db3e17c71e2ab709efb760e2a305c67aace
元コミット内容
Goランタイムのsrc/pkg/runtime/mgc0.c
ファイルにおいて、ガベージコレクタのscanblock
関数およびdebug_scanblock
関数が、メモリブロックの長さを表す引数n
にint64
型を使用していました。
変更の背景
この変更の主な背景は、32ビットプラットフォーム、特にARMアーキテクチャにおけるパフォーマンスの最適化です。
-
32ビットプラットフォームでの
int64
の非効率性: 32ビットシステムでは、int64
(64ビット整数)を扱う際に、CPUが一度に処理できるワードサイズ(32ビット)を超えるため、複数の命令が必要になります。これは「ダブルワード量(double word quantities)」の処理と呼ばれ、余分なオーバーヘッドが発生します。メモリブロックの長さは通常、ポインタサイズ(32ビットシステムでは32ビット)で十分表現できるため、int64
を使用することは不必要にリソースを消費し、パフォーマンスを低下させる要因となっていました。 -
ARMアーキテクチャでのパフォーマンスペナルティ: ARMプロセッサは、特に組み込みシステムやモバイルデバイスで広く使用されており、リソースが限られていることが多いです。このような環境で
int64
のような大きなデータ型を頻繁に扱うことは、顕著なパフォーマンスペナルティにつながります。コミットメッセージに記載されているベンチマーク結果は、このパフォーマンス改善の具体的な証拠です。BenchmarkGzip
やBenchmarkGunzip
で50%以上の改善が見られるなど、I/Oやデータ処理に関連する操作で大きな効果が出ています。 -
uintptr
の適切な利用:uintptr
型は、ポインタを保持するのに十分な大きさの符号なし整数型であり、プラットフォームのワードサイズに依存します(32ビットシステムでは32ビット、64ビットシステムでは64ビット)。メモリブロックの長さは本質的にアドレス空間内のオフセットやサイズを表すため、uintptr
が最も適切で効率的な型です。これにより、32ビットシステムでは32ビットのレジスタを効率的に利用できるようになり、処理速度が向上します。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
-
Goランタイム (Go Runtime): Go言語で書かれたプログラムを実行するために必要な低レベルのコード群です。これには、ガベージコレクタ、スケジューラ、プリミティブな同期メカニズムなどが含まれます。Goランタイムの多くはC言語(またはGo言語自体)で実装されており、
src/pkg/runtime/mgc0.c
はその一部です。 -
ガベージコレクタ (Garbage Collector, GC): プログラムが動的に確保したメモリのうち、もはや使用されていない(参照されていない)領域を自動的に解放するシステムです。GoのGCは並行・世代別・マーク&スイープ方式を採用しており、プログラムの実行中にバックグラウンドで動作します。
scanblock
関数は、GCがメモリブロックを走査し、到達可能なオブジェクトを特定する(マークする)プロセスの一部です。 -
uintptr
型: Go言語における組み込み型の一つで、ポインタを保持するのに十分な大きさの符号なし整数型です。そのサイズは実行環境のポインタサイズ(通常はCPUのワードサイズ)に依存します。例えば、32ビットシステムでは32ビット(4バイト)、64ビットシステムでは64ビット(8バイト)になります。uintptr
は、ポインタ演算を行う際や、C言語との相互運用でメモリアドレスを整数として扱う場合に特に有用です。 -
int64
型: 64ビット幅の符号付き整数型です。32ビットシステムでは、int64
の値を扱うために2つの32ビットレジスタを使用したり、複数のCPU命令を必要としたりするため、処理にオーバーヘッドが生じます。 -
ARMアーキテクチャ: スマートフォン、タブレット、組み込みシステムなどで広く利用されているCPUアーキテクチャです。x86アーキテクチャと比較して、一般的に消費電力が低く、命令セットがシンプルであるという特徴があります。パフォーマンス最適化においては、データ型のサイズやアライメントが特に重要になります。
-
ベンチマーク (Benchmark): ソフトウェアの性能を測定するためのテストです。このコミットでは、
ns/op
(操作あたりのナノ秒)やMB/s
(1秒あたりのメガバイト)といった指標で、変更前後の性能を比較しています。
技術的詳細
このコミットの技術的な核心は、Goランタイムのガベージコレクタにおけるメモリブロックの走査処理の最適化です。
Goのガベージコレクタは、ヒープ上のオブジェクトをマークし、到達可能なオブジェクトを特定する「マークフェーズ」を持っています。このフェーズにおいて、scanblock
関数は特定のメモリブロックを走査し、そのブロック内に含まれるポインタを識別して、それらが指すオブジェクトもマーク対象としてキューに追加します。
元の実装では、scanblock
関数とdebug_scanblock
関数が、走査対象のメモリブロックの長さ(サイズ)をint64 n
という引数で受け取っていました。
// 変更前
static void
scanblock(byte *b, int64 n)
しかし、メモリブロックの長さは、そのシステムのアドレス空間のサイズを超えることはありません。32ビットシステムでは、アドレス空間は最大4GBであり、これを表現するのにint64
は過剰です。uintptr
はポインタサイズに合わせた整数型であるため、32ビットシステムでは32ビット、64ビットシステムでは64ビットのサイズを持ちます。これにより、32ビットシステムではuintptr
を使用することで、CPUが一度に処理できる32ビットワードにデータが収まるようになり、int64
を扱う際に発生していた「ダブルワード量」の処理によるオーバーヘッドが解消されます。
変更後のコードでは、n
の型がuintptr
に変更されています。
// 変更後
static void
scanblock(byte *b, uintptr n)
この変更に伴い、scanblock
およびdebug_scanblock
関数内の型キャストやエラーチェックも調整されています。
if((int64)(uintptr)n != n || n < 0)
というチェックは、int64
へのキャストが元の値と一致するか、または負の値でないかを確認していました。uintptr
は符号なし整数であるため、負の値になることはありません。また、uintptr
にすることで、int64
への不必要なキャストが不要になります。- 新しいチェック
if((intptr)n < 0)
は、uintptr
を符号付きのintptr
にキャストして負の値でないかを確認しています。これは、uintptr
が非常に大きな値を持つ場合に、符号付き整数として解釈すると負の値になる可能性があるため、異常なケースを検出するためのものです。ただし、メモリブロックの長さが負になることは通常ありえないため、このチェックは主に型変換の安全性を確保するためのものです。 runtime·printf
のフォーマット文字列も、n
がuintptr
になったことに合わせて(int64)n
として出力するように変更されています。これは、%D
フォーマット指定子がint64
を期待するためです。
この変更により、特に32ビットARMプラットフォームにおいて、ガベージコレクタがメモリブロックを走査する際の効率が大幅に向上し、結果としてGoプログラム全体のパフォーマンスが改善されました。ベンチマーク結果が示すように、特にデータ処理やI/Oが絡む操作(Gobエンコード/デコード、Gzip/Gunzip)で顕著な速度向上が見られます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/mgc0.c
ファイルに集中しています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -148,7 +148,7 @@ static struct {
// body. Keeping an explicit work list is easier on the stack allocator and
// more efficient.\n static void
-scanblock(byte *b, int64 n)
+scanblock(byte *b, uintptr n)
{\n \tbyte *obj, *arena_start, *arena_used, *p;\n \tvoid **vp;\n@@ -159,8 +159,8 @@ scanblock(byte *b, int64 n)
\tWorkbuf *wbuf;\n \tbool keepworking;\n \n-\tif((int64)(uintptr)n != n || n < 0) {\n-\t\truntime·printf(\"scanblock %p %D\\n\", b, n);\n+\tif((intptr)n < 0) {\n+\t\truntime·printf(\"scanblock %p %D\\n\", b, (int64)n);\n \t\truntime·throw(\"scanblock\");\n \t}\n \n@@ -191,7 +191,7 @@ scanblock(byte *b, int64 n)
\t\t// Each iteration scans the block b of length n, queueing pointers in\n \t\t// the work buffer.\n \t\tif(Debug > 1)\n-\t\t\truntime·printf(\"scanblock %p %D\\n\", b, n);\n+\t\t\truntime·printf(\"scanblock %p %D\\n\", b, (int64)n);\n \n \t\tvp = (void**)b;\n \t\tn >>= (2+PtrSize/8); /* n /= PtrSize (4 or 8) */\n@@ -339,7 +339,7 @@ scanblock(byte *b, int64 n)
// it is simpler, slower, single-threaded, recursive,\n // and uses bitSpecial as the mark bit.\n static void
-debug_scanblock(byte *b, int64 n)
+debug_scanblock(byte *b, uintptr n)
{\n \tbyte *obj, *p;\n \tvoid **vp;\n@@ -349,8 +349,8 @@ debug_scanblock(byte *b, int64 n)
\tif(!DebugMark)\n \t\truntime·throw(\"debug_scanblock without DebugMark\");\n \n-\tif((int64)(uintptr)n != n || n < 0) {\n-\t\truntime·printf(\"debug_scanblock %p %D\\n\", b, n);\n+\tif((intptr)n < 0) {\n+\t\truntime·printf(\"debug_scanblock %p %D\\n\", b, (int64)n);\n \t\truntime·throw(\"debug_scanblock\");\n \t}\n \n```
## コアとなるコードの解説
このコミットでは、以下の2つの関数シグネチャと、それに伴う内部の型チェックおよびデバッグ出力が変更されています。
1. **`scanblock`関数の変更**:
- 変更前: `static void scanblock(byte *b, int64 n)`
- 変更後: `static void scanblock(byte *b, uintptr n)`
- `n`の型が`int64`から`uintptr`に変更されました。`b`は走査対象のメモリブロックの開始アドレスを指す`byte`ポインタです。
2. **`debug_scanblock`関数の変更**:
- 変更前: `static void debug_scanblock(byte *b, int64 n)`
- 変更後: `static void debug_scanblock(byte *b, uintptr n)`
- こちらも`n`の型が`int64`から`uintptr`に変更されました。この関数はデバッグ目的で使用される`scanblock`のバリアントです。
3. **型チェックの変更**:
- 元のコードでは、`n`が`int64`型でありながら、`uintptr`にキャストした値と一致するか、および負の値でないかをチェックしていました。これは、`uintptr`が表現できる範囲を超える`int64`の値が渡された場合の安全策と考えられます。
- 変更後、`n`が`uintptr`になったため、`if((intptr)n < 0)`というチェックに変わりました。`uintptr`は符号なし整数なので、本来負の値にはなりませんが、非常に大きな値が渡された場合に`intptr`(符号付きポインタサイズ整数)として解釈すると負になる可能性があるため、異常な入力に対する防御的なチェックとして残されています。
4. **デバッグ出力の変更**:
- `runtime·printf`関数での`n`の出力において、`%D`フォーマット指定子が`int64`を期待するため、`uintptr`型の`n`を`(int64)n`に明示的にキャストして渡すように変更されました。
これらの変更は、メモリブロックの長さを表すのに最も効率的で適切な型である`uintptr`を使用することで、特に32ビットシステムにおけるガベージコレクタの性能を向上させることを目的としています。`uintptr`はポインタサイズに合わせた整数型であるため、CPUのワードサイズを最大限に活用し、不必要な64ビット演算を避けることができます。
## 関連リンク
- Go言語の`uintptr`に関する公式ドキュメント: [https://pkg.go.dev/unsafe#Pointer](https://pkg.go.dev/unsafe#Pointer) (Goの`unsafe.Pointer`と`uintptr`の関係について説明されています)
- Goのガベージコレクションに関する一般的な情報: [https://go.dev/doc/gc-guide](https://go.dev/doc/gc-guide)
## 参考にした情報源リンク
- Go言語のソースコード (`src/pkg/runtime/mgc0.c`)
- コミットメッセージに記載されているベンチマーク結果
- Go言語の公式ドキュメント
- Go言語のガベージコレクションに関する一般的な知識
- ARMアーキテクチャにおけるデータ型とパフォーマンスに関する一般的な知識
# [インデックス 13287] ファイルの概要
このコミットは、Goランタイムのガベージコレクタの一部である`scanblock`関数と`debug_scanblock`関数において、メモリブロックの長さを表す型を`int64`から`uintptr`に変更するものです。この変更は、特に32ビットプラットフォーム、中でもARMアーキテクチャにおけるパフォーマンスの最適化を目的としています。
## コミット
commit 09f48db3e17c71e2ab709efb760e2a305c67aace Author: Dave Cheney dave@cheney.net Date: Tue Jun 5 18:55:14 2012 +1000
runtime: use uintptr for block length in scanblock
Using an int64 for a block size doesn't make
sense on 32bit platforms but extracts a performance
penalty dealing with double word quantities on Arm.
linux/arm
benchmark old ns/op new ns/op delta
BenchmarkGobDecode 155401600 144589300 -6.96%
BenchmarkGobEncode 72772220 62460940 -14.17%
BenchmarkGzip 5822632 2604797 -55.26%
BenchmarkGunzip 326321 151721 -53.51%
benchmark old MB/s new MB/s speedup
BenchmarkGobDecode 4.94 5.31 1.07x
BenchmarkGobEncode 10.55 12.29 1.16x
R=golang-dev, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/6272047
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/09f48db3e17c71e2ab709efb760e2a305c67aace](https://github.com/golang.org/cl/6272047)
## 元コミット内容
Goランタイムの`src/pkg/runtime/mgc0.c`ファイルにおいて、ガベージコレクタの`scanblock`関数および`debug_scanblock`関数が、メモリブロックの長さを表す引数`n`に`int64`型を使用していました。
## 変更の背景
この変更の主な背景は、32ビットプラットフォーム、特にARMアーキテクチャにおけるパフォーマンスの最適化です。
1. **32ビットプラットフォームでの`int64`の非効率性**: 32ビットシステムでは、`int64`(64ビット整数)を扱う際に、CPUが一度に処理できるワードサイズ(32ビット)を超えるため、複数の命令が必要になります。これは「ダブルワード量(double word quantities)」の処理と呼ばれ、余分なオーバーヘッドが発生します。メモリブロックの長さは通常、ポインタサイズ(32ビットシステムでは32ビット)で十分表現できるため、`int64`を使用することは不必要にリソースを消費し、パフォーマンスを低下させる要因となっていました。
2. **ARMアーキテクチャでのパフォーマンスペナルティ**: ARMプロセッサは、特に組み込みシステムやモバイルデバイスで広く使用されており、リソースが限られていることが多いです。このような環境で`int64`のような大きなデータ型を頻繁に扱うことは、顕著なパフォーマンスペナルティにつながります。コミットメッセージに記載されているベンチマーク結果は、このパフォーマンス改善の具体的な証拠です。`BenchmarkGzip`や`BenchmarkGunzip`で50%以上の改善が見られるなど、I/Oやデータ処理に関連する操作で大きな効果が出ています。
3. **`uintptr`の適切な利用**: `uintptr`型は、ポインタを保持するのに十分な大きさの符号なし整数型であり、プラットフォームのワードサイズに依存します(32ビットシステムでは32ビット、64ビットシステムでは64ビット)。メモリブロックの長さは本質的にアドレス空間内のオフセットやサイズを表すため、`uintptr`が最も適切で効率的な型です。これにより、32ビットシステムでは32ビットのレジスタを効率的に利用できるようになり、処理速度が向上します。
## 前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
1. **Goランタイム (Go Runtime)**: Go言語で書かれたプログラムを実行するために必要な低レベルのコード群です。これには、ガベージコレクタ、スケジューラ、プリミティブな同期メカニズムなどが含まれます。Goランタイムの多くはC言語(またはGo言語自体)で実装されており、`src/pkg/runtime/mgc0.c`はその一部です。
2. **ガベージコレクタ (Garbage Collector, GC)**: プログラムが動的に確保したメモリのうち、もはや使用されていない(参照されていない)領域を自動的に解放するシステムです。GoのGCは並行・世代別・マーク&スイープ方式を採用しており、プログラムの実行中にバックグラウンドで動作します。`scanblock`関数は、GCがメモリブロックを走査し、到達可能なオブジェクトを特定する(マークする)プロセスの一部です。
3. **`uintptr`型**: Go言語における組み込み型の一つで、ポインタを保持するのに十分な大きさの符号なし整数型です。そのサイズは実行環境のポインタサイズ(通常はCPUのワードサイズ)に依存します。例えば、32ビットシステムでは32ビット(4バイト)、64ビットシステムでは64ビット(8バイト)になります。`uintptr`は、ポインタ演算を行う際や、C言語との相互運用でメモリアドレスを整数として扱う場合に特に有用です。
4. **`int64`型**: 64ビット幅の符号付き整数型です。32ビットシステムでは、`int64`の値を扱うために2つの32ビットレジスタを使用したり、複数のCPU命令を必要としたりするため、処理にオーバーヘッドが生じます。
5. **ARMアーキテクチャ**: スマートフォン、タブレット、組み込みシステムなどで広く利用されているCPUアーキテクチャです。x86アーキテクチャと比較して、一般的に消費電力が低く、命令セットがシンプルであるという特徴があります。パフォーマンス最適化においては、データ型のサイズやアライメントが特に重要になります。
6. **ベンチマーク (Benchmark)**: ソフトウェアの性能を測定するためのテストです。このコミットでは、`ns/op`(操作あたりのナノ秒)や`MB/s`(1秒あたりのメガバイト)といった指標で、変更前後の性能を比較しています。
## 技術的詳細
このコミットの技術的な核心は、Goランタイムのガベージコレクタにおけるメモリブロックの走査処理の最適化です。
Goのガベージコレクタは、ヒープ上のオブジェクトをマークし、到達可能なオブジェクトを特定する「マークフェーズ」を持っています。このフェーズにおいて、`scanblock`関数は特定のメモリブロックを走査し、そのブロック内に含まれるポインタを識別して、それらが指すオブジェクトもマーク対象としてキューに追加します。
元の実装では、`scanblock`関数と`debug_scanblock`関数が、走査対象のメモリブロックの長さ(サイズ)を`int64 n`という引数で受け取っていました。
```c
// 変更前
static void
scanblock(byte *b, int64 n)
しかし、メモリブロックの長さは、そのシステムのアドレス空間のサイズを超えることはありません。32ビットシステムでは、アドレス空間は最大4GBであり、これを表現するのにint64
は過剰です。uintptr
はポインタサイズに合わせた整数型であるため、32ビットシステムでは32ビット、64ビットシステムでは64ビットのサイズを持ちます。これにより、32ビットシステムではuintptr
を使用することで、CPUが一度に処理できる32ビットワードにデータが収まるようになり、int64
を扱う際に発生していた「ダブルワード量」の処理によるオーバーヘッドが解消されます。
変更後のコードでは、n
の型がuintptr
に変更されています。
// 変更後
static void
scanblock(byte *b, uintptr n)
この変更に伴い、scanblock
およびdebug_scanblock
関数内の型キャストやエラーチェックも調整されています。
if((int64)(uintptr)n != n || n < 0)
というチェックは、int64
へのキャストが元の値と一致するか、または負の値でないかを確認していました。uintptr
は符号なし整数であるため、負の値になることはありません。また、uintptr
にすることで、int64
への不必要なキャストが不要になります。- 新しいチェック
if((intptr)n < 0)
は、uintptr
を符号付きのintptr
にキャストして負の値でないかを確認しています。これは、uintptr
が非常に大きな値を持つ場合に、符号付き整数として解釈すると負の値になる可能性があるため、異常なケースを検出するためのものです。ただし、メモリブロックの長さが負になることは通常ありえないため、このチェックは主に型変換の安全性を確保するためのものです。 runtime·printf
のフォーマット文字列も、n
がuintptr
になったことに合わせて(int64)n
として出力するように変更されています。これは、%D
フォーマット指定子がint64
を期待するためです。
この変更により、特に32ビットARMプラットフォームにおいて、ガベージコレクタがメモリブロックを走査する際の効率が大幅に向上し、結果としてGoプログラム全体のパフォーマンスが改善されました。ベンチマーク結果が示すように、特にデータ処理やI/Oが絡む操作(Gobエンコード/デコード、Gzip/Gunzip)で顕著な速度向上が見られます。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/mgc0.c
ファイルに集中しています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -148,7 +148,7 @@ static struct {
// body. Keeping an explicit work list is easier on the stack allocator and
// more efficient.\n static void
-scanblock(byte *b, int64 n)
+scanblock(byte *b, uintptr n)
{\n \tbyte *obj, *arena_start, *arena_used, *p;\n \tvoid **vp;\n@@ -159,8 +159,8 @@ scanblock(byte *b, int64 n)
\tWorkbuf *wbuf;\n \tbool keepworking;\n \n-\tif((int64)(uintptr)n != n || n < 0) {\n-\t\truntime·printf(\"scanblock %p %D\\n\", b, n);\n+\tif((intptr)n < 0) {\n+\t\truntime·printf(\"scanblock %p %D\\n\", b, (int64)n);\n \t\truntime·throw(\"scanblock\");\n \t}\n \n@@ -191,7 +191,7 @@ scanblock(byte *b, int64 n)
\t\t// Each iteration scans the block b of length n, queueing pointers in\n \t\t// the work buffer.\n \t\tif(Debug > 1)\n-\t\t\truntime·printf(\"scanblock %p %D\\n\", b, n);\n+\t\t\truntime·printf(\"scanblock %p %D\\n\", b, (int64)n);\n \n \t\tvp = (void**)b;\n \t\tn >>= (2+PtrSize/8); /* n /= PtrSize (4 or 8) */\n@@ -339,7 +339,7 @@ scanblock(byte *b, int64 n)
// it is simpler, slower, single-threaded, recursive,\n // and uses bitSpecial as the mark bit.\n static void
-debug_scanblock(byte *b, int64 n)
+debug_scanblock(byte *b, uintptr n)
{\n \tbyte *obj, *p;\n \tvoid **vp;\n@@ -349,8 +349,8 @@ debug_scanblock(byte *b, int64 n)
\tif(!DebugMark)\n \t\truntime·throw(\"debug_scanblock without DebugMark\");\n \n-\tif((int64)(uintptr)n != n || n < 0) {\n-\t\truntime·printf(\"debug_scanblock %p %D\\n\", b, n);\n+\tif((intptr)n < 0) {\n+\t\truntime·printf(\"debug_scanblock %p %D\\n\", b, (int64)n);\n \t\truntime·throw(\"debug_scanblock\");\n \t}\n \n```
## コアとなるコードの解説
このコミットでは、以下の2つの関数シグネチャと、それに伴う内部の型チェックおよびデバッグ出力が変更されています。
1. **`scanblock`関数の変更**:
- 変更前: `static void scanblock(byte *b, int64 n)`
- 変更後: `static void scanblock(byte *b, uintptr n)`
- `n`の型が`int64`から`uintptr`に変更されました。`b`は走査対象のメモリブロックの開始アドレスを指す`byte`ポインタです。
2. **`debug_scanblock`関数の変更**:
- 変更前: `static void debug_scanblock(byte *b, int64 n)`
- 変更後: `static void debug_scanblock(byte *b, uintptr n)`
- こちらも`n`の型が`int64`から`uintptr`に変更されました。この関数はデバッグ目的で使用される`scanblock`のバリアントです。
3. **型チェックの変更**:
- 元のコードでは、`n`が`int64`型でありながら、`uintptr`にキャストした値と一致するか、および負の値でないかをチェックしていました。これは、`uintptr`が表現できる範囲を超える`int64`の値が渡された場合の安全策と考えられます。
- 変更後、`n`が`uintptr`になったため、`if((intptr)n < 0)`というチェックに変わりました。`uintptr`は符号なし整数なので、本来負の値にはなりませんが、非常に大きな値が渡された場合に`intptr`(符号付きポインタサイズ整数)として解釈すると負になる可能性があるため、異常な入力に対する防御的なチェックとして残されています。
4. **デバッグ出力の変更**:
- `runtime·printf`関数での`n`の出力において、`%D`フォーマット指定子が`int64`を期待するため、`uintptr`型の`n`を`(int64)n`に明示的にキャストして渡すように変更されました。
これらの変更は、メモリブロックの長さを表すのに最も効率的で適切な型である`uintptr`を使用することで、特に32ビットシステムにおけるガベージコレクタの性能を向上させることを目的としています。`uintptr`はポインタサイズに合わせた整数型であるため、CPUのワードサイズを最大限に活用し、不必要な64ビット演算を避けることができます。
## 関連リンク
- Go言語の`uintptr`に関する公式ドキュメント: [https://pkg.go.dev/unsafe#Pointer](https://pkg.go.dev/unsafe#Pointer) (Goの`unsafe.Pointer`と`uintptr`の関係について説明されています)
- Goのガベージコレクションに関する一般的な情報: [https://go.dev/doc/gc-guide](https://go.dev/doc/gc-guide)
## 参考にした情報源リンク
- Go言語のソースコード (`src/pkg/runtime/mgc0.c`)
- コミットメッセージに記載されているベンチマーク結果
- Go言語の公式ドキュメント
- Go言語のガベージコレクションに関する一般的な知識
- ARMアーキテクチャにおけるデータ型とパフォーマンスに関する一般的な知識
- Web search results for "Go uintptr performance 32-bit ARM" (特に、`uintptr`が整数型であり、32ビットARMではネイティブなワードサイズに対応すること、GCとの関係、32ビットARMにおけるアライメントの重要性など)