Go 语言编译器的 "//go:" 详解

栏目: Go · 发布时间: 5年前

内容简介:一上来不太好说明白 Go 语言里我猜,大部分人第一行代码都是在计算机编程中,

前言

C 语言的 #include

一上来不太好说明白 Go 语言里 //go: 是什么,我们先来看下非常简单,也是几乎每个写代码的人都知道的东西:C 语言的 #include

我猜,大部分人第一行代码都是 #include 吧。完整的就是 #include <stdio.h> 。意思很简单,引入一个 stdio.h 。谁引入?答案是 编译器 。那么, # 字符的作用就是给 编译器 一个 指示 ,让编译器知道接下来要做什么。

编译指示

在计算机编程中, 编译指示(pragma) 是一种语言结构,它指示编译器应该如何处理其输入。 指示 不是编程语言语法的一部分,因编译器而异。

这里 Wiki 详细介绍了它,值得你看一下。

Go 语言的编译指示

官方文档 https://golang.org/cmd/compil...

形如 //go: 就是 Go 语言编译指示的实现方式。相信看过 Go SDK 的同学对此并不陌生,经常能在代码函数声明的上一行看到这样的写法。

有同学会问了, // 这不是注释吗?确实,它是以注释的形式存在的。

编译器源码 这里可以看到全部的指示,但是要注意, //go: 是连续的, //go 之间并没有空格。

常用指示详解

//go:noinline

noinline 顾名思义,不要内联。

Inline 内联

Inline ,是在编译期间发生的,将函数调用调用处替换为被调用函数主体的一种编译器优化手段。Wiki: Inline 定义

使用 Inline 有一些优势,同样也有一些问题。

优势:

  • 减少函数调用的开销,提高执行速度。
  • 复制后的更大函数体为其他编译优化带来可能性,如 过程间优化
  • 消除分支,并改善空间局部性和指令顺序性,同样可以提高性能。

问题:

  • 代码复制带来的空间增长。
  • 如果有大量重复代码,反而会降低缓存命中率,尤其对 CPU 缓存是致命的。

所以,在实际使用中,对于是否使用内联,要谨慎考虑,并做好平衡,以使它发挥最大的作用。

简单来说,对于短小而且工作较少的函数,使用内联是有效益的。

内联的例子

func appendStr(word string) string {
    return "new " + word
}

执行 GOOS=linux GOARCH=386 go tool compile -S main.go > main.S

我截取有区别的部分展出它编译后的样子:

0x0015 00021 (main.go:4)    LEAL    ""..autotmp_3+28(SP), AX
    0x0019 00025 (main.go:4)    PCDATA    $2, $0
    0x0019 00025 (main.go:4)    MOVL    AX, (SP)
    0x001c 00028 (main.go:4)    PCDATA    $2, $1
    0x001c 00028 (main.go:4)    LEAL    go.string."new "(SB), AX
    0x0022 00034 (main.go:4)    PCDATA    $2, $0
    0x0022 00034 (main.go:4)    MOVL    AX, 4(SP)
    0x0026 00038 (main.go:4)    MOVL    $4, 8(SP)
    0x002e 00046 (main.go:4)    PCDATA    $2, $1
    0x002e 00046 (main.go:4)    LEAL    go.string."hello"(SB), AX
    0x0034 00052 (main.go:4)    PCDATA    $2, $0
    0x0034 00052 (main.go:4)    MOVL    AX, 12(SP)
    0x0038 00056 (main.go:4)    MOVL    $5, 16(SP)
    0x0040 00064 (main.go:4)    CALL    runtime.concatstring2(SB)

可以看到,它并没有调用 appendStr 函数,而是直接把这个函数体的功能内联了。

那么话说回来,如果你不想被内联,怎么办呢?此时就该使用 go//:noinline 了,像下面这样写:

//go:noinline
func appendStr(word string) string {
    return "new " + word
}

编译后是:

0x0015 00021 (main.go:4)    LEAL    go.string."hello"(SB), AX
    0x001b 00027 (main.go:4)    PCDATA    $2, $0
    0x001b 00027 (main.go:4)    MOVL    AX, (SP)
    0x001e 00030 (main.go:4)    MOVL    $5, 4(SP)
    0x0026 00038 (main.go:4)    CALL    "".appendStr(SB)

此时编译器就不会做内联,而是直接调用 appendStr 函数。

//go:nosplit

nosplit 的作用是: 跳过栈溢出检测。

栈溢出是什么?

正是因为一个 Goroutine 的起始栈大小是有限制的,且比较小的,才可以做到支持并发很多 Goroutine,并高效调度。

stack.go 源码中可以看到, _StackMin 是 2048 字节,也就是 2k,它不是一成不变的,当不够用时,它会动态地增长。

那么,必然有一个检测的机制,来保证可以及时地知道栈不够用了,然后再去增长。

回到话题, nosplit 就是将这个跳过这个机制。

优劣

显然地,不执行栈溢出检查,可以提高性能,但同时也有可能发生 stack overflow 而导致编译失败。

//go:noescape

noescape 的作用是: 禁止逃逸,而且它必须指示一个只有声明没有主体的函数。

逃逸是什么?

Go 相比 C、C++ 是内存更为安全的语言,主要一个点就体现在它可以自动地将超出自身生命周期的变量,从函数栈转移到堆中,逃逸就是指这种行为。

请参考我之前的文章, 逃逸分析

优劣

最显而易见的好处是,GC 压力变小了。

因为它已经告诉编译器,下面的函数无论如何都不会逃逸,那么当函数返回时,其中的资源也会一并都被销毁。

不过,这么做代表会绕过编译器的逃逸检查,一旦进入运行时,就有可能导致严重的错误及后果。

//go:norace

norace 的作用是: 跳过竞态检测

我们知道,在多线程程序中,难免会出现数据竞争,正常情况下,当编译器检测到有数据竞争,就会给出提示。如:

var sum int

func main() {
    go add()
    go add()
}

func add() {
    sum++
}

执行 go run -race main.go 利用 -race 来使编译器报告数据竞争问题。你会看到:

==================
WARNING: DATA RACE
Read at 0x00000112f470 by goroutine 6:
  main.add()
      /Users/sxs/Documents/go/src/test/main.go:15 +0x3a

Previous write at 0x00000112f470 by goroutine 5:
  main.add()
      /Users/sxs/Documents/go/src/test/main.go:15 +0x56

Goroutine 6 (running) created at:
  main.main()
      /Users/sxs/Documents/go/src/test/main.go:11 +0x5a

Goroutine 5 (finished) created at:
  main.main()
      /Users/sxs/Documents/go/src/test/main.go:10 +0x42
==================
Found 1 data race(s)

说明两个 goroutine 执行的 add() 在竞争。

优劣

使用 norace 除了减少编译时间,我想不到有其他的优点了。但缺点却很明显,那就是数据竞争会导致程序的不确定性。

总结

我认为绝大多数情况下,无需在编程时使用 //go: Go 语言的编译器指示,除非你确认你的程序的性能瓶颈在编译器上,否则你都应该先去关心其他更可能出现瓶颈的事情。


以上所述就是小编给大家介绍的《Go 语言编译器的 "//go:" 详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Web Reputation Systems

Building Web Reputation Systems

Randy Farmer、Bryce Glass / Yahoo Press / 2010 / GBP 31.99

What do Amazon's product reviews, eBay's feedback score system, Slashdot's Karma System, and Xbox Live's Achievements have in common? They're all examples of successful reputation systems that enable ......一起来看看 《Building Web Reputation Systems》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试