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

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

このコミットは、Go言語の標準ライブラリである net パッケージと text/tabwriter パッケージにおける make 関数の使用方法を最適化するものです。具体的には、スライス作成時の容量(capacity)引数を明示的に指定することで、より効率的なメモリ割り当てを実現しています。

コミット

commit 563d0b62b8f13583e0ee27dcbf925b8b6bd3284c
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date:   Thu Dec 12 10:13:17 2013 +0400

    net, text/tabwriter: use cap arg to make

    Changes generated by:

    gofmt -w -r 'make(a, b)[0:0] -> make(a, 0, b)' src/pkg

    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/24820045

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

https://github.com/golang/go/commit/563d0b62b8f13583e0ee27dcbf925b8b6bd3284c

元コミット内容

このコミットは、gofmt -w -r 'make(a, b)[0:0] -> make(a, 0, b)' src/pkg という gofmt コマンドによって自動生成された変更を適用しています。これは、make 関数でスライスを初期化する際に、長さ(length)を0に、容量(capacity)を明示的に指定するパターンにコードを書き換えるものです。

変更の背景

Go言語のスライスは、内部的に配列へのポインタ、長さ、容量の3つの要素で構成されています。make([]T, length, capacity) の形式でスライスを作成すると、length で指定された要素数分のメモリが初期化され、capacity で指定された要素数分のメモリが確保されます。make([]T, length) の形式で作成した場合、capacitylength と同じになります。

このコミット以前のコードでは、make(a, b)[0:0] のようなパターンが使われていました。これは、b の容量を持つスライスを作成し、その直後に [0:0] というスライス式を使って長さを0にリセットするというイディオムです。この方法は機能的には正しいものの、make 関数に直接容量引数を渡す make(a, 0, b) という形式の方が、より意図が明確で、かつ一部のケースではコンパイラによる最適化の機会が増える可能性があります。

この変更の背景には、Go言語のコードベース全体でより統一された、かつ効率的なスライスの初期化パターンを推進するという目的があります。gofmt -r を使用してこの変更を自動化したことは、大規模なコードベース全体にわたるリファクタリングを効率的に行うためのGoコミュニティのアプローチを示しています。

前提知識の解説

Go言語のスライスと make 関数

Go言語のスライスは、可変長シーケンスを扱うための強力なデータ構造です。スライスは、基盤となる配列の一部を参照します。スライスは以下の3つの要素で構成されます。

  1. ポインタ: スライスが参照する基盤配列の先頭要素へのポインタ。
  2. 長さ (Length): スライスに含まれる要素の数。len() 関数で取得できます。
  3. 容量 (Capacity): スライスが基盤配列のどこまで拡張できるかを示す最大要素数。cap() 関数で取得できます。

make 関数は、スライス、マップ、チャネルを初期化するために使用されます。スライスの場合は、以下の形式で使われます。

  • make([]Type, length): 指定された length のスライスを作成します。この場合、容量は長さと同じになります。
  • make([]Type, length, capacity): 指定された lengthcapacity のスライスを作成します。capacitylength 以上である必要があります。

例:

s1 := make([]int, 5)        // len=5, cap=5
s2 := make([]int, 0, 5)     // len=0, cap=5
s3 := make([]int, 5)[0:0]   // len=0, cap=5 (このコミットで変更されたパターン)

s2s3 はどちらも長さ0、容量5のスライスを作成しますが、s2 の方が直接的で推奨される方法です。

gofmt コマンドと -r オプション

gofmt はGo言語のコードを自動的にフォーマットするツールです。Go言語の標準的なコーディングスタイルに準拠させることで、コードの可読性と一貫性を高めます。

-r オプションは gofmt にリライトルール(rewrite rule)を適用させることができます。これは、指定されたパターンに一致するコードを別のパターンに書き換える機能です。このコミットでは、'make(a, b)[0:0] -> make(a, 0, b)' というリライトルールが使用されています。

  • make(a, b)[0:0]: これは、型 a のスライスを長さ b で作成し、その直後に [0:0] というスライス式を使って長さを0にリセットするパターンを意味します。a はスライスの要素の型(例: []string)、b は容量として使われる整数値です。
  • make(a, 0, b): これは、型 a のスライスを長さ0、容量 b で作成するパターンを意味します。

