• Golang骚操作——使用runtime私有函数


    在Golang中,变量、函数、结构体等访问权限是由标识符的首字母大小写决定的,首字母小写的变量等在其它包中是不可见的,那么我们如何才能使用它们呢?这就需要使用一些骚操作了。

            说起Golang的入口函数,我们都会认为是main.main,但是在Golang程序启动前,需要先执行runtime.main来初始化调度器、垃圾回收器等。在proc.go文件中我们可以找到这样一行代码:

    //go:linkname main_main main.main
    func main_main()
    
    • 1
    • 2

            这个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)
    
    • 1
    • 2

            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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    测试:

    
    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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    结果:

    $ 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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,可能还不如内置的copy速度快,挺鸡肋的。。。。
    但是我们可以用来实现字符串和切片的拷贝:
    在这里插入图片描述

  • 相关阅读:
    Django静态文件&媒体文件&文件上传
    搭建 AI 图像生成器 (SAAS) php laravel
    343. 整数拆分
    Go 1.22 中的 for 循环新特性详解
    面试算法31:最近最少使用缓存
    TypeScript入门与编译的配置
    2022.08.01 洛谷P7845 「dWoi R2」Change
    CSS学习(3)-浮动和定位
    电脑屏幕怎么录制?5 个最佳免费录屏软件
    UI for Apache Kafka
  • 原文地址:https://blog.csdn.net/Peerless__/article/details/126483647