- 市川燿
- 約 3,700 文字
- 1,000 View
目次
はじめに
Go1.24リリース連載 の Go 1.24で新たに追加されたstring bytes パッケージの関数、およびencodingパッケージに追加されたインターフェースTextAppender、BinaryAppenderとその実装について扱います。
strings, bytesに新規に追加された関数
Go 1.24で strings bytes パッケージに以下の関数がそれぞれ以下の関数が追加されました。
これらの関数の返り値は strings パッケージのほうは、iter.Seq[string]、bytesパッケージのほうはiter.Seq[[]byte]となっています。strings bytes パッケージで同様の関数のため strings パッケージをメインに解説します。
新規追加関数実装サンプル
今回新たに追加された関数サンプルを示します。
package main |
それぞれの関数の特徴をまとめると以下のようになります。
| 関数 | 区切り文字 | 区切り文字の除去有無 |
|---|---|---|
| Lines | \n (改行コード) |
残す |
| SplitSeq | 第2引数で指定した文字 | 除去する |
| SplitAfterSeq | 第2引数で指定した文字 | 残す |
| FieldsSeq | スペース (unicode.IsSpace) |
除去する |
| FieldsFuncSeq | 第2引数で指定した関数で trueになったrune |
除去する |
既存パッケージ、関数との関係
返り値が[]stringのものがありましたが、今回新規に iter.Seq の返り値の関数が追加された形になります。iter パッケージは1.23で新規に追加されたパッケージです。iter パッケージについては1.23リリースでの弊社解説記事をご参照ください。
| 1.24新規関数 | []string返り値の対応関数 |
|---|---|
| Lines | (なし) |
| SplitSeq | Split |
| SplitAfterSeq | SplitAfter |
| FieldsSeq | Fields |
| FieldsFuncSeq | FieldsFunc |
iter.Seqを返すことになったことによりrangeでのループ処理でインデックスの返り値が不要になりました。
package main |
また、iter.Seqが返り値のため、iter.Pullを利用することで next()の形でループ処理もできます。
package main |
encoding.TextAppender, encoding.BinaryAppender
1.24からencodingパッケージに encoding.TextAppender, encoding.BinaryAppenderのインターフェースが追加されました。
type TextAppender interface { |
似たようなインターフェースとしてencoding.TextMarshaler, encoding.BinaryMarshalerが以前から存在します。
type TextMarshaler interface { |
issueによると、すぐ不要になる有効期限が短い文字列を生成ことになり、無駄が発生していることの改善のようです。あらかじめ、キャパシティを確保したスライスを用意し引数として渡すことにより、新規に文字列を作成せず効率よくスライスに追記できるようです。
似たような解説を1.22リリース時の弊社記事でも解説しているため、合わせてお読みください。
インターフェース追加に合わせて net/netipや、timeなどで実装が追加されています。
encoding.TextAppenderの内部処理
例えば、net/netipの Addrの内部処理を見てみると、以下のように元のbyteスライス retに対しappendを行い、追記するように実装されており効率的な処理になっています。
func (ip Addr) appendTo4(ret []byte) []byte { |
TextMarshalerとTextAppenderの性能比較
encoding.TextAppenderによって無駄な処理が改善させることなので、いくつかのパッケージでencoding.TextMarshaler(MarshalText)encoding.TextAppender(AppendText)とを比較してみました。
また、執筆時点での最新バージョン(1.23.5)とも比較しました。
- 検証対象
- net/netip.Addr
- AppendText (1.24のみ)
- AppendTo (AppendTextのエイリアス)
- MarshalText
- time.Time
- AppendText (1.24のみ)
- MarshalText
- regexp.Regexp
- AppendText (1.24のみ)
- MarshalText
- net/netip.Addr
また、appendに必要なメモリは事前に確保した状態で計測しています。
検証プログラム
package main |
//go:build go1.24 |
package main |
//go:build go1.24 |
検証結果
以下が結果になります。テストは1000連続関数を実行する処理を1セットとして、それぞれ5回試行しました。
表の値は5回の平均値となります。
実行結果ログ
go.exe test -bench . -benchmem -count 5 |
go.exe test -bench . -benchmem -count 5 |
net/netip.Addr
| 項目 | 1.24rc2 AppendText |
1.24rc2 AppendTo |
1.24rc2 MarshalText |
1.23.5 AppendTo |
1.23.5 MarshalText |
|---|---|---|---|---|---|
| 実行速度 (ns/op) |
10815.6 | 12484.8 | 37264.0 | 10382.4 | 29839.2 |
| アロケーション サイズ (B/op) |
0 | 0 | 16000 | 0 | 16000 |
| アロケーション 回数 (allocs/op) |
0 | 0 | 1000 | 0 | 1000 |
time.Time
| 項目 | 1.24rc2 AppendText |
1.24rc2 MarshalText |
1.23.5 MarshalText |
|---|---|---|---|
| 実行速度 (ns/op) |
40889.4 | 47967.8 | 79187.2 |
| アロケーション サイズ (B/op) |
0 | 0 | 48000 |
| アロケーション 回数 (allocs/op) |
0 | 0 | 1000 |
regexp.Regexp
| 項目 | 1.24rc2 AppendText |
1.24rc2 MarshalText |
1.23.5 MarshalText |
|---|---|---|---|
| 実行速度 (ns/op) |
2384.6 | 30566.8 | 2557.0 |
| アロケーション サイズ (B/op) |
0 | 16000 | 0 |
| アロケーション 回数 (allocs/op) |
0 | 1000 | 0 |
結果まとめ
- net/netip.Addr
- AppendText、AppendToではあまり差が見られず、メモリアロケーションは発生しなかった
- MarshalTextではメモリアロケーションが毎回発生し、AppendText、AppendToと比較し3倍ほど遅かった
- バージョン1.24rc2と1.23.5での際はあまり見られなかった
- time.Time
- 1.24rc2ではAppendTextのほうがややMarshalTextよりも高速だった(ともにメモリアロケーションなし)
- MarshalTextは1.24rc2と1.23.5で大きな差異があり、1.23.5ではメモリアロケーションが毎回発生し、速度も1.24rc2より2倍程度遅かった
- regexp.Regexp
- 1.24rc2のAppendTextと1.23.5のMarshalTextではあまり差が見られず、メモリアロケーションは発生しなかった
- 1.24rc2のMarshalTextは他と比較し10倍程度遅く、また毎回メモリアロケーションが発生した
結果考察
バージョンアップによって結果が悪くなったregexp.Regexpについて考察します。
1.23.5, 1.24rc2のMarshalText, AppendTextの実装は以下のようになっています。
// MarshalText implements [encoding.TextMarshaler]. The output |
// AppendText implements [encoding.TextAppender]. The output |
1.24rc2ではMarshalText内でAppendTextを利用するように変更になっています。
そのためMarshalTextの処理が実質的に…
return append([]byte(nil), []byte(re.String())...), nil |
…になります。
1.23.5ではそのままバイトスライスにキャストして返していた処理が、AppendTextを利用するようになりバイトスライスに一旦詰め替える処理変更されたため、遅くなりメモリアロケーションも発生することになったようです。
また、1.24rc2のAppendTextの処理内でre.String()を呼んでいるため、一旦全量を作成しappendする形になっているため、1.24rc2のAppendTextと1.23.5のMarshalTextの性能が変わらないようです。(re.Stringの処理はRegexp.exp値を返しているだけで新規に文字列処理は行っていないため、AppnedTextの恩恵は薄いようです。)
// String returns the source text used to compile the regular expression. |
net/netip.Addrについては、1.24で encoding.TextAppender の形式に対応しましたが、特に性能的な影響なし、time.Timeについては、encoding.(Binary|Text)Appender対応コミット(819b1b4, 2b664d5)を確認してみましたが、変更前後で性能的な差が無いように見えため本件とは別で機能改善されたようです。
おわりに
Go 1.24で新たに追加されたstring, bytesパッケージの関数、およびencodingパッケージに追加されたインターフェースTextAppender、BinaryAppenderとその実装について扱いました。
Go1.24リリース連載でGo 1.24でアップデートした他の内容についても解説しているため、ぜひ合わせてご覧ください。