このリライトルールは、コードベース全体で特定のイディオムをより推奨される形式に自動的に変換するために使用されました。

技術的詳細

このコミットの技術的なポイントは、Go言語のスライス初期化におけるメモリ割り当ての効率とコードの意図の明確化にあります。

make([]Type, length)[0:0] というパターンは、まず length と同じ長さと容量を持つスライスを作成し、その後すぐにそのスライスを長さ0にスライスし直すという二段階の操作を行います。この操作自体は正しい結果(長さ0、元の length と同じ容量のスライス)をもたらしますが、以下のような考慮点があります。

  1. 意図の明確さ: make([]Type, 0, capacity) は、最初から長さ0で特定の容量を持つスライスを作成するという意図を直接的に表現しています。一方、make([]Type, capacity)[0:0] は、一度長さ capacity のスライスを作成してから長さを0にリセットするという、やや回りくどい表現になります。コードの可読性の観点から、直接的な表現が好まれます。
  2. コンパイラの最適化: 現代のGoコンパイラは非常に高度であり、多くの場合、make([]Type, capacity)[0:0] のようなパターンも make([]Type, 0, capacity) と同等に最適化できる可能性があります。しかし、常にそうであるとは限りません。特に古いコンパイラや、より複雑なコンテキストでは、二段階の操作がわずかながら余分な処理を発生させる可能性もゼロではありません。make([]Type, 0, capacity) の形式は、コンパイラにとってスライスの初期状態がより明確であるため、より効率的なコード生成を促す可能性があります。
  3. 一貫性: Go言語の標準ライブラリや慣習では、スライスを初期化する際に容量を明示的に指定する場合は make([]Type, 0, capacity) の形式が推奨されます。このコミットは、Goのコードベース全体でこの推奨されるパターンへの移行を促進し、コードの一貫性を高めることを目的としています。

この変更は、パフォーマンスに劇的な影響を与えるものではありませんが、Go言語のイディオムに沿ったクリーンで効率的なコードを維持するための継続的な努力の一環です。

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

以下の3つのファイルで変更が行われています。

  1. src/pkg/net/dnsconfig_unix.go
  2. src/pkg/net/parse.go
  3. src/pkg/text/tabwriter/tabwriter_test.go

それぞれの変更は、make(a, b)[0:0] の形式を make(a, 0, b) に置き換えるものです。

src/pkg/net/dnsconfig_unix.go

--- a/src/pkg/net/dnsconfig_unix.go
+++ b/src/pkg/net/dnsconfig_unix.go
@@ -27,7 +27,7 @@ func dnsReadConfig() (*dnsConfig, error) {
 		return nil, &DNSConfigError{err}
 	}
 	conf := new(dnsConfig)
-	conf.servers = make([]string, 3)[0:0] // small, but the standard limit
+	conf.servers = make([]string, 0, 3) // small, but the standard limit
 	conf.search = make([]string, 0)
 	conf.ndots = 1
 	conf.timeout = 5

conf.servers スライスの初期化が make([]string, 3)[0:0] から make([]string, 0, 3) に変更されています。これは、DNSサーバーのアドレスを格納するためのスライスで、最初は空ですが、最大3つの要素を格納できる容量を持たせています。

src/pkg/net/parse.go

