[インデックス 14806] ファイルの概要
このコミットは、Go言語の標準ライブラリおよびツールにおいて、バイトスライス([]byte
)の比較にbytes.Compare(a, b) == 0
を使用している箇所を、より効率的で読みやすいbytes.Equal(a, b)
に置き換える変更です。この変更は、gofmt
ツールによる自動置換によって広範囲に適用されました。
コミット
commit 46811d27ce6b3753f70bc49423f4f448e613609d
Author: Matthew Dempsky <mdempsky@google.com>
Date: Mon Jan 7 10:03:49 2013 +1100
src: Use bytes.Equal instead of bytes.Compare where possible.
bytes.Equal is simpler to read and should also be faster because
of short-circuiting and assembly implementations.
Change generated automatically using:
gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)'
gofmt -r 'bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)'
R=golang-dev, dave, adg, rsc
CC=golang-dev
https://golang.org/cl/7038051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/46811d27ce6b3753f70bc49423f4f448e613609d
元コミット内容
src: Use bytes.Equal instead of bytes.Compare where possible.
bytes.Equal is simpler to read and should also be faster because
of short-circuiting and assembly implementations.
Change generated automatically using:
gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)'
gofmt -r 'bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)'
R=golang-dev, dave, adg, rsc
CC=golang-dev
https://golang.org/cl/7038051
変更の背景
この変更の主な背景は、Go言語におけるバイトスライスの比較処理の可読性とパフォーマンスの向上です。
Go言語のbytes
パッケージには、バイトスライスを比較するための2つの主要な関数、bytes.Compare
とbytes.Equal
があります。
bytes.Compare(a, b)
: 2つのバイトスライスa
とb
を辞書順に比較し、a == b
なら0
、a < b
なら-1
、a > b
なら1
を返します。これは、ソートなどの順序関係を判定する際に使用されます。bytes.Equal(a, b)
: 2つのバイトスライスa
とb
が完全に等しいかどうかを判定し、等しければtrue
、そうでなければfalse
を返します。
これまで、バイトスライスの等価性をチェックするためにbytes.Compare(a, b) == 0
という表現が使われることがありました。しかし、これは冗長であり、bytes.Equal(a, b)
というより直接的で意図が明確な関数が存在します。
このコミットは、以下の理由からbytes.Equal
への置き換えを推進しています。
- 可読性の向上:
bytes.Equal(a, b)
は、2つのバイトスライスが等しいかどうかを直接的に表現するため、コードの意図がより明確になります。 - パフォーマンスの向上:
bytes.Equal
は、等価性チェックに特化して最適化されています。特に、バイトスライスの長さが異なる場合や、先頭から比較してすぐに不一致が見つかる場合に、bytes.Compare
よりも高速に処理を終了できます(ショートサーキット)。また、一部のアーキテクチャではアセンブリ言語による最適化された実装が提供されている場合があり、これもパフォーマンス向上に寄与します。
この変更は、gofmt
というGo言語のコードフォーマッタの-r
オプション(リライトルール)を使用して自動的に行われました。これにより、Go言語のコードベース全体で一貫した改善が効率的に適用されました。
前提知識の解説
Go言語のbytes
パッケージ
bytes
パッケージは、バイトスライス([]byte
)を操作するためのユーティリティ関数を提供します。文字列操作におけるstrings
パッケージに相当するものです。
bytes.Compare(a, b []byte) int
この関数は、2つのバイトスライスa
とb
を辞書順に比較します。
a
がb
より小さい場合、負の整数(通常-1
)を返します。a
がb
と等しい場合、0
を返します。a
がb
より大きい場合、正の整数(通常1
)を返します。
この関数は、主にバイトスライスの順序付けが必要な場合(例: ソートアルゴリズムの比較関数)に使用されます。
bytes.Equal(a, b []byte) bool
この関数は、2つのバイトスライスa
とb
がバイト単位で完全に等しいかどうかを判定します。
- 両者の長さが異なれば、即座に
false
を返します。 - 長さが同じであれば、各バイトを比較し、すべてが一致すれば
true
、一つでも異なればfalse
を返します。
パフォーマンスの違い
bytes.Compare(a, b) == 0
とbytes.Equal(a, b)
は、結果的に同じ等価性チェックを行いますが、内部的な実装とパフォーマンス特性が異なります。
bytes.Compare
の挙動:bytes.Compare
は、等価性だけでなく、辞書順の順序関係も判定するため、比較対象のバイトスライスが等しい場合でも、最後までバイトを比較し続ける可能性があります(実装によるが、一般的に等価性チェックに特化した関数よりオーバーヘッドが大きい)。bytes.Equal
の挙動:bytes.Equal
は、等価性チェックに特化しているため、以下の最適化が施されています。- 長さのチェック: まず最初に2つのバイトスライスの長さを比較します。長さが異なれば、それ以上バイトを比較することなく
false
を返します。これは非常に高速なチェックです。 - ショートサーキット: バイト単位の比較中に、不一致のバイトが見つかった場合、その時点で比較を中止し
false
を返します。 - アセンブリ最適化: 多くのプラットフォームでは、
bytes.Equal
の内部実装に、CPUのSIMD命令などを利用したアセンブリ言語による高速な比較ルーチンが使用されています。これにより、C言語やGo言語で書かれた一般的なループによる比較よりもはるかに高速に動作します。
- 長さのチェック: まず最初に2つのバイトスライスの長さを比較します。長さが異なれば、それ以上バイトを比較することなく
したがって、等価性のみをチェックする場合は、bytes.Equal
を使用する方が、コードの意図が明確になるだけでなく、パフォーマンス面でも有利です。
gofmt
ツール
gofmt
は、Go言語のソースコードを自動的にフォーマットするツールです。Go言語の標準的なコーディングスタイルに準拠させることで、コードの可読性と一貫性を高めます。
gofmt
には-r
オプションがあり、これを使用すると指定したリライトルールに基づいてコードを変換できます。リライトルールは、パターン -> 置換
の形式で記述され、AST(抽象構文木)レベルでコードを変換します。
このコミットでは、以下のリライトルールが使用されました。
bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)
bytes.Compare(a, b) != 0 -> !bytes.Equal(a, b)
これにより、コードベース全体で安全かつ効率的にbytes.Compare
からbytes.Equal
への置き換えが行われました。
技術的詳細
このコミットは、Go言語の標準ライブラリ内の複数のパッケージ(cmd/gofmt
, pkg/bufio
, pkg/crypto/rsa
, pkg/encoding/asn1
, pkg/encoding/hex
, pkg/encoding/json
, pkg/exp/locale/collate
, pkg/go/doc
, pkg/math/big
)にわたる変更を含んでいます。これらの変更はすべて、バイトスライスの等価性チェックをbytes.Compare(a, b) == 0
またはbytes.Compare(a, b) != 0
から、それぞれbytes.Equal(a, b)
または!bytes.Equal(a, b)
に置き換えるものです。
この変更の技術的なメリットは以下の点に集約されます。
-
パフォーマンス最適化:
- ショートサーキット評価:
bytes.Equal
は、比較対象のバイトスライスの長さが異なる場合、または比較中に最初の不一致バイトが見つかった場合、即座に結果を返します。これはbytes.Compare
が辞書順比較のために、より多くのバイトを比較する必要がある場合があるのと対照的です。 - アセンブリ言語による実装: 多くのGoのランタイム環境では、
bytes.Equal
は基盤となるCPUアーキテクチャに最適化されたアセンブリ言語ルーチンを利用しています。これにより、C言語やGo言語で書かれた同等のループよりもはるかに高速なバイト比較が可能になります。特に大量のバイトスライスを頻繁に比較するようなケースでは、この最適化が顕著な性能向上をもたらします。 - コンパイラの最適化の機会:
bytes.Equal
のような特化された関数は、コンパイラがより積極的な最適化(例: インライン化)を行う機会を提供し、結果として生成される機械語コードの効率を高めることができます。
- ショートサーキット評価:
-
コードの意図の明確化:
bytes.Equal(a, b)
は、その名前が示す通り「a
とb
が等しいか」という意図を直接的に表現します。一方、bytes.Compare(a, b) == 0
は、「a
とb
の辞書順比較の結果が0であるか」という間接的な表現であり、等価性チェックという目的を読み取るには一歩思考が必要です。コードの可読性は、特に大規模なプロジェクトやチーム開発において、バグの発見やメンテナンスの容易さに直結します。
-
gofmt
による自動化:- この変更が手動ではなく
gofmt -r
コマンドによって自動的に行われたことは、Goエコシステムにおけるツールの成熟度と、大規模なコードベース全体にわたる一貫したリファクタリングの容易さを示しています。gofmt
のリライト機能は、特定のパターンに合致するコードを安全かつ網羅的に変換できるため、開発者が手動で変更を行う際の人為的ミスや見落としを防ぎます。
- この変更が手動ではなく
これらの技術的側面から、このコミットはGo言語のコードベース全体の品質とパフォーマンスを向上させるための重要な一歩と言えます。
コアとなるコードの変更箇所
このコミットでは、多数のファイルで同様の変更が行われています。以下に代表的な変更箇所をいくつか示します。
src/cmd/gofmt/gofmt_test.go
--- a/src/cmd/gofmt/gofmt_test.go
+++ b/src/cmd/gofmt/gofmt_test.go
@@ -56,7 +56,7 @@ func runTest(t *testing.T, in, out, flags string) {
return
}
- if got := buf.Bytes(); bytes.Compare(got, expected) != 0 {
+ if got := buf.Bytes(); !bytes.Equal(got, expected) {
t.Errorf("(gofmt %s) != %s (see %s.gofmt)", in, out, in)
d, err := diff(expected, got)
if err == nil {
src/pkg/crypto/rsa/pkcs1v15_test.go
--- a/src/pkg/crypto/rsa/pkcs1v15_test.go
+++ b/src/pkg/crypto/rsa/pkcs1v15_test.go
@@ -57,7 +57,7 @@ func TestDecryptPKCS1v15(t *testing.T) {
t.Errorf("#%d error decrypting", i)
}
want := []byte(test.out)
- if bytes.Compare(out, want) != 0 {
+ if !bytes.Equal(out, want) {
t.Errorf("#%d got:%#v want:%#v", i, out, want)
}
}
@@ -90,7 +90,7 @@ func TestEncryptPKCS1v15(t *testing.T) {
return false
}
- if bytes.Compare(plaintext, in) != 0 {
+ if !bytes.Equal(plaintext, in) {
t.Errorf("output mismatch: %#v %#v", plaintext, in)
return false
}
@@ -132,7 +132,7 @@ func TestEncryptPKCS1v15SessionKey(t *testing.T) {
t.Errorf("#%d error decrypting", i)
}
want := []byte(test.out)
- if bytes.Compare(key, want) != 0 {
+ if !bytes.Equal(key, want) {
t.Errorf("#%d got:%#v want:%#v", i, key, want)
}
}
@@ -176,7 +176,7 @@ func TestSignPKCS1v15(t *testing.T) {
}
expected, _ := hex.DecodeString(test.out)
- if bytes.Compare(s, expected) != 0 {
+ if !bytes.Equal(s, expected) {
t.Errorf("#%d got: %x want: %x", i, s, expected)
}
}
src/pkg/encoding/json/scanner_test.go
--- a/src/pkg/encoding/json/scanner_test.go
+++ b/src/pkg/encoding/json/scanner_test.go
@@ -92,7 +92,7 @@ func TestCompactBig(t *testing.T) {
t.Fatalf("Compact: %v", err)
}
b := buf.Bytes()
- if bytes.Compare(b, jsonBig) != 0 {
+ if !bytes.Equal(b, jsonBig) {
t.Error("Compact(jsonBig) != jsonBig")
diff(t, b, jsonBig)
return
@@ -118,7 +118,7 @@ func TestIndentBig(t *testing.T) {
t.Fatalf("Indent2: %v", err)
}
b1 := buf1.Bytes()
- if bytes.Compare(b1, b) != 0 {
+ if !bytes.Equal(b1, b) {
t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)")
diff(t, b1, b)
return
@@ -130,7 +130,7 @@ func TestIndentBig(t *testing.T) {
t.Fatalf("Compact: %v", err)
}
b1 = buf1.Bytes()
- if bytes.Compare(b1, jsonBig) != 0 {
+ if !bytes.Equal(b1, jsonBig) {
t.Error("Compact(Indent(jsonBig)) != jsonBig")
diff(t, b1, jsonBig)
return
コアとなるコードの解説
上記の変更箇所は、すべて同じパターンに従っています。
- 変更前:
bytes.Compare(sliceA, sliceB) != 0
またはbytes.Compare(sliceA, sliceB) == 0
- 変更後:
!bytes.Equal(sliceA, sliceB)
またはbytes.Equal(sliceA, sliceB)
例えば、src/cmd/gofmt/gofmt_test.go
の変更では、gofmt
のテストにおいて、フォーマット後のバイトスライスgot
と期待されるバイトスライスexpected
が等しくないことを確認する条件式が変更されています。
変更前:
if got := buf.Bytes(); bytes.Compare(got, expected) != 0 {
これは「got
とexpected
を比較した結果が0ではない(つまり等しくない)ならば」という意味です。
変更後:
if got := buf.Bytes(); !bytes.Equal(got, expected) {
これは「got
とexpected
が等しくないならば」という意味で、より直接的で読みやすくなっています。
同様に、src/pkg/crypto/rsa/pkcs1v15_test.go
やsrc/pkg/encoding/json/scanner_test.go
などのテストファイルでも、復号化されたデータやエンコードされたデータが期待値と一致するかどうかをチェックする際に、bytes.Compare
を用いた比較からbytes.Equal
を用いた比較へと置き換えられています。
これらの変更は、コードの意図をより明確にし、同時にbytes.Equal
が提供するパフォーマンス上の利点(特にショートサーキットやアセンブリ最適化)を享受することを目的としています。
関連リンク
- Go CL 7038051: https://golang.org/cl/7038051
参考にした情報源リンク
- Go言語
bytes
パッケージドキュメント: https://pkg.go.dev/bytes bytes.Equal
vsbytes.Compare
performance:- geeksforgeeks.org: https://www.geeksforgeeks.org/bytes-compare-function-in-golang/
- tutorialspoint.com: https://www.tutorialspoint.com/golang-bytes-equal-function
- datadoghq.com: https://www.datadoghq.com/blog/go-bytes-equal-vs-compare/
- sobyte.net: https://www.sobyte.net/post/2022-03/go-bytes-compare-vs-bytes-equal/
- endorama.dev: https://endorama.dev/posts/go-bytes-compare-vs-equal/