Edited at

golang で string を []byte にキャストするとメモリコピーが走ります

More than 5 years have passed since last update.


概要

string を byte スライスにしたり,またその逆だったりをおこなうのに,


string2byteslice

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 にいました.


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 へのキャストだと,もしかしたらメモリコピーしなくてもすむんじゃないかと思って調べてみた.


src/pkg/runtime/string.goc

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 のキーでキャストしているようなのにしかこの最適化は適用されないっぽい.