いまはもっと賢くなってる。最適化バンザイ
https://medium.com/a-journey-with-go/go-string-conversion-optimization-767b019b75ef
概要
string を byte スライスにしたり,またその逆だったりをおこなうのに,
vec := []byte("hello")
なんてコードをよく書きます.string は読み込み専用のスライスみたいな物だという認識だったので,キャストしても,ポインタがコピーされるだけで,必要になったらコピーされるだろうぐらいに思ってたんですが,調べてみたらメモリがまるっとコピーされるのでパフォーマンスに影響しそうなときは要注意です.
詳細
string を byte スライスにキャストするプログラムを書いて,アセンブリコードを吐かせてみました.
1 package main
2
3 import "fmt"
4
5 func main() {
6 str := "hello, world!"
7 vec := []byte(str)
8 fmt.Println(vec)
9 }
7行目でキャストしてます.下記の要領でアセンブリを吐き出します.
% go tool 8g -S stringbytes.go
なかみをのぞいてみると,7行目でキャストしているコードに対応するところで
runtime.stringtoslicebyte()
というのが呼ばれています.
0x0039 00057 (stringbytes.go:7) CALL ,runtime.stringtoslicebyte(SB)
探してみたところ,こいつは,src/pkg/runtime/string.goc にいました.
func stringtoslicebyte(s String) (b Slice) {
uintptr cap;
cap = runtime·roundupsize(s.len);
b.array = runtime·mallocgc(cap, 0, FlagNoScan|FlagNoZero);
b.len = s.len;
b.cap = cap;
runtime·memmove(b.array, s.str, s.len);
if(cap != b.len)
runtime·memclr(b.array+b.len, cap-b.len);
}
どうやらメモリを確保してコピーしているようです.なるほど・・・.
感想
strings
パッケージの ReadByte()
とか使ったり,そもそも byte スライスで扱うように見直してみたりすることで,むやみにメモリコピー&GCが走るのを抑制できそうです.string をキャストする際には一呼吸置いて考える必要がありそうです.
追記:byte スライスから string へのキャスト
byte スライスから string へのキャストだと,もしかしたらメモリコピーしなくてもすむんじゃないかと思って調べてみた.
func slicebytetostring(b Slice) (s String) {
void *pc;
if(raceenabled) {
pc = runtime·getcallerpc(&b);
runtime·racereadrangepc(b.array, b.len, pc, runtime·slicebytetostring);
}
s = gostringsize(b.len);
runtime·memmove(s.str, b.array, s.len);
}
func slicebytetostringtmp(b Slice) (s String) {
void *pc;
if(raceenabled) {
pc = runtime·getcallerpc(&b);
runtime·racereadrangepc(b.array, b.len, pc, runtime·slicebytetostringtmp);
}
// Return a "string" referring to the actual []byte bytes.
// This is only for use by internal compiler optimizations
// that know that the string form will be discarded before
// the calling goroutine could possibly modify the original
// slice or synchronize with another goroutine.
// Today, the only such case is a m[string(k)] lookup where
// m is a string-keyed map and k is a []byte.
s.str = b.array;
s.len = b.len;
}
やっぱりコピーされるが,コンパイラの最適化でなんかやりたい雰囲気は見られた.
いまのところ,map のキーでキャストしているようなのにしかこの最適化は適用されないっぽい.