--- a/src/pkg/net/parse.go
+++ b/src/pkg/net/parse.go
@@ -67,7 +67,7 @@ func open(name string) (*file, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &file{fd, make([]byte, os.Getpagesize())[0:0], false}, nil
+	return &file{fd, make([]byte, 0, os.Getpagesize()), false}, nil
 }
 
 func byteIndex(s string, c byte) int {

file 構造体のフィールド(おそらくバイトスライス)の初期化が make([]byte, os.Getpagesize())[0:0] から make([]byte, 0, os.Getpagesize()) に変更されています。これは、OSのページサイズを容量として持つバイトスライスを初期化しています。

src/pkg/text/tabwriter/tabwriter_test.go

--- a/src/pkg/text/tabwriter/tabwriter_test.go
+++ b/src/pkg/text/tabwriter/tabwriter_test.go
@@ -14,7 +14,7 @@ type buffer struct {
 	a []byte
 }
 
-func (b *buffer) init(n int) { b.a = make([]byte, n)[0:0] }
+func (b *buffer) init(n int) { b.a = make([]byte, 0, n) }
 
 func (b *buffer) clear() { b.a = b.a[0:0] }
 

buffer 型の init メソッド内で、バイトスライス b.a の初期化が make([]byte, n)[0:0] から make([]byte, 0, n) に変更されています。これはテストコード内の変更であり、tabwriter の内部バッファの初期化に関連しています。

コアとなるコードの解説

変更された各行は、Go言語のスライスを初期化する際の一般的なパターンを、より推奨される形式に統一しています。

元のコード: make(Type, capacity)[0:0] これは、Type 型のスライスを capacity の長さと容量で作成し、その後すぐに [0:0] というスライス式を使って長さを0にリセットしていました。結果として、長さ0で指定された容量を持つスライスが生成されます。

変更後のコード: make(Type, 0, capacity) これは、Type 型のスライスを直接、長さ0、容量 capacity で作成します。この形式は、スライスが最初は空であるものの、将来的に capacity までの要素を効率的に追加できることを明確に示しています。

この変更は、機能的な振る舞いを一切変えることなく、コードの意図をより明確にし、Go言語のイディオムに準拠させるためのものです。特に、gofmt -r を用いた自動化は、このような大規模なリファクタリングを安全かつ効率的に行うためのGoエコシステムの強みを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に src/pkg ディレクトリ)
  • Go言語のブログ記事やコミュニティの議論(make 関数の容量引数やスライスのイディオムに関するもの)
  • gofmt -r の使用例に関する情報
  • GitHubのコミット履歴と関連するコードレビュー(CL: Change List)

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

このコミットは、Go言語の標準ライブラリである net パッケージと text/tabwriter パッケージにおける make 関数の使用方法を最適化するものです。具体的には、スライス作成時の容量(capacity)引数を明示的に指定することで、より効率的なメモリ割り当てとコードの意図の明確化を実現しています。

コミット

commit 563d0b62b8f13583e0ee27dcbf925b8b6bd3284c
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date:   Thu Dec 12 10:13:17 2013 +0400

    net, text/tabwriter: use cap arg to make

    Changes generated by:

    gofmt -w -r 'make(a, b)[0:0] -> make(a, 0, b)' src/pkg

    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/24820045

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

https://github.com/golang/go/commit/563d0b62b8f13583e0ee27dcbf925b8b6bd3284c

元コミット内容

このコミットは、gofmt -w -r 'make(a, b)[0:0] -> make(a, 0, b)' src/pkg という gofmt コマンドによって自動生成された変更を適用しています。これは、make 関数でスライスを初期化する際に、長さ(length)を0に、容量(capacity)を明示的に指定するパターンにコードを書き換えるものです。

変更の背景

Go言語のスライスは、内部的に配列へのポインタ、長さ、容量の3つの要素で構成されています。make([]T, length, capacity) の形式でスライスを作成すると、length で指定された要素数分のメモリが初期化され、capacity で指定された要素数分のメモリが確保されます。make([]T, length) の形式で作成した場合、capacitylength と同じになります。

このコミット以前のコードでは、make(a, b)[0:0] のようなパターンが使われていました。これは、b の容量を持つスライスを作成し、その直後に [0:0] というスライス式を使って長さを0にリセットするというイディオムです。この方法は機能的には正しいものの、make 関数に直接容量引数を渡す make(a, 0, b) という形式の方が、より意図が明確で、かつ一部のケースではコンパイラによる最適化の機会が増える可能性があります。

この変更の背景には、Go言語のコードベース全体でより統一された、かつ効率的なスライスの初期化パターンを推進するという目的があります。gofmt -r を使用してこの変更を自動化したことは、大規模なコードベース全体にわたるリファクタリングを効率的に行うためのGoコミュニティのアプローチを示しています。

前提知識の解説

Go言語のスライスと make 関数

Go言語のスライスは、可変長シーケンスを扱うための強力なデータ構造です。スライスは、基盤となる配列の一部を参照します。スライスは以下の3つの要素で構成されます。

  1. ポインタ: スライスが参照する基盤配列の先頭要素へのポインタ。
  2. 長さ (Length): スライスに含まれる要素の数。len() 関数で取得できます。
  3. 容量 (Capacity): スライスが基盤配列のどこまで拡張できるかを示す最大要素数。cap() 関数で取得できます。

make 関数は、スライス、マップ、チャネルを初期化するために使用されます。スライスの場合は、以下の形式で使われます。

  • make([]Type, length): 指定された length のスライスを作成します。この場合、容量は長さと同じになります。
  • make([]Type, length, capacity): 指定された lengthcapacity のスライスを作成します。capacitylength 以上である必要があります。

例:

s1 := make([]int, 5)        // len=5, cap=5
s2 := make([]int, 0, 5)     // len=0, cap=5
s3 := make([]int, 5)[0:0]   // len=0, cap=5 (このコミットで変更されたパターン)

s2s3 はどちらも長さ0、容量5のスライスを作成しますが、s2 の方が直接的で推奨される方法です。make([]int, 0, 5) のように、長さ0と容量5を明示的に指定することで、スライスが最初は空であるものの、最大5つの要素を再割り当てなしで追加できることが明確に示されます。

gofmt コマンドと -r オプション

gofmt はGo言語のコードを自動的にフォーマットするツールです。Go言語の標準的なコーディングスタイルに準拠させることで、コードの可読性と一貫性を高めます。

-r オプションは gofmt にリライトルール(rewrite rule)を適用させることができます。これは、指定されたパターンに一致するコードを別のパターンに書き換える機能です。このコミットでは、'make(a, b)[0:0] -> make(a, 0, b)' というリライトルールが使用されています。

  • make(a, b)[0:0]: これは、型 a のスライスを長さ b で作成し、その直後に [0:0] というスライス式を使って長さを0にリセットするパターンを意味します。a はスライスの要素の型(例: []string)、b は容量として使われる整数値です。
  • make(a, 0, b): これは、型 a のスライスを長さ0、容量 b で作成するパターンを意味します。

このリライトルールは、コードベース全体で特定のイディオムをより推奨される形式に自動的に変換するために使用されました。gofmt -r は、pattern -> replacement の形式でルールを指定し、ワイルドカードとして小文字の単一文字識別子(例: x, y, z)を使用できます。-w フラグと組み合わせることで、変更を直接ソースファイルに書き込むことができます。

技術的詳細

このコミットの技術的なポイントは、Go言語のスライス初期化におけるメモリ割り当ての効率とコードの意図の明確化にあります。

make([]Type, length)[0:0] というパターンは、まず length と同じ長さと容量を持つスライスを作成し、その後すぐにそのスライスを長さ0にスライスし直すという二段階の操作を行います。この操作自体は正しい結果(長さ0、元の length と同じ容量のスライス)をもたらしますが、以下のような考慮点があります。

  1. 意図の明確さ: make([]Type, 0, capacity) は、最初から長さ0で特定の容量を持つスライスを作成するという意図を直接的に表現しています。一方、make([]Type, capacity)[0:0] は、一度長さ capacity のスライスを作成してから長さを0にリセットするという、やや回りくどい表現になります。コードの可読性の観点から、直接的な表現が好まれます。
  2. コンパイラの最適化: 現代のGoコンパイラは非常に高度であり、多くの場合、make([]Type, capacity)[0:0] のようなパターンも make([]Type, 0, capacity) と同等に最適化できる可能性があります。しかし、常にそうであるとは限りません。特に古いコンパイラや、より複雑なコンテキストでは、二段階の操作がわずかながら余分な処理を発生させる可能性もゼロではありません。make([]Type, 0, capacity) の形式は、コンパイラにとってスライスの初期状態がより明確であるため、より効率的なコード生成を促す可能性があります。
  3. 一貫性: Go言語の標準ライブラリや慣習では、スライスを初期化する際に容量を明示的に指定する場合は make([]Type, 0, capacity) の形式が推奨されます。このコミットは、Goのコードベース全体でこの推奨されるパターンへの移行を促進し、コードの一貫性を高めることを目的としています。

この変更は、パフォーマンスに劇的な影響を与えるものではありませんが、Go言語のイディオムに沿ったクリーンで効率的なコードを維持するための継続的な努力の一環です。

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

以下の3つのファイルで変更が行われています。

  1. src/pkg/net/dnsconfig_unix.go
  2. src/pkg/net/parse.go
  3. src/pkg/text/tabwriter/tabwriter_test.go

それぞれの変更は、make(a, b)[0:0] の形式を make(a, 0, b) に置き換えるものです。

src/pkg/net/dnsconfig_unix.go

--- a/src/pkg/net/dnsconfig_unix.go
+++ b/src/pkg/net/dnsconfig_unix.go
@@ -27,7 +27,7 @@ func dnsReadConfig() (*dnsConfig, error) {
 		return nil, &DNSConfigError{err}
 	}
 	conf := new(dnsConfig)
-	conf.servers = make([]string, 3)[0:0] // small, but the standard limit
+	conf.servers = make([]string, 0, 3) // small, but the standard limit
 	conf.search = make([]string, 0)
 	conf.ndots = 1
 	conf.timeout = 5

conf.servers スライスの初期化が make([]string, 3)[0:0] から make([]string, 0, 3) に変更されています。これは、DNSサーバーのアドレスを格納するためのスライスで、最初は空ですが、最大3つの要素を格納できる容量を持たせています。

src/pkg/net/parse.go

--- a/src/pkg/net/parse.go
+++ b/src/pkg/net/parse.go
@@ -67,7 +67,7 @@ func open(name string) (*file, error) {
 	if err != nil {
 		return nil, err
 	}
-	return &file{fd, make([]byte, os.Getpagesize())[0:0], false}, nil
+	return &file{fd, make([]byte, 0, os.Getpagesize()), false}, nil
 }
 
 func byteIndex(s string, c byte) int {

file 構造体のフィールド(おそらくバイトスライス)の初期化が make([]byte, os.Getpagesize())[0:0] から make([]byte, 0, os.Getpagesize()) に変更されています。これは、OSのページサイズを容量として持つバイトスライスを初期化しています。

src/pkg/text/tabwriter/tabwriter_test.go

--- a/src/pkg/text/tabwriter/tabwriter_test.go
+++ b/src/pkg/text/tabwriter/tabwriter_test.go
@@ -14,7 +14,7 @@ type buffer struct {
 	a []byte
 }
 
-func (b *buffer) init(n int) { b.a = make([]byte, n)[0:0] }
+func (b *buffer) init(n int) { b.a = make([]byte, 0, n) }
 
 func (b *buffer) clear() { b.a = b.a[0:0] }
 

buffer 型の init メソッド内で、バイトスライス b.a の初期化が make([]byte, n)[0:0] から make([]byte, 0, n) に変更されています。これはテストコード内の変更であり、tabwriter の内部バッファの初期化に関連しています。

コアとなるコードの解説

変更された各行は、Go言語のスライスを初期化する際の一般的なパターンを、より推奨される形式に統一しています。

元のコード: make(Type, capacity)[0:0] これは、Type 型のスライスを capacity の長さと容量で作成し、その後すぐに [0:0] というスライス式を使って長さを0にリセットしていました。結果として、長さ0で指定された容量を持つスライスが生成されます。

変更後のコード: make(Type, 0, capacity) これは、Type 型のスライスを直接、長さ0、容量 capacity で作成します。この形式は、スライスが最初は空であるものの、将来的に capacity までの要素を効率的に追加できることを明確に示しています。

この変更は、機能的な振る舞いを一切変えることなく、コードの意図をより明確にし、Go言語のイディオムに準拠させるためのものです。特に、gofmt -r を用いた自動化は、このような大規模なリファクタリングを安全かつ効率的に行うためのGoエコシステムの強みを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に src/pkg ディレクトリ)
  • Go言語のブログ記事やコミュニティの議論(make 関数の容量引数やスライスのイディオムに関するもの)
  • gofmt -r の使用例に関する情報
  • GitHubのコミット履歴と関連するコードレビュー(CL: Change List)
  • Web検索: "Go make slice length 0 capacity"
  • Web検索: "gofmt -r rewrite rules"