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

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

このコミットは、Go言語の標準ライブラリ全体で、特定の文字(バイト)を文字列内で検索する際に、strings.Index関数の代わりにstrings.IndexByte関数を使用するように変更するものです。これにより、パフォーマンスの向上が期待されます。

コミット

commit 4c772cda54896b0213b5eaffed81031e259f26d4
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Aug 5 15:46:06 2013 -0700

    all: use strings.IndexByte instead of Index where possible

    R=golang-dev, khr
    CC=golang-dev
    https://golang.org/cl/12486043

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

https://github.com/golang/go/commit/4c772cda54896b0213b5eaffed81031e259f26d4

元コミット内容

このコミットの元の内容は、「可能な限りstrings.Indexの代わりにstrings.IndexByteを使用する」というものです。これは、Go言語の標準ライブラリ内の多くのファイルにわたる変更であり、特定の文字の検索処理を最適化することを目的としています。

変更の背景

Go言語の文字列はUTF-8でエンコードされており、strings.Index(s, substr)関数は、substrが単一のルーン(Unicodeコードポイント)であっても、そのルーンが複数バイトで表現される可能性があるため、内部的にUTF-8のデコード処理を伴う場合があります。一方、strings.IndexByte(s, c byte)関数は、検索対象が常に単一のバイトであると保証されているため、UTF-8のデコード処理をスキップし、より高速なバイト単位の比較を行うことができます。

このコミットが行われた2013年当時、Go言語のパフォーマンス最適化は活発に行われていました。特に、標準ライブラリのような頻繁に利用されるコードパスにおいて、わずかなパフォーマンス改善でも全体的なアプリケーションの応答性やスループットに大きな影響を与える可能性があります。この変更は、単一バイト文字(例: ,, ., /, =, , :, -, *, ?, $など)を検索する際に、より効率的なstrings.IndexByteを使用することで、Goプログラム全体の実行速度を向上させることを目的としています。

前提知識の解説

Go言語の文字列とUTF-8

Go言語において、文字列はバイトのシーケンスとして扱われます。具体的には、Goの文字列はUTF-8エンコードされたUnicodeテキストを保持するように設計されています。

  • string: Goのstring型は、不変のバイトスライス([]byte)として実装されています。これは、文字列が直接文字のシーケンスではなく、バイトのシーケンスであることを意味します。
  • UTF-8: UTF-8は可変長エンコーディングであり、ASCII文字(U+0000からU+007F)は1バイトで表現され、それ以外の文字は2バイト以上で表現されます。これにより、ASCII互換性を保ちつつ、世界のほとんどの言語の文字を表現できます。

strings.Indexstrings.IndexByte

Goの標準ライブラリstringsパッケージには、文字列内で部分文字列やバイトを検索するための関数が提供されています。

  • func Index(s, substr string) int:

    • この関数は、文字列s内でsubstrが最初に現れるインデックスを返します。見つからない場合は-1を返します。
    • substrが単一の文字(ルーン)であっても、内部的にはsubstrが複数バイトで構成される可能性を考慮し、UTF-8のデコード処理やルーン単位の比較を行う場合があります。これは、特にsubstrがASCII範囲外の文字である場合にオーバーヘッドとなります。
  • func IndexByte(s string, c byte) int:

    • この関数は、文字列s内でバイトcが最初に現れるインデックスを返します。見つからない場合は-1を返します。
    • この関数は、検索対象が常に単一のバイトであることが保証されているため、UTF-8のデコード処理を必要とせず、バイト単位で直接比較を行います。これにより、strings.Indexよりも高速に動作します。

技術的詳細

このコミットの技術的な核心は、Go言語の文字列処理におけるパフォーマンス最適化です。

strings.Index(s, substr)は、substrが単一の文字であっても、その文字がUTF-8で複数バイトとしてエンコードされている可能性を考慮して設計されています。例えば、日本語の文字や絵文字などは複数バイトで表現されます。そのため、strings.Indexは、検索対象のsubstrが何バイトで構成されているかを判断し、それに応じて文字列sをルーン単位で走査するような処理を行う必要があります。これは、特にsubstrが常に1バイト文字であることが分かっている場合、不要なオーバーヘッドとなります。

