golang で string を []byte にキャストしてもメモリコピーが走らない方法を考えてみる

More than 1 year has passed since last update.


追記: go1.9 でこの方法を使うと安定動作しない可能性があります


コピーしない方法は無くはないんじゃないかなーと思いまして筆を取りました。

golang では []byte(str) をコンパイルすると runtime.gostringtoslicebyte によりバイト配列にコピーされます。


runtime.go

func stringtoslicebyte(s string) []byte {

b := rawbyteslice(len(s))
copy(b, s)
return b
}

しかし実際に golang は内部で utf-8 な文字列を扱っている訳なので、そのまま取り出す方法さえあればいいはずなんですよね。

そこで unsafe の登場です...

これを使うと自らプログラムを破壊出来るんだ。

内部のバイト列はもちろん...


stringbytes.go

package main

import (
"fmt"
"unsafe"
)

func main() {
str := "hello, world!"
vec := *(*[]byte)(unsafe.Pointer(&str))
fmt.Println(vec)
}


ポインタ演算まで出来ちゃう!


pointer.go

package main

import (
"unsafe"
)

type foo struct {
k int64
v int64
}

func main() {
f := &foo{3,4}

// unsafe.Pointer() で匿名ポインタにして
// uintptr() で演算可能にして
// +8 バイト(64bit)足して
// unsafe.Pointer で匿名ポインタに戻して
// そこにはフィールド v があるはずなので *int64 にキャストして
// デリファレンスすれば出来上がり
*(*int64)(unsafe.Pointer((uintptr(unsafe.Pointer(f))+8))) = 5 // グヒヒ

println(f.v) // 5
}


ちなみに上記の stringbytes.gogo tool 8g -S stringbytes.go でアセンブラにしてみると...

    0x001c 00028 (stringbytes2.go:8)    LEAL    go.string."hello, world!"+0(SB),BX

0x0022 00034 (stringbytes2.go:8) MOVL (BX),CX
0x0024 00036 (stringbytes2.go:8) MOVL 4(BX),BP
0x0027 00039 (stringbytes2.go:9) MOVL CX,"".str+24(SP)
0x002b 00043 (stringbytes2.go:9) MOVL CX,(SP)
0x002e 00046 (stringbytes2.go:9) MOVL BP,"".str+28(SP)
0x0032 00050 (stringbytes2.go:9) MOVL BP,4(SP)
0x0036 00054 (stringbytes2.go:9) PCDATA $0,$0
0x0036 00054 (stringbytes2.go:9) CALL ,runtime.stringtoslicebyte(SB)
0x003b 00059 (stringbytes2.go:9) MOVL 8(SP),DX
0x003f 00063 (stringbytes2.go:9) MOVL 12(SP),CX
0x0043 00067 (stringbytes2.go:9) MOVL 16(SP),AX

※部分抜粋

この様にコードに落ちていたのが

    0x001c 00028 (stringbytes.go:9) LEAL    go.string."hello, world!"+0(SB),BX

0x0022 00034 (stringbytes.go:9) MOVL (BX),BP
0x0024 00036 (stringbytes.go:9) MOVL BP,"".str+24(SP)
0x0028 00040 (stringbytes.go:9) MOVL 4(BX),BP
0x002b 00043 (stringbytes.go:9) MOVL BP,"".str+28(SP)
0x002f 00047 (stringbytes.go:10) LEAL "".str+24(SP),BX
0x0033 00051 (stringbytes.go:10) CMPL BX,$0
0x0036 00054 (stringbytes.go:10) JEQ $1,232
0x003c 00060 (stringbytes.go:10) MOVL (BX),DX
0x003e 00062 (stringbytes.go:10) MOVL 4(BX),CX
0x0041 00065 (stringbytes.go:10) MOVL 8(BX),BP

こーんな形になるー!

これでコピー無しで文字列が扱える!

プログラムも高速になる!

だれも困らない!

みんなハッピー!

みんなどんどん使っていこう!

unsafe をご使用になる時は、用法・用量を守り正しくお使い下さい。race conditionでもあり、あらゆるケースにおいてもGC-safeという訳ではありません。