概要
Go言語は、ガベージコレクタ(GC)と強力な型システムによって守られた「安全な」言語です。しかし、1秒間に数千万リクエストを捌くような極限の環境では、その安全性がボトルネックになることがあります。
今回は、型安全というプロテクションをあえて外す禁断のパッケージ「unsafe」を使用し、メモリコピーを一切発生させずに []byte を string に変換する「ゼロコピー変換」の破壊的なパフォーマンスを実測しました。
実行環境
goos: linux
goarch: amd64
cpu: 12th Gen Intel(R) Core(TM) i7-1260P
実装
検証には以下の main_test.go を使用しました。標準的な変換(コピー発生)と、unsafeによるポインタ読み替え(ゼロコピー)を比較しています。
package main
import (
"testing"
"unsafe"
)
var data = make([]byte, 1024)
// 標準的な変換:新しいメモリを確保してコピーする
func BenchmarkStandardConversion(b *testing.B) {
for i := 0; i < b.N; i++ {
s := string(data)
if len(s) != 1024 {
b.Fatal("error")
}
}
}
// unsafeを使った変換:メモリの中身はそのままで型解釈だけを変える
func BenchmarkUnsafeConversion(b *testing.B) {
for i := 0; i < b.N; i++ {
s := *(*string)(unsafe.Pointer(&data))
if len(s) != 1024 {
b.Fatal("error")
}
}
}
検証
最新の第12世代Intel CPU環境で、メモリ割り当ての詳細を含めてベンチマークを実行しました。
実行環境:12th Gen Intel(R) Core(TM) i7-1260P / Linux amd64
実行コマンド:go test -bench . -benchmem
結果
実測結果は、予想を遥かに上回る「異次元」の数字を叩き出しました。
BenchmarkStandardConversion:
- 試行回数: 7,591,849 回
- 処理速度: 161.9 ns/op
- メモリ割当: 1024 B/op (1 allocs/op)
BenchmarkUnsafeConversion:
- 試行回数: 1,000,000,000 回(計測上限に到達)
- 処理速度: 0.2191 ns/op
- メモリ割当: 0 B/op (0 allocs/op)
【分析】
処理速度の差は約740倍。標準版が1秒間に約760万回しか回らないのに対し、unsafe版は計測上限である「10億回」を軽々と走り抜けました。
また、メモリ割り当てが完全に「0」である点も重要です。ポインタの住所を書き換えるだけなので、GC(ガベージコレクタ)に一切の仕事をさせません。
実用事例
実務のアプリケーション層で unsafe を見かけることは稀ですが、以下のような「1%の高速化が数千万円の価値を生む現場」では重宝されています。
- ByteDance (sonic)
TikTokを運営するByteDanceが開発した、JITとSIMDを駆使した爆速JSONパーサーです。内部でunsafeを多用してゼロコピーを実現しています。
URL: github.com/bytedance/sonic
ポイント: READMEの「Benchmarks」を見ると、標準のencoding/jsonとの圧倒的な差が確認できます。
- fasthttp
Goの標準net/httpをあえて使わず、unsafeによるゼロコピー変換で極限までパフォーマンスを上げた有名なHTTPライブラリです。
URL: github.com/valyala/fasthttp
- Cloudflare (低レイヤでの活用)
CloudflareはGoをネットワークの深い階層で利用しており、unsafeが引き起こすエッジケースなバグ(コンパイラレベルの挙動)についても高度な発信をしています。
URL: How we found a bug in Go's arm64 compiler (Cloudflare Blog)
ポイント: unsafe.Pointerを駆使してスタックやメモリを覗き込む過程で発見された、非常にマニアックかつ重要なバグ調査の記録です。
まとめ
unsafeによるゼロコピーは、安全性を代償に「10億回の世界」を提示してくれました。
不変(immutable)であるべき文字列を書き換え可能にしてしまうリスクはありますが、この圧倒的なパフォーマンスを知っていることは、エンジニアとして「最後の手段」を理解しているのと同義です。
Rustを使えばいいじゃんとも思いましたが、Goで運用しているシステムにRustを導入したくない。負債となってしまう。と考える現場もあると思います。どうしても速度を上げたい!そのときは、unsafeを思い出してはいかがでしょうか?ステージングでのテストは入念にやる必要はあるかもですが。