完全转载,都没排版:原文:

原文地址
别人的简书转载

前言

C 语言的 #include

一上来不太好说明白 Go 语言里 //go: 是什么,我们先来看下非常简单,也是几乎每个写代码的人都知道的东西:C 语言的 #include。
我猜,大部分人第一行代码都是 #include 吧。完整的就是#include <stdio.h>。意思很简单,引入一个 stdio.h。谁引入?答案是编译器。那么,# 字符的作用就是给 编译器 一个 指示,让编译器知道接下来要做什么。

编译指示

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

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

Go 语言的编译指示 官方文档 https://golang.org/cmd/compile/#hdr-Compiler_Directives

形如 //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语言编译器的 “//go:“ 详解相关推荐

  1. C 语言编译器 gcc 命令详解

    Linux 系统中最重要的软件开发工具是 gcc,在 Linux 系统中,c89.cc 和 gcc 这些命令基本上都指向系统的C语言编译器,通常是GNU C编译器,或都简称 gcc.在 UNIX 系统 ...

  2. 嵌入式c语言为什么变量定义在前面,嵌入式C语言数据类型和变量详解

    原标题:嵌入式C语言数据类型和变量详解 一般来讲,标准的C语言类型在嵌入式编译器中是合法的.但由于嵌入式控制器的受限环境.嵌入式c语言的变量和数据类型具有新的特征,这些特征体现在如下方面. 嵌入式C语 ...

  3. java文档注释定界符_c语言的注释定界符详解

    c语言的注释定界符详解 c语言的注释定界符是什么 1.最早期的C语言注释是:/* */ 2.后来又增加的行注释:// 其中/**/是多行注释,//是单行注释. 需要注意的是:C 语言的注释并不是可以出 ...

  4. C语言-入门级别函数详解

    C语言-入门级别函数详解 写在开始 关于函数 1. 函数的定义形式 2.函数的声明 3. 返回语句 4.函数参数 4.1 形式参数(传值调用) 4.2 实际参数(传址调用) 4.3无参数 5.函数的调 ...

  5. 程序人生 | C语言字节对齐问题详解 - 对齐/字节序/位序/网络序等(上)

    本文首发于 2014-07-21 15:32:28 1. 引言 考虑下面的结构体定义: typedef struct{char c1;short s; char c2; int i; }T_FOO; ...

  6. C语言字节对齐问题详解

    转载原文连接:https://www.cnblogs.com/clover-toeic/p/3853132.html C语言字节对齐问题详解 引言 考虑下面的结构体定义: 1 typedef stru ...

  7. C语言qsort快速排序函数详解

    直接进入主题,在c语言中qsort函数是用来快速排序的,qsort有4个参数,分别是数组地址,数组元素个数,数组元素字节大小和一个比较数组元素的函数指针.让我来看一下官方给出的使用标准,上图: 让我们 ...

  8. [Go语言入门] 14 Go语言goroutine和通道详解

    文章目录 14 Go语言goroutine和通道详解 14.1 goroutine 14.2 通道(channel) 声明通道变量 创建通道 通道操作 14.3 管道 14.4 单向通道 14.5 通 ...

  9. c语言的编译过程详解

    c语言的编译过程详解 IDE的使用让很多和我一样的人对C/C++可执行程序的底层生成一知半解,不利于我们深入理解原理.在这里小结一下,望路过的大神指正~ 前言:从一个源文件(.c文件)到可执行程序到底 ...

最新文章

  1. 苹果新算法已混进 iOS 14.3!CSAM 检测技术再遭网友争议
  2. 设计模式学习(一) 基本理念
  3. python游戏编程入门书籍推荐-游戏编程入门书籍推荐:想要游戏编程尽快入门这些书不要错过...
  4. HTML5/CSS3/JavaScript
  5. python中json文件处理涉及的四个函数json.dumps()和json.loads()、json.dump()和json.load()的区分
  6. Coding and Paper Letter(六)
  7. Tomcat7性能优化
  8. OpenCV函数cvFindContours
  9. 告诉各位为如何学习linux系统
  10. adb avd install 失败_Android 模拟器(emulator-5554...)出现错误解决办法
  11. vue 动态的修改样式
  12. SkinSharp函数文档
  13. 低代码平台集成方案,打通企业内部业务管理系统
  14. meanshift算法图解
  15. 图片理解引擎算法实现简介
  16. Linux命令hostname -i
  17. 分水岭算法的理解和应用
  18. RocketMQ初识
  19. oracle批量替换保留字,Oracle中的关键字保留字
  20. 天翼云服务器的一些问题及解决方式

热门文章

  1. 【C++】henuACM暑期培训Day11 KMP
  2. 图卷积网络原理(二)【图信号与图的拉普拉斯矩阵】
  3. 【单片机】Proteus安装、MDK5安装、Proteus与Keil联合仿真教程
  4. 机器学习 数学基础 学习笔记 (1) 导数
  5. MIZ7035上的AXI接口的MIG测试
  6. DDOS攻击相关问题
  7. 收藏的书录,值得花时间去读的书
  8. YOLOv4---(详解各种trick)
  9. 什么是CUDA和CUDNN?——GeForce NVIDIA显卡用于深度学习计算的GPU加速工具
  10. 金蝶协同办公平台任意文件下载漏洞(无需登录)