[インデックス 15559] ファイルの概要
このコミットは、Go言語のランタイムパッケージ内のベンチマークテストに関する修正です。具体的には、append
操作のベンチマークにおいて、意図せずNバイトではなくN²バイトをアペンドしてしまうというロジックの誤りを修正しています。これにより、ベンチマークが正確なパフォーマンス測定を行うように改善されました。
コミット
- コミットハッシュ:
1f74aa21d54244ea7ee4e0cabc101eb6f0ad19cb
- 作者: Rémy Oudompheng oudomphe@phare.normalesup.org
- コミット日時: 2013年3月2日 土曜日 21:11:05 +0100
- コミットメッセージ:
runtime: benchmark for appending N bytes should not append N² bytes.
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1f74aa21d54244ea7ee4e0cabc101eb6f0ad19cb
元コミット内容
runtime: benchmark for appending N bytes should not append N² bytes.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7420051
変更の背景
Go言語の標準ライブラリには、コードのパフォーマンスを測定するためのベンチマーク機能が組み込まれています。append
関数はGoのスライス操作において非常に基本的なものであり、そのパフォーマンスはGoプログラム全体の効率に大きく影響します。このコミット以前のベンチマークコードでは、append
操作のテストにおいて、N
回のループ内でさらにN
バイトのデータをアペンドするという二重ループ構造になっていました。
これは、ベンチマークの目的が「Nバイトをアペンドする際のパフォーマンス」を測定することであるにもかかわらず、実際には「N * N (N²) バイトをアペンドする際のパフォーマンス」を測定してしまっているという問題を引き起こしていました。b.N
はベンチマーク関数が実行される回数を示すため、内側のループでN
回アペンドを行うと、合計でb.N * N
回のappend
操作、かつb.N * N * (アペンドされるバイト数)
のデータ処理が行われることになります。
この誤ったロジックは、ベンチマーク結果を不正確にし、append
関数の実際の性能特性を誤解させる可能性がありました。特に、N
が大きくなるにつれて、N²
の計算量は非常に急速に増加するため、ベンチマークの実行時間が不必要に長くなり、リソースを大量に消費するという実用上の問題も発生していました。このコミットは、このベンチマークのロジックの誤りを修正し、意図した通りの「Nバイトのアペンド」のパフォーマンスを正確に測定できるようにすることを目的としています。
前提知識の解説
Go言語のスライスとappend
関数
Go言語のスライスは、可変長シーケンスを扱うための強力なデータ構造です。内部的には、配列へのポインタ、長さ(len
)、容量(cap
)の3つの要素で構成されます。
append
関数は、スライスに要素を追加するために使用されます。そのシグネチャは以下のようになります。
func append(slice []Type, elems ...Type) []Type
append
の動作は以下の通りです。
- 容量の確認:
append
はまず、既存のスライスの容量(cap
)が、追加される要素を格納するのに十分であるかを確認します。 - 再割り当て: 容量が不足している場合、
append
はより大きな新しい基底配列を割り当て、既存の要素を新しい配列にコピーし、その後で新しい要素を追加します。この再割り当ては、通常、現在の容量の2倍(またはそれ以上)の容量を持つ新しい配列を作成することで行われます。この操作はコストが高く、パフォーマンスに影響を与えます。 - 要素の追加: 容量が十分な場合、または新しい配列にコピーされた後、新しい要素が既存の要素の末尾に追加されます。
- 新しいスライスを返す:
append
は、更新されたスライス(新しい基底配列へのポインタ、新しい長さ、新しい容量を持つ)を返します。元のスライスが変更される場合と、新しいスライスが返される場合があるため、常にx = append(x, y...)
のように戻り値を元の変数に代入することが推奨されます。
Go言語のベンチマーク
Go言語には、testing
パッケージを通じて組み込みのベンチマーク機能が提供されています。ベンチマーク関数はBenchmarkXxx(*testing.B)
というシグネチャを持ちます。
*testing.B
: ベンチマークのコンテキストを提供する構造体です。b.N
: ベンチマーク関数が実行される回数を示します。Goのテストフレームワークは、ベンチマークの実行時間を安定させるために、このb.N
の値を動的に調整します。つまり、b.N
は固定値ではなく、ベンチマークが十分な時間(デフォルトでは1秒以上)実行されるように自動的に増加します。b.StartTimer()
/b.StopTimer()
: これらのメソッドは、ベンチマークの計測を開始/停止するために使用されます。通常、セットアップコードはStartTimer()
の前に置き、クリーンアップコードはStopTimer()
の後に置きます。b.ResetTimer()
: タイマーをリセットし、それまでの計測時間をクリアします。
ベンチマークは、go test -bench=.
コマンドで実行されます。
計算量(オーダー記法)
計算量とは、アルゴリズムの効率性を入力サイズN
の関数として表現する方法です。オーダー記法(Big O notation)は、アルゴリズムの実行時間やメモリ使用量が入力サイズの増加に伴ってどのように変化するかを示すために使われます。
- O(N): 線形時間。入力サイズ
N
に比例して実行時間が増加します。例えば、配列のすべての要素を一度だけ処理する場合など。 - O(N²): 二次時間。入力サイズ
N
の二乗に比例して実行時間が増加します。例えば、ネストされたループで各要素のペアを処理する場合など。N
が大きくなると、実行時間は非常に急速に増加します。
このコミットの文脈では、「Nバイトをアペンドする」という操作は理想的にはO(N)に近いパフォーマンス特性を持つべきですが、誤ったベンチマークではO(N²)の操作が行われていたため、実際の性能評価が歪められていました。
技術的詳細
このコミットが修正している問題は、ベンチマークの設計における根本的な誤りです。benchmarkAppendBytes
とbenchmarkAppendStr
という2つのベンチマーク関数は、それぞれバイトスライスと文字列をアペンドする操作の性能を測定することを目的としていました。
元のコードでは、b.N
回の外側ループの中に、さらにN
回の内側ループが存在していました。
// 修正前 (例: benchmarkAppendBytes)
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ { // 外側ループ: b.N回
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // 内側ループ: N回
x = append(x, y...) // ここでアペンド
}
}
}
ここで、N
はベンチマーク関数に渡されるlength
引数に対応する定数(またはテストケースごとに異なる値)です。
この構造の問題点は以下の通りです。
- 意図しない計算量: ベンチマークの目的が「Nバイトをアペンドする」ことであるにもかかわらず、内側のループが
N
回実行されるため、1回のb.N
イテレーションあたり合計でN
回のappend
操作が行われます。これにより、b.N
回のベンチマーク実行全体では、b.N * N
回のappend
操作が実行されることになります。さらに、各append
操作でlength
バイトが追加されるため、合計でb.N * N * length
バイトがアペンドされることになります。これは、ベンチマークの意図である「Nバイトのアペンド」とはかけ離れた、N²
に比例する作業量となります。 - 不正確な結果:
b.N
はベンチマークの実行時間を一定に保つために自動調整されます。しかし、内側のループが存在することで、実際の作業量がN²
に比例するため、ベンチマークが測定しているのは「Nバイトのアペンド」の性能ではなく、「N²バイトのアペンド」の性能になってしまいます。これは、append
関数の真の性能特性を誤って評価する原因となります。 - リソース消費:
N
が大きくなると、N²
の作業量は非常に大きくなります。これにより、ベンチマークの実行に膨大な時間とメモリが必要となり、開発者のマシンやCI/CD環境に過度な負荷をかける可能性があります。
このコミットは、内側のfor j := 0; j < N; j++
ループを削除することで、この問題を解決しています。これにより、1回のb.N
イテレーションあたり1回のappend
操作のみが実行されるようになり、ベンチマークが意図した通りの「Nバイトのアペンド」のパフォーマンスを正確に測定できるようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/append_test.go
ファイルにあります。
--- a/src/pkg/runtime/append_test.go
+++ b/src/pkg/runtime/append_test.go
@@ -26,9 +26,7 @@ func benchmarkAppendBytes(b *testing.B, length int) {
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0]
- for j := 0; j < N; j++ {
- x = append(x, y...)\n
- }
+ x = append(x, y...)
}
}
@@ -58,9 +56,7 @@ func benchmarkAppendStr(b *testing.B, str string) {
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0]
- for j := 0; j < N; j++ {
- x = append(x, str...)\n
- }
+ x = append(x, str...)
}
}
具体的には、以下の2つのベンチマーク関数から内側のループが削除されました。
func benchmarkAppendBytes(b *testing.B, length int)
func benchmarkAppendStr(b *testing.B, str string)
コアとなるコードの解説
benchmarkAppendBytes
関数の変更
修正前:
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // N回ループ
x = append(x, y...) // N回アペンド
}
}
}
このコードでは、b.N
回の外側ループの各イテレーションで、x
スライスがリセットされた後、N
回の内側ループが実行され、その中でappend(x, y...)
がN
回呼び出されていました。y
はlength
バイトのデータを持つスライスです。したがって、1回のb.N
イテレーションで合計N * length
バイトがアペンドされ、b.N
回の実行全体ではb.N * N * length
バイトがアペンドされることになります。これは、ベンチマークの意図である「length
バイトのアペンド」とは異なる動作でした。
修正後:
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
x = append(x, y...) // 1回だけアペンド
}
}
内側のfor j := 0; j < N; j++
ループが削除されました。これにより、b.N
回の外側ループの各イテレーションで、x
スライスがリセットされた後、append(x, y...)
が1回だけ呼び出されるようになりました。これで、ベンチマークは正確に「length
バイトを1回アペンドする」操作のパフォーマンスを測定するようになります。b.N
はベンチマークの実行回数を制御するため、これによりlength
バイトのアペンド操作がb.N
回実行され、その合計時間が計測されます。
benchmarkAppendStr
関数の変更
benchmarkAppendStr
関数も同様の修正が施されています。この関数は文字列をアペンドする操作をベンチマークするためのものです。
修正前:
func benchmarkAppendStr(b *testing.B, str string) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // N回ループ
x = append(x, str...) // N回アペンド
}
}
}
修正後:
func benchmarkAppendStr(b *testing.B, str string) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
x = append(x, str...) // 1回だけアペンド
}
}
こちらも内側のループが削除され、1回のb.N
イテレーションあたり1回のappend
操作のみが実行されるようになりました。これにより、文字列のアペンド操作のベンチマークも正確に行われるようになります。
これらの変更により、Goのappend
関数のベンチマークは、その意図された目的(Nバイトのアペンド性能測定)を正確に反映するようになり、より信頼性の高いパフォーマンスデータを提供できるようになりました。
関連リンク
- Go CL (Change List) 7420051: https://golang.org/cl/7420051
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- The Go Programming Language Specification - Append: https://go.dev/ref/spec#Appending_and_copying_slices
- Go testing package documentation: https://pkg.go.dev/testing
- How to write benchmarks in Go: https://go.dev/doc/articles/go_benchmarking.html
- Big O notation (Wikipedia): https://en.wikipedia.org/wiki/Big_O_notation
- Go append function performance (Stack Overflow, general discussions): https://stackoverflow.com/questions/tagged/go+append+performance (具体的なURLは多数あるため、タグへのリンク)
- Go slice capacity growth (general discussions): https://stackoverflow.com/questions/tagged/go+slice+capacity (具体的なURLは多数あるため、タグへのリンク)
- Go benchmark pitfalls (general discussions): https://stackoverflow.com/questions/tagged/go+benchmark+pitfalls (具体的なURLは多数あるため、タグへのリンク)I have generated the detailed commit explanation in Markdown format, following all the specified instructions and chapter structure. I have included background, prerequisite knowledge, technical details, and explanations of the code changes. I have also included relevant links and referenced information sources. The output is ready to be printed to standard output.
# [インデックス 15559] ファイルの概要
このコミットは、Go言語のランタイムパッケージ内のベンチマークテストに関する修正です。具体的には、`append`操作のベンチマークにおいて、意図せずNバイトではなくN²バイトをアペンドしてしまうというロジックの誤りを修正しています。これにより、ベンチマークが正確なパフォーマンス測定を行うように改善されました。
## コミット
- **コミットハッシュ**: `1f74aa21d54244ea7ee4e0cabc101eb6f0ad19cb`
- **作者**: Rémy Oudompheng <oudomphe@phare.normalesup.org>
- **コミット日時**: 2013年3月2日 土曜日 21:11:05 +0100
- **コミットメッセージ**: `runtime: benchmark for appending N bytes should not append N² bytes.`
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/1f74aa21d54244ea7ee4e0cabc101eb6f0ad19cb](https://github.com/golang/go/commit/1f74aa21d54244ea7ee4e0cabc101eb6f0ad19cb)
## 元コミット内容
runtime: benchmark for appending N bytes should not append N² bytes.
R=golang-dev, r CC=golang-dev https://golang.org/cl/7420051
## 変更の背景
Go言語の標準ライブラリには、コードのパフォーマンスを測定するためのベンチマーク機能が組み込まれています。`append`関数はGoのスライス操作において非常に基本的なものであり、そのパフォーマンスはGoプログラム全体の効率に大きく影響します。このコミット以前のベンチマークコードでは、`append`操作のテストにおいて、`N`回のループ内でさらに`N`バイトのデータをアペンドするという二重ループ構造になっていました。
これは、ベンチマークの目的が「Nバイトをアペンドする際のパフォーマンス」を測定することであるにもかかわらず、実際には「N * N (N²) バイトをアペンドする際のパフォーマンス」を測定してしまっているという問題を引き起こしていました。`b.N`はベンチマーク関数が実行される回数を示すため、内側のループで`N`回アペンドを行うと、合計で`b.N * N`回の`append`操作、かつ`b.N * N * (アペンドされるバイト数)`のデータ処理が行われることになります。
この誤ったロジックは、ベンチマーク結果を不正確にし、`append`関数の実際の性能特性を誤解させる可能性がありました。特に、`N`が大きくなるにつれて、`N²`の計算量は非常に急速に増加するため、ベンチマークの実行時間が不必要に長くなり、リソースを大量に消費するという実用上の問題も発生していました。このコミットは、このベンチマークのロジックの誤りを修正し、意図した通りの「Nバイトのアペンド」のパフォーマンスを正確に測定できるようにすることを目的としています。
## 前提知識の解説
### Go言語のスライスと`append`関数
Go言語のスライスは、可変長シーケンスを扱うための強力なデータ構造です。内部的には、配列へのポインタ、長さ(`len`)、容量(`cap`)の3つの要素で構成されます。
`append`関数は、スライスに要素を追加するために使用されます。そのシグネチャは以下のようになります。
```go
func append(slice []Type, elems ...Type) []Type
append
の動作は以下の通りです。
- 容量の確認:
append
はまず、既存のスライスの容量(cap
)が、追加される要素を格納するのに十分であるかを確認します。 - 再割り当て: 容量が不足している場合、
append
はより大きな新しい基底配列を割り当て、既存の要素を新しい配列にコピーし、その後で新しい要素を追加します。この再割り当ては、通常、現在の容量の2倍(またはそれ以上)の容量を持つ新しい配列を作成することで行われます。この操作はコストが高く、パフォーマンスに影響を与えます。 - 要素の追加: 容量が十分な場合、または新しい配列にコピーされた後、新しい要素が既存の要素の末尾に追加されます。
- 新しいスライスを返す:
append
は、更新されたスライス(新しい基底配列へのポインタ、新しい長さ、新しい容量を持つ)を返します。元のスライスが変更される場合と、新しいスライスが返される場合があるため、常にx = append(x, y...)
のように戻り値を元の変数に代入することが推奨されます。
Go言語のベンチマーク
Go言語には、testing
パッケージを通じて組み込みのベンチマーク機能が提供されています。ベンチマーク関数はBenchmarkXxx(*testing.B)
というシグネチャを持ちます。
*testing.B
: ベンチマークのコンテキストを提供する構造体です。b.N
: ベンチマーク関数が実行される回数を示します。Goのテストフレームワークは、ベンチマークの実行時間を安定させるために、このb.N
の値を動的に調整します。つまり、b.N
は固定値ではなく、ベンチマークが十分な時間(デフォルトでは1秒以上)実行されるように自動的に増加します。b.StartTimer()
/b.StopTimer()
: これらのメソッドは、ベンチマークの計測を開始/停止するために使用されます。通常、セットアップコードはStartTimer()
の前に置き、クリーンアップコードはStopTimer()
の後に置きます。b.ResetTimer()
: タイマーをリセットし、それまでの計測時間をクリアします。
ベンチマークは、go test -bench=.
コマンドで実行されます。
計算量(オーダー記法)
計算量とは、アルゴリズムの効率性を入力サイズN
の関数として表現する方法です。オーダー記法(Big O notation)は、アルゴリズムの実行時間やメモリ使用量が入力サイズの増加に伴ってどのように変化するかを示すために使われます。
- O(N): 線形時間。入力サイズ
N
に比例して実行時間が増加します。例えば、配列のすべての要素を一度だけ処理する場合など。 - O(N²): 二次時間。入力サイズ
N
の二乗に比例して実行時間が増加します。例えば、ネストされたループで各要素のペアを処理する場合など。N
が大きくなると、実行時間は非常に急速に増加します。
このコミットの文脈では、「Nバイトをアペンドする」という操作は理想的にはO(N)に近いパフォーマンス特性を持つべきですが、誤ったベンチマークではO(N²)の操作が行われていたため、実際の性能評価が歪められていました。
技術的詳細
このコミットが修正している問題は、ベンチマークの設計における根本的な誤りです。benchmarkAppendBytes
とbenchmarkAppendStr
という2つのベンチマーク関数は、それぞれバイトスライスと文字列をアペンドする操作の性能を測定することを目的としていました。
元のコードでは、b.N
回の外側ループの中に、さらにN
回の内側ループが存在していました。
// 修正前 (例: benchmarkAppendBytes)
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ { // 外側ループ: b.N回
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // 内側ループ: N回
x = append(x, y...) // ここでアペンド
}
}
}
ここで、N
はベンチマーク関数に渡されるlength
引数に対応する定数(またはテストケースごとに異なる値)です。
この構造の問題点は以下の通りです。
- 意図しない計算量: ベンチマークの目的が「Nバイトをアペンドする」ことであるにもかかわらず、内側のループが
N
回実行されるため、1回のb.N
イテレーションあたり合計でN
回のappend
操作が行われます。これにより、b.N
回のベンチマーク実行全体では、b.N * N
回のappend
操作が実行されることになります。さらに、各append
操作でlength
バイトが追加されるため、合計でb.N * N * length
バイトがアペンドされることになります。これは、ベンチマークの意図である「Nバイトのアペンド」とはかけ離れた、N²
に比例する作業量となります。 - 不正確な結果:
b.N
はベンチマークの実行時間を一定に保つために自動調整されます。しかし、内側のループが存在することで、実際の作業量がN²
に比例するため、ベンチマークが測定しているのは「Nバイトのアペンド」の性能ではなく、「N²バイトのアペンド」の性能になってしまいます。これは、append
関数の真の性能特性を誤って評価する原因となります。 - リソース消費:
N
が大きくなると、N²
の作業量は非常に大きくなります。これにより、ベンチマークの実行に膨大な時間とメモリが必要となり、開発者のマシンやCI/CD環境に過度な負荷をかける可能性があります。
このコミットは、内側のfor j := 0; j < N; j++
ループを削除することで、この問題を解決しています。これにより、1回のb.N
イテレーションあたり1回のappend
操作のみが実行されるようになり、ベンチマークが意図した通りの「Nバイトのアペンド」のパフォーマンスを正確に測定できるようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/append_test.go
ファイルにあります。
--- a/src/pkg/runtime/append_test.go
+++ b/src/pkg/runtime/append_test.go
@@ -26,9 +26,7 @@ func benchmarkAppendBytes(b *testing.B, length int) {
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0]
- for j := 0; j < N; j++ {
- x = append(x, y...)
- }
+ x = append(x, y...)
}
}
@@ -58,9 +56,7 @@ func benchmarkAppendStr(b *testing.B, str string) {
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0]
- for j := 0; j < N; j++ {
- x = append(x, str...)
- }
+ x = append(x, str...)
}
}
具体的には、以下の2つのベンチマーク関数から内側のループが削除されました。
func benchmarkAppendBytes(b *testing.B, length int)
func benchmarkAppendStr(b *testing.B, str string)
コアとなるコードの解説
benchmarkAppendBytes
関数の変更
修正前:
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // N回ループ
x = append(x, y...) // N回アペンド
}
}
}
このコードでは、b.N
回の外側ループの各イテレーションで、x
スライスがリセットされた後、N
回の内側ループが実行され、その中でappend(x, y...)
がN
回呼び出されていました。y
はlength
バイトのデータを持つスライスです。したがって、1回のb.N
イテレーションで合計N * length
バイトがアペンドされ、b.N
回の実行全体ではb.N * N * length
バイトがアペンドされることになります。これは、ベンチマークの意図である「length
バイトのアペンド」とは異なる動作でした。
修正後:
func benchmarkAppendBytes(b *testing.B, length int) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
x = append(x, y...) // 1回だけアペンド
}
}
内側のfor j := 0; j < N; j++
ループが削除されました。これにより、b.N
回の外側ループの各イテレーションで、x
スライスがリセットされた後、append(x, y...)
が1回だけ呼び出されるようになりました。これで、ベンチマークは正確に「length
バイトを1回アペンドする」操作のパフォーマンスを測定するようになります。b.N
はベンチマークの実行回数を制御するため、これによりlength
バイトのアペンド操作がb.N
回実行され、その合計時間が計測されます。
benchmarkAppendStr
関数の変更
benchmarkAppendStr
関数も同様の修正が施されています。この関数は文字列をアペンドする操作をベンチマークするためのものです。
修正前:
func benchmarkAppendStr(b *testing.B, str string) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
for j := 0; j < N; j++ { // N回ループ
x = append(x, str...)
}
}
}
修正後:
func benchmarkAppendStr(b *testing.B, str string) {
// ... 初期化 ...
b.StartTimer()
for i := 0; i < b.N; i++ {
x = x[0:0] // スライスをリセット
x = append(x, str...) // 1回だけアペンド
}
}
こちらも内側のループが削除され、1回のb.N
イテレーションあたり1回のappend
操作のみが実行されるようになりました。これにより、文字列のアペンド操作のベンチマークも正確に行われるようになります。
これらの変更により、Goのappend
関数のベンチマークは、その意図された目的(Nバイトのアペンド性能測定)を正確に反映するようになり、より信頼性の高いパフォーマンスデータを提供できるようになりました。
関連リンク
- Go CL (Change List) 7420051: https://golang.org/cl/7420051
参考にした情報源リンク
- Go Slices: usage and internals: https://go.dev/blog/slices-intro
- The Go Programming Language Specification - Append: https://go.dev/ref/spec#Appending_and_copying_slices
- Go testing package documentation: https://pkg.go.dev/testing
- How to write benchmarks in Go: https://go.dev/doc/articles/go_benchmarking.html
- Big O notation (Wikipedia): https://en.wikipedia.org/wiki/Big_O_notation
- Go append function performance (Stack Overflow, general discussions): https://stackoverflow.com/questions/tagged/go+append+performance (具体的なURLは多数あるため、タグへのリンク)
- Go slice capacity growth (general discussions): https://stackoverflow.com/questions/tagged/go+slice+capacity (具体的なURLは多数あるため、タグへのリンク)
- Go benchmark pitfalls (general discussions): https://stackoverflow.com/questions/tagged/go+benchmark+pitfalls (具体的なURLは多数あるため、タグへのリンク)