一方、strings.IndexByte(s, c byte)は、検索対象が常に1バイトのデータ(byte型)であることが保証されています。したがって、この関数は文字列sをバイトスライスとして扱い、単純なバイト単位の比較を効率的に実行できます。UTF-8のデコードやルーンの境界を意識する必要がないため、strings.Indexよりも高速に動作します。

このコミットでは、コードベース全体で、カンマ(,)、ピリオド(.)、スラッシュ(/)、イコール(=)、スペース( )、コロン(:)、ハイフン(-)、アスタリスク(*)、疑問符(?)、ドル記号($)など、常に1バイトで表現されるASCII文字を検索している箇所を特定し、strings.Indexからstrings.IndexByteに置き換えています。これにより、これらの頻繁に実行される文字列検索操作のパフォーマンスが向上し、Goプログラム全体の実行効率に貢献します。

この最適化は、マイクロベンチマークレベルでは顕著な差が出ますが、大規模なアプリケーション全体で見ると、これらの小さな改善が積み重なって、全体的な応答時間の短縮やCPU使用率の低下につながる可能性があります。

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

このコミットは、Go標準ライブラリの多岐にわたるパッケージに影響を与えています。以下に、変更されたファイルの一部と、その中での代表的な変更例を示します。

変更されたファイル例:

  • src/pkg/crypto/x509/pem_decrypt.go
  • src/pkg/debug/gosym/symtab.go
  • src/pkg/encoding/json/tags.go
  • src/pkg/encoding/xml/typeinfo.go
  • src/pkg/encoding/xml/xml.go
  • src/pkg/go/build/build.go
  • src/pkg/go/printer/printer.go
  • src/pkg/math/big/rat.go
  • src/pkg/mime/mediatype.go
  • src/pkg/net/http/cgi/child.go
  • src/pkg/net/http/cookie.go
  • src/pkg/net/http/fs.go
  • src/pkg/net/http/request.go
  • src/pkg/net/http/server.go
  • src/pkg/net/url/url.go
  • src/pkg/os/os_test.go
  • src/pkg/os/user/lookup_unix.go
  • src/pkg/path/match.go
  • src/pkg/regexp/exec_test.go
  • src/pkg/regexp/regexp.go
  • src/pkg/unicode/maketables.go

代表的な変更例 (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.Index(dek, ",")
+	idx := strings.IndexByte(dek, ',')
 	if idx == -1 {
 		return nil, errors.New("x509: malformed DEK-Info header")
 	}

別の例 (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.Index(tag, ","); idx != -1 {
+	if idx := strings.IndexByte(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.Index(s, ",")
+		i := strings.IndexByte(s, ',')
 		if i >= 0 {
 			s, next = s[:i], s[i+1:]
 		}

コアとなるコードの解説

上記の変更例に見られるように、このコミットの主要な変更は、strings.Index(someString, "singleCharString")という形式の呼び出しを、strings.IndexByte(someString, 'singleCharByte')という形式に置き換えることです。

  • 変更前: strings.Index(dek, ",")
    • ","は文字列リテラルであり、Goコンパイラはこれをstring型として扱います。strings.Indexは、この文字列","を検索対象として処理します。
  • 変更後: strings.IndexByte(dek, ',')
    • ','は文字リテラルであり、Goではrune型(Unicodeコードポイント)として扱われますが、ASCII文字の場合はそのバイト値と等価です。strings.IndexByteは、このバイト値','を検索対象として処理します。

この変更は、検索対象が常に単一のASCII文字(つまり単一バイト)であることが明確な場合に適用されています。これにより、Goランタイムは、より特化され、効率的なIndexByteの実装を利用できるようになります。結果として、これらの操作が頻繁に発生するGoアプリケーションにおいて、全体的なパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • Go言語のstringsパッケージドキュメント: https://pkg.go.dev/strings
  • Go言語の文字列とバイトスライスに関する一般的な情報源 (例: Go言語の公式ブログ、Effective Goなど)
  • UTF-8エンコーディングに関する情報源 (例: Wikipediaなど)
  • Go言語のパフォーマンス最適化に関する議論 (GoコミュニティのメーリングリストやIssueトラッカーなど)