在Golang中,变量、函数、结构体等访问权限是由标识符的首字母大小写决定的,首字母小写的变量等在其它包中是不可见的,那么我们如何才能使用它们呢?这就需要使用一些骚操作了。
说起Golang的入口函数,我们都会认为是main.main,但是在Golang程序启动前,需要先执行runtime.main来初始化调度器、垃圾回收器等。在proc.go文件中我们可以找到这样一行代码:
//go:linkname main_main main.main
func main_main()
这个main_main函数没有函数体,在链接的时候,编译器会将其链接到我们自己的main函数,然后由runtime.main来调用。
上面的一行正是编译器指令,就行C/C++中的#开头的编译指令,在Go中使用 //go:
来表示,使用//go:linkname
指示在编译时将该函数链接到其它函数,也就是将main_main链接到main.main。
因此我们也可以使用这个编译器指令将函数链接到runtime中的私有函数。
memmove
// memmove copies n bytes from "from" to "to".
func memmove(to, from unsafe.Pointer, n uintptr)
memmove像是C语言中的memcpy函数,它可以进行内存拷贝,但是它是小写开头的,因此我们需要使用编译器指令才能使用:
在我们的文件中先进行一个声明:
然后我们就可以使用它了,我们可以实现一个切片拷贝的函数,在这里使用Go1.18的泛型:
最后,来测试一下这个函数与直接一个一个赋值来拷贝的方式进行一个对比测试:
// 使用memmove来拷贝切片
func SliceClone[T BaseType](s []T) []T {
res := make([]T, len(s), cap(s))
sptr := (*Slice)(unsafe.Pointer(&s))
resptr := (*Slice)(unsafe.Pointer(&res))
var tmp T
memmove(resptr.Ptr, sptr.Ptr, unsafe.Sizeof(tmp) * uintptr(sptr.Len))
resptr.Len = sptr.Len
return res
}
// 一个个赋值拷贝切片
func SliceCopy[T BaseType](s []T) []T {
res := make([]T, len(s), cap(s))
for i := 0; i < len(s); i++ {
res[i] = s[i]
}
return res
}
// 使用内置copy来拷贝切片
func SliceCopyBuildIn[T BaseType](s []T) []T {
res := make([]T, len(s), cap(s))
copy(res, s)
return res
}
测试:
func BenchmarkSliceClone(b *testing.B) {
sc := make([]int, 10000)
for i := 0; i < 10000; i++ {
sc[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SliceClone(sc)
}
}
func BenchmarkSliceCopy(b *testing.B) {
sc := make([]int, 10000)
for i := 0; i < 10000; i++ {
sc[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SliceCopy(sc)
}
}
func BenchmarkSliceCopyBuildIn(b *testing.B) {
sc := make([]int, 10000)
for i := 0; i < 10000; i++ {
sc[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SliceCopyBuildIn(sc)
}
}
结果:
$ go test -bench=.
goos: windows
goarch: amd64
pkg: code/go_scheduler_analyse
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkSliceClone-8 115420 10169 ns/op
BenchmarkSliceCopy-8 101098 11957 ns/op
BenchmarkSliceCopyBuildIn-8 118712 10069 ns/op
PASS
ok code/go_scheduler_analyse 4.111s
可以看到,可能还不如内置的copy速度快,挺鸡肋的。。。。
但是我们可以用来实现字符串和切片的拷贝: