在座的看看官,首先,本文毫无技术含量,只是作者做个放置忘记的参考笔记用的,所以,你们还是别往下看了!谢谢!
我们如果查看过go源码的一般都会看到有 //go: xxx 之类的注释。那么这种注释到底是干嘛用的呢?其实啊,这种注释都是有特殊含义的,一般就是为了提醒 编译器对代码做相应的处理的。来,下面我们就说说这些特殊的注释吧。
//go:noinline
表示不做内联
(什么是内联? Inline,是在编译期间发生的,将函数调用调用处替换为被调用函数主体的一种编译器优化手段)
内联的好坏:
好处:
- 减少函数调用的开销,提高执行速度。
- 复制后的更大函数体为其他编译优化带来可能性,如 过程间优化
- 消除分支,并改善空间局部性和指令顺序性,同样可以提高性能。
坏处:
- 代码复制带来的空间增长。
- 如果有大量重复代码,反而会降低缓存命中率,尤其对 CPU 缓存是致命的。
简单来说,对于短小而且工作较少的函数,使用内联是有效益的
自动内联的例子:
在命令行编译: go tool compile -S inline.go > inline.s
那么我们不需要做内联的时候,则直接在 appendStr( ) 函数上加上注释 //go:noinline
执行编译之后: go tool compile -S noinline.go > noinline.s
//go:nosplit
跳过栈溢出检测。
什么是栈溢出?
一个 Goroutine 的起始栈大小是有限制的,且比较小的,才可以做到支持并发很多 Goroutine,并高效调度。
stack.go 源码中可以看到,_StackMin 是 2048 字节,也就是 2k,它不是一成不变的,当不够用时,它会动态地增长。
那么,必然有一个检测的机制,来保证可以及时地知道栈不够用了,然后再去增长。
回到话题,nosplit 就是将这个跳过这个机制。
显然地,不执行栈溢出检查,可以提高性能,但同时也有可能发生 stack overflow 而导致编译失败 。
//go:noescape
禁止逃逸。
注意: 它必须指示一个只有声明没有主体的函数
什么是逃逸?Go 相比 C、C++ 是内存更为安全的语言,主要一个点就体现在它可以 自动地将超出自身生命周期的变量,从函数栈转移到堆中 ,逃逸就是指这种行为。
最显而易见的好处是,GC 压力变小了。
因为它已经告诉编译器,下面的函数无论如何都不会逃逸,那么当函数返回时,其中的资源也会一并都被销毁。
不过,这么做代表会绕过编译器的逃逸检查,一旦进入运行时,就有可能导致严重的错误及后果。
//go:norace
跳过竞态检测。 除了减少编译时间,没鸡毛用。
//go:notinheap
用于类型声明
表示 这个类型不允许从 GC 堆上进行申请内存
在运行时中 常用其来做较低层次的内部结构,避免调度器和内存分配中的写屏障。能够提高性能。
//go:nowritebarrierrec 和
//go:yeswritebarrierrec
搞不懂这两个,应该是和 写屏障 相关的谢谢!
**//go:linkname 当前函数名/变量名【**localname
**】 被关联的函数名/变量名(包含包名)【**importpath.name 】
黑魔法 。由于这个伪指令,可以破坏类型系统和包模块化。因此 只有引用了 unsafe 包才可以使用 。
简单来讲,就是 ** importpath.name 是 localname 的符号别名,编译器实际上会调用 localname** 。
注意: linkname 是双向的,具有导出 和导入 两种使用方式。
导出的不能被作为导入,但是导入的可以作为多个导入, 仅限函数,变量不行