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

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

このコミットは、Go言語の標準ライブラリにおけるstrings.IndexByteの使用箇所をstrings.Indexに戻す変更を元に戻すものです。具体的には、以前のコミット(CL 12486043 / ab644299d124)でstrings.Indexからstrings.IndexByteへの変更が行われましたが、本コミットはその変更を「元に戻す」ことを目的としています。これにより、コードベース全体で特定のバイト(ASCII文字)を検索する際に、より汎用的なstrings.Index関数が再び使用されるようになります。

コミット

commit d8e27db39562d2106f0c9cf7518eaa9ade748a4f
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Aug 5 16:27:24 2013 -0700

    undo CL 12486043 / ab644299d124
    
    Uglier.
    
    ««« original CL description
    all: use strings.IndexByte instead of Index where possible
    
    R=golang-dev, khr
    CC=golang-dev
    https://golang.org/cl/12486043
    »»»
    
    R=golang-dev
    CC=golang-dev
    https://golang.org/cl/12485044

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

https://github.com/golang/go/commit/d8e27db39562d2106f0c9cf7518eaa9ade748a4f

元コミット内容

このコミットは、以前のコミット(ハッシュ ab644299d124、CL 12486043)を元に戻すものです。元のコミットの目的は、「可能な限りstrings.Indexの代わりにstrings.IndexByteを使用する」ことでした。これは、特定のバイト(単一のASCII文字など)を文字列内で検索する際に、より特化したstrings.IndexByteを使用することで、パフォーマンスの向上が期待されたためと考えられます。

変更の背景

このコミットの背景には、Go言語の標準ライブラリにおける文字列検索関数の選択に関する議論があります。

元のコミット(ab644299d124)では、strings.IndexByteが導入され、特定のバイトを検索する多くの箇所でstrings.Indexの代わりに採用されました。strings.IndexByte(s, c byte)は、文字列s内でバイトcが最初に出現するインデックスを返します。一方、strings.Index(s, substr string)は、文字列s内で部分文字列substrが最初に出現するインデックスを返します。

strings.IndexByteは、単一のバイトを検索するというより限定的なタスクに特化しているため、理論的にはstrings.Indexよりも高速である可能性があります。しかし、このコミットのメッセージにある「Uglier.」という一言が示唆するように、strings.IndexByteへの変更がコードの可読性や保守性を損なうと判断された可能性があります。

具体的なパフォーマンス上のメリットが期待ほど大きくなかった、あるいはコードの統一性やシンプルさが優先された、といった理由が考えられます。Go言語の設計哲学は「シンプルさ」と「可読性」を重視するため、わずかなパフォーマンス向上のためにコードが複雑になることは避けられる傾向にあります。このコミットは、その哲学に基づき、以前の変更を元に戻す決定がなされたことを示しています。

前提知識の解説

Go言語のstringsパッケージ

Go言語の標準ライブラリには、文字列操作のためのstringsパッケージが含まれています。このパッケージは、文字列の検索、置換、分割、結合など、多岐にわたる機能を提供します。

strings.Index(s, substr string) int

  • 機能: 文字列s内で、部分文字列substrが最初に出現するインデックスを返します。substrが見つからない場合は-1を返します。
  • 特徴: substrは任意の長さの文字列を指定できます。内部的には、より複雑な文字列検索アルゴリズム(例: Boyer-Mooreアルゴリズムの最適化版など)が使用されることがあります。

strings.IndexByte(s string, c byte) int

  • 機能: 文字列s内で、バイトcが最初に出現するインデックスを返します。cが見つからない場合は-1を返します。
  • 特徴: cは単一のバイト(byte型)である必要があります。これは、ASCII文字やUTF-8エンコーディングにおける単一バイト文字(例: ,, ., /, :, =, -, など)の検索に特に適しています。単一バイトの検索に特化しているため、strings.Indexよりも高速な実装が可能です。

パフォーマンスと可読性のトレードオフ

ソフトウェア開発において、パフォーマンスと可読性はしばしばトレードオフの関係にあります。

  • パフォーマンス: 処理速度やリソース使用効率を指します。特定のタスクに特化した関数は、より汎用的な関数よりも高速である可能性があります。
  • 可読性: コードがどれだけ理解しやすいかを指します。コードがシンプルで、意図が明確であれば、可読性が高いと言えます。

このコミットでは、strings.IndexByteが提供する可能性のあるパフォーマンス上の利点よりも、strings.Indexを使用することによるコードの統一性や可読性が優先されたと考えられます。特に、検索対象が単一の文字であっても、それが「バイト」として扱われることの意図がコードを読んだ際に直感的に理解しにくい場合、可読性が損なわれる可能性があります。

技術的詳細

このコミットは、Go言語の標準ライブラリ内の複数のパッケージにわたって、strings.IndexByteの呼び出しをstrings.Indexの呼び出しに置き換えるという、非常に直接的な変更を行っています。

具体的には、以下のような変更パターンが適用されています。

// 変更前
idx := strings.IndexByte(someString, 'X')

// 変更後
idx := strings.Index(someString, "X")

ここで、'X'は単一のバイトリテラル(byte型)であり、"X"は単一の文字からなる文字列リテラル(string型)です。

