当前位置: 首页 > 科技观察

你知道Go源代码中的这些--go-指令吗?

时间:2023-03-12 21:44:46 科技观察

大家好,我是炸鱼。如果平时有看源码的习惯,一定会找到的。咦,为什么有的方法上面总是写着//go:之类的指令。它们的用途是什么?今天就和大家一起揭开它们的面纱,为大家介绍一下它们的作用。go:linkname//go:linknamelocalnameimportpath.name该指令指示编译器使用importpath.name作为源代码中声明为localname的变量或函数的目标文件符号名称。但是因为这个指令,类型系统和包的模块化可以被打破,只有在引用了不安全包的情况下才能使用。简单的说,importpath.name是localname的符号别名,编译器会实际调用localname。使用它的前提是使用unsafe包才能使用。casetime/time.go...funcnow()(secint64,nsecint32,monoint64)runtime/timestub.goimport_"unsafe"//forgo:linkname//go:linknametime_nowtime.nowfunctime_now()(secint64,nsecint32,monoint64){sec,nsec=walltime()returnssec,nsec,nanotime()-startNano}这种情况下可以看到time.now,没有具体实现。乍一看可能会令人困惑。这时候建议大家全局搜索一下源码,会发现其实是在runtime.time_now中。通过前面的使用说明,我们可以知道,在runtime包中,我们声明了time_now方法是time.now的一个符号别名。并且在文件头中引入了unsafe来实现前置条件。go:noescape//go:noescape该指令指定了下一个有语句但没有函数体(意味着实现可能不是Go)的函数,不允许编译器对其进行逃逸分析。通常,该指令用于内存分配优化。编译器默认会进行逃逸分析,会通过规则判断一个变量是分配在堆上还是分配在栈上。但一切都出乎意料。虽然有些函数逃脱了分析,但它们存储在堆上。但对我们来说,这很特别。我们可以使用go:noescape指令强制编译器将其分配到函数栈上。case//memmovecopiesnbytesfrom"from"to"to".//inmemmove_*.s//go:noescapefuncmemmove(to,fromunsafe.Pointer,nuintptr)我们观察这个case,它满足了这条指令的共同特征。如下:memmove_*.s:只有语句,没有正文。它的主体由底层程序集memmove:function函数实现,在栈上的处理性能会更好简单的说,这个函数跳过了堆栈溢出检查。case//go:nosplitfunckey32(p*uintptr)*uint32{return(*uint32)(unsafe.Pointer(p))}go:nowritebarrierrec//go:nowritebarrierrec这条指令的意思是当编译器遇到写屏障时,会生成错误,并且允许递归。即这个函数调用的其他函数如果有写屏障也会报错。简单的说就是对writebarrier的处理,防止其死循环。case//go:nowritebarrierrecfuncgcFlushBgCredit(scanWorkint64){...}go:yeswritebarrierrec//go:yeswritebarrierrec该指令与go:nowritebarrierrec相反。在go:nowritebarrierrec指令标记的函数上,遇到写屏障会产生错误。相反,编译器会在遇到go:yeswritebarrierrec指令时停止。case//go:yeswritebarrierrecfuncghelper(){...}go:noinline这条指令表示该函数禁止内联。案例//go:noinlinefuncunexportedPanicForTesting(b[]byte,iint)byte{returnb[i]}我们来看这个案例,直接通过索引获取值,逻辑比较简单。如果不加go:noinline,编译器会优化inline。显然,内联有其优点和缺点。该指令就是提供这种特殊处理。go:norace//go:norace该指令表示禁止种族检测。一种常见的形式是在启动时执行gorun-race,可以检测应用中是否存在双向数据竞争,非常有用。case//go:noracefuncforkAndExecInChild(argv0*byte,argv,envv[]*byte,chroot,dir*byte,attr*ProcAttr,sys*SysProcAttr,pipeint)(pidint,errErrno){...}go:notinheap//go:notinheap该指令常用于类型声明中,表示该类型不允许从GC堆中分配内存。它经常在运行时用作较低级别的内部结构,避免调度程序和内存分配中的写障碍,并提高性能。case//notInHeapisoff-heapmemoryallocatedbyalower-levelallocator//likesysAllocopsistentAlloc.////Ingeneral,it'sbettertoususerealtypesmarkedasgo:notinheap,//butthisservesasagenerictypeforsituationswherethatisn't//notpossible(like{intheallocators)./////go:notinheapstructtype引入了一些通用指令套,让大家有一个整体的系统认识。这些指令在Go项目中通常不会用到,常见的瓶颈可能更多在其自身的应用中。不过了解了这些机制之后,对你阅读Go语言底层源码,理解运行机制会有很大的帮助:)