|
|
|
|
|
|
d****n 发帖数: 1637 | 1 http://zhuanlan.zhihu.com/thought/20010926
达达 · 6 天前
今天我要教大家一些无用技能,也可以叫它奇技淫巧或者黑魔法。用得好可以提升性能
,用得不好就会招来恶魔,嘿嘿。
黑魔法导论
为了让大家在学习了基础黑魔法之后能有所悟,在必要的时候能创造出本文传授之外的
属于自己的魔法,这里需要先给大家打好基础。
学习Go语言黑魔法之前,需要先看清Go世界的本质,你才能获得像Neo一样的能力。
在Go语言中,Slice本质是什么呢?是一个reflect.SliceHeader结构体和这个结构体中
Data字段所指向的内存。String本质是什么呢?是一个reflect.StringHeader结构体和
这个结构体所指向的内存。
在Go语言中,指针的本质是什么呢?是unsafe.Pointer和uintptr。
当你清楚了它们的本质之后,你就可以随意的玩弄它们,嘿嘿嘿。
第一式 - 获得Slice和String的内存数据
让我小试身手,你有一个CGO接口要调用,需要你把一个字符串数据或者字节数组数据
从Go这边传递到C那边,比如像这个:mysql/conn.go at master · funny/mysql ·
GitHub
查了各种教程和文档,它们都告诉你要用C.GoString或C.GoBytes来转换数据。
但是,当你调用这两个函数的时候,发生了什么事情呢?这时候Go复制了一份数据,然
后再把新数据的地址传给C,因为Go不想冒任何风险。
你的C程序只是想一次性的用一下这些数据,也不得不做一次数据复制,这对于一个性
能癖来说是多麽可怕的一个事实!
这时候我们就需要一个黑魔法,来做到不拷贝数据又能把指针地址传递给C。
// returns &s[0], which is not allowed in go
func stringPointer(s string) unsafe.Pointer {
p := (*reflect.StringHeader)(unsafe.Pointer(&s))
return unsafe.Pointer(p.Data)
}
// returns &b[0], which is not allowed in go
func bytePointer(b []byte) unsafe.Pointer {
p := (*reflect.SliceHeader)(unsafe.Pointer(&b))
return unsafe.Pointer(p.Data)
}
以上就是黑魔法第一式,我们先去到Go字符串的指针,它本质上是一个*reflect.
StringHeader,但是Go告诉我们这是一个*string,我们告诉Go它同时也是一个unsafe.
Pointer,Go说好吧它是,于是你得到了unsafe.Pointer,接着你就躲过了Go的监视,
偷偷的把unsafe.Pointer转成了*reflect.StringHeader。
有了*reflect.StringHeader,你很快就取到了Data字段指向的内存地址,它就是Go保
护着不想给你看到的隐秘所在,你把这个地址偷偷告诉给了C,于是C就愉快的偷看了Go
的隐私。
第二式 - 把[]byte转成string
你肯定要笑,要把[]byte转成string还不简单?Go语言初学者都会的类型转换语法:[]
byte(str)。
但是你知道这么做的代价吗?既然我们能随意的玩弄SliceHeader和StringHeader,为
什么我们不能造个string给Go呢?Go的内部会不会就是这么做的呢?
先上个实验吧:
package labs28
import "testing"
import "unsafe"
func Test_ByteString(t *testing.T) {
var x = []byte("Hello World!")
var y = *(*string)(unsafe.Pointer(&x))
var z = string(x)
if y != z {
t.Fail()
}
}
func Benchmark_Normal(b *testing.B) {
var x = []byte("Hello World!")
for i := 0; i < b.N; i ++ {
_ = string(x)
}
}
func Benchmark_ByteString(b *testing.B) {
var x = []byte("Hello World!")
for i := 0; i < b.N; i ++ {
_ = *(*string)(unsafe.Pointer(&x))
}
}
这个实验先证明了我们可以用[]byte的数据造个string给Go。接着做了两组Benchmark
,分别测试了普通的类型转换和伪造string的效率。
结果如下:
$ go test -bench="."
PASS
Benchmark_Normal 20000000 63.4 ns/op
Benchmark_ByteString 2000000000 0.55 ns/op
ok github.com/idada/go-labs/labs28 2.486s
哟西,显然Go这次又为了稳定性做了些复制数据之类的事情了!这让性能癖怎么能忍受
! |
|
|
|
|
|