この変更は、Goのコンパイラやランタイムの最適化に依存する部分もありますが、主な影響は以下の点に集約されます。

  1. 型の一貫性: strings.Indexは第二引数にstring型を期待するため、検索対象の文字をbyteリテラルからstringリテラルに変換する必要があります。これは、Goの型システムにおける厳密さを維持する上で自然な選択です。
  2. 汎用性: strings.Indexは単一の文字だけでなく、任意の長さの部分文字列を検索できるため、コードの意図がより明確になります。例えば、strings.Index(s, ",")と書くことで、「カンマという文字を探している」という意図がstrings.IndexByte(s, ',')よりも直感的に伝わりやすくなります。
  3. パフォーマンスの再評価: 以前のコミットでstrings.IndexByteが導入されたのはパフォーマンス向上が目的でしたが、その後の評価で、そのメリットがコードの複雑性や可読性の低下に見合わないと判断された可能性があります。Goのstrings.Indexは、内部的に高度に最適化されており、多くの場合、単一バイトの検索においてもstrings.IndexByteと比べて顕著なパフォーマンス差がないか、あるいは特定のケースではstrings.Indexの方が効率的である可能性も考えられます。特に、コンパイラが定数文字列の検索を最適化できる場合、strings.Index("abc", "b")のような呼び出しは非常に効率的になります。

この変更は、Go言語の標準ライブラリ全体にわたる一貫性と可読性を重視する設計思想を反映していると言えます。

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

このコミットは、Go言語の標準ライブラリ内の多数のファイルにわたって、strings.IndexByteの呼び出しをstrings.Indexに置き換えています。以下に、その変更の一部を抜粋して示します。

src/pkg/crypto/x509/pem_decrypt.go

--- a/src/pkg/crypto/x509/pem_decrypt.go
+++ b/src/pkg/crypto/x509/pem_decrypt.go
@@ -115,7 +115,7 @@ func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
 		return nil, errors.New("x509: no DEK-Info header in block")
 	}
 
-	idx := strings.IndexByte(dek, ',')
+	idx := strings.Index(dek, ",")
 	if idx == -1 {
 		return nil, errors.New("x509: malformed DEK-Info header")
 	}

src/pkg/debug/gosym/symtab.go

--- a/src/pkg/debug/gosym/symtab.go
+++ b/src/pkg/debug/gosym/symtab.go
@@ -40,7 +40,7 @@ func (s *Sym) Static() bool { return s.Type >= 'a' }
 // PackageName returns the package part of the symbol name,
 // or the empty string if there is none.
 func (s *Sym) PackageName() string {
-	if i := strings.IndexByte(s.Name, '.'); i != -1 {
+	if i := strings.Index(s.Name, "."); i != -1 {
 		return s.Name[0:i]
 	}
 	return ""
@@ -49,7 +49,7 @@ func (s *Sym) PackageName() string {
 // ReceiverName returns the receiver type name of this symbol,
 // or the empty string if there is none.
 func (s *Sym) ReceiverName() string {
-	l := strings.IndexByte(s.Name, '.')
+	l := strings.Index(s.Name, ".")
 	r := strings.LastIndex(s.Name, ".")
 	if l == -1 || r == -1 || l == r {
 		return ""

src/pkg/encoding/json/tags.go

--- a/src/pkg/encoding/json/tags.go
+++ b/src/pkg/encoding/json/tags.go
@@ -15,7 +15,7 @@ type tagOptions string
 // parseTag splits a struct field's json tag into its name and
 // comma-separated options.
 func parseTag(tag string) (string, tagOptions) {
-	if idx := strings.IndexByte(tag, ','); idx != -1 {
+	if idx := strings.Index(tag, ","); idx != -1 {
 		return tag[:idx], tagOptions(tag[idx+1:])
 	}
 	return tag, tagOptions("")
@@ -31,7 +31,7 @@ func (o tagOptions) Contains(optionName string) bool {
 	s := string(o)
 	for s != "" {
 		var next string
-		i := strings.IndexByte(s, ',')
+		i := strings.Index(s, ",")
 		if i >= 0 {
 			s, next = s[:i], s[i+1:]
 		}

これらの例からわかるように、変更は一貫してstrings.IndexByte(s, byte_char)strings.Index(s, "string_char")に置き換えています。

コアとなるコードの解説

変更された各ファイルでは、特定の区切り文字(例: ,, ., :, =, /, など)を文字列内で検索するためにstrings.IndexByteが使用されていました。このコミットにより、これらの検索がすべてstrings.Indexに統一されました。

例えば、src/pkg/crypto/x509/pem_decrypt.goの変更では、PEMブロックのDEK-Infoヘッダーを解析する際に、カンマ(,)の位置を特定するためにstrings.IndexByteが使われていましたが、これがstrings.Indexに置き換えられました。

// 変更前: バイトリテラル ',' を検索
idx := strings.IndexByte(dek, ',')

// 変更後: 文字列リテラル "," を検索
idx := strings.Index(dek, ",")

この変更は、機能的な振る舞いを変更するものではありません。どちらの関数も、指定された文字(またはバイト)が最初に出現するインデックスを返します。しかし、strings.Indexを使用することで、コードの意図が「特定の文字(文字列)を検索する」という点でより明確になります。また、strings.IndexByteが導入された際のパフォーマンス上の期待が、実際のコードベース全体でのメリットよりも、コードの統一性や可読性の維持という観点から劣ると判断された結果であると考えられます。

Go言語の標準ライブラリは、その設計においてシンプルさと一貫性を非常に重視しています。このコミットは、その原則に立ち返り、特定の最適化よりも全体的なコード品質と保守性を優先した結果であると言えます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • GitHubのGoリポジトリのコミット履歴
  • Go言語のstringsパッケージに関する一般的な情報源(ブログ記事、フォーラムの議論など)