go-linkname

假设有一个内部包,它提供一个方法如下:

package internal

import "fmt"

func print(msg string) {
 fmt.Println("[internal]", msg)
}

这个方法是内部使用的,它没有导出属性,因此它无法被其他外部包import,那既然如此,那有没有办法能在包外部去调用这个方法呢?答案是肯定的,只不过这个黑科技屏蔽了至少80%的Gopher的认知,它就是go:linkname。

01 go:linkname基础

在理解go:linkname之前,需要先理解Golang中独有的内部包internal。在Go1.14中加入了Go 1.4 “Internal” Packages
An import of a path containing the element “internal” is disallowed if the importing code is outside the tree rooted at the parent of the “internal” directory.
简单理解就是这个特殊的internal包只能被特定外部包导入:
包/a/b/c/internal/d/e/f只能被/a/b/c导入,而不能被/a/b/d导入。
包·$GOROOT/src/pkg/internal/xxx,只能被$GOROOT/src/导入。
$GOROOT/src/pkg/net/http/internal只能被net/http和net/http/*导入。
$GOPATH/src/mypkg/internal/foo只能被$GOPATH/src/mypkg导入。
在不违反这个原则的情况下,如何直接引用internal.print这个方法呢?

The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported "unsafe".

//go:linkname指令指示编译器使用importpath.name作为源代码中声明为localname的变量或函数的对象文件符号名。由于该指令可以破坏类型系统和包模块化,因此仅在导入了unsafe的文件中启用该指令。如下:

package main

import (
    _ "demo/internal"
 _ "unsafe"
)

//go:linkname Print demo/internal.print
func Print(data string)

func main() {
    Print("hello world")
}

这样就完成了go:linkname将方法实现指向一个外部包未导出的方法实现了。简单理解,就是通过go:linkname [local] [target]为当前这个local方法绑定其具体实现target。当直接运行时会提示missing body的错误。这是因为go build会增加-complete参数检查完整性,显然这个Print方法是没有包体的。因此,需要告知编译器绕过这个限制,在调用目录下增加xxx.s文件即可。最后整个文件目录如下:

.
├── go.mod
├── internal
│   └── internal.go
└── main.go

运行后输出:
# go run *.go
[internal] hello world

02 go:linkname进阶1:随机数

//go:linkname FastRand runtime.fastrand
func FastRand() uint32

runtime.fastrand和math.Rand都是伪随机数生成器,但不同的是runtime.fastrand是在当前goroutine上下文环境下的,因此在频繁调用过程中不需要加锁,所以,它的性能要比math.Rand要好得多。以下是两者的性能测试:

package main

import (
 "math/rand"
 "testing"
)

func BenchmarkMathRand(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _ = rand.Int()
 }
}

func BenchmarkRuntimeRand(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _ = FastRand()
 }
}

执行得到基准性能数据,可见runtime.Rand在性能上碾压math/rand:

Running tool: /usr/bin/go test -benchmem -run=^$ -coverprofile=/tmp/vscode-goVPugfM/go-code-cover -bench . demo

goos: linux
goarch: amd64
pkg: demo
BenchmarkMathRand     91929873         12.8 ns/op        0 B/op        0 allocs/op
BenchmarkRuntimeRand  316043065          3.71 ns/op        0 B/op        0 allocs/op
PASS
coverage: 0.0% of statements
ok   demo 2.750s

03 go:linkname进阶2:时间戳

//go:linkname nanotime1 runtime.nanotime1
func nanotime1() int64

time.Now()和runtime.nanotime1()两者都是获取时间戳,但是time.Now()其底层调用了runtime.walltime1和runtime.nanotime,分别获取时间戳和程序运行时间。而后者只需要单独获取时间戳。因此,在某些场景下,比如统计耗时,那么就可以直接通过nanotime1()获得更好的性能。以下为基准测试代码:

package main

import (
 "testing"
 "time"
)

func BenchmarkTimeNow(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _ = time.Now()
 }
}

func BenchmarkRuntimeNanotime(b *testing.B) {
 for i := 0; i < b.N; i++ {
  _ = nanotime1()
 }
}

执行得到基准性能数据,可见runtime.nanotime1()在性能上碾压time.Now():

Running tool: /usr/bin/go test -benchmem -run=^$ -coverprofile=/tmp/vscode-goVPugfM/go-code-cover -bench . demo

goos: linux
goarch: amd64
pkg: demo
BenchmarkTimeNow          10140447        117 ns/op        0 B/op        0 allocs/op
BenchmarkRuntimeNanotime  22762699         55.7 ns/op        0 B/op        0 allocs/op
PASS
coverage: 0.0% of statements
ok   demo 2.635s

04 总结

在理解go:linkname的原理后,我们就可以举一反三,针对特定场景来优化代码,提高性能瓶颈。这个指令虽然可以绕过限制访问到其他包的私有方法,但不失为一种巧技。在阅读golang的源码时也可以看到大量的go:linkname指令,因此有助于我们更好理解golang代码的底层逻辑。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 目录 1.go 各种代码运行 2.go 在线编辑代码运行 3.通过 Gob 包序列化二进制数据 4.使用 ...
    杨言锡阅读 1,156评论 0 1
  • 现如今即便是个人开发的一般程序,可能其包含的函数都超过了一万个,这些函数代码一般都由他人编写并打包为“包”或者“模...
    左蓝阅读 6,515评论 3 15
  • File types(文件类型) go命令检查目录中特定文件的集合。它根据文件的扩展名表示要检查的文件。这些扩展名...
    Cxb168阅读 1,781评论 0 0
  • 指针 指针就是地址,指针变量就是存储地址的变量 *p : 解引用,间接引用 栈帧:用来给函数运行提供内存空间,取内...
    雪上霜阅读 324评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,997评论 19 139