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

  • 110
    いいね
  • 5
    コメント

追記: 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という訳ではありません。