gcflags参数有啥用

6,980 阅读6分钟

问题

看一些文章涉及是否优化或者输出plan9 汇编源码时都需要使用-gcflags参数,那这些参数都有什么?哪里能看到文档呢

解决

google一下解决问题,

[What's Go cmd option 'gcflags' all possible values](stackoverflow.com/questions/6…)

翻译过来就是 gcflags的参数值是传递给go tool compile的参数,所以可以通过命令(如下)看到所有可能的值

go tool compile -help

实际场景

堆逃逸

参考:

用 Go struct 不能犯的一个低级错误!

func main() {
 a := new(struct{})
 b := new(struct{})
 println(a, b, a == b)
​
 c := new(struct{})
 d := new(struct{})
 fmt.Println(c, d)
 println(c, d, c == d)
}
go run -gcflags="-m -l" main.go

-m print optimization decisions

-l disable inlining

go run -gcflags="-N -l" main.go 

-n disable optimizations

若逃逸到堆上,空结构体则默认分配的是 runtime.zerobase 变量,是专门用于分配到堆上的 0 字节基础地址。因此两个空结构体,都是 runtime.zerobase,一比较当然就是 true 了。

若没有发生逃逸,也就分配到栈上。在 Go 编译器的代码优化阶段,会对其进行优化,直接返回 false。并不是传统意义上的,真的去比较了。

汇编

查看汇编代码

go tool compile -S struct_t.go
go run -gcflags="-S" struct_t.go

汇编学习请看 为什么要学汇编

其他

什么是内联优化

内联就是把简短的函数在调用它的地方展开。

参考:

[译] Go语言inline内联的策略与限制

go中的内联优化

go启动过程

参考:

详解 Go 程序的启动流程,你知道 g0,m0 是什么吗?

因为在 Go1.11 起,为了减少二进制文件大小,调试信息会被压缩。导致在 MacOS 上使用 gdb 时无法理解压缩的 DWARF 的含义是什么

$ GOFLAGS="-ldflags=-compressdwarf=false" go build 
(gdb) info files
Symbols from "/Users/firaga/goasm/gostart".
Local exec file:
  `/Users/firaga/goasm/gostart', file type mach-o-x86-64.
  Entry point: 0x1065600
  0x0000000001001000 - 0x00000000010a2e8a is .text
  0x00000000010a2ea0 - 0x00000000010a2fa2 is __TEXT.__symbol_stub1
  0x00000000010a2fc0 - 0x00000000010e9947 is __TEXT.__rodata
  0x00000000010e9960 - 0x00000000010ea0e8 is __TEXT.__typelink
  0x00000000010ea100 - 0x00000000010ea168 is __TEXT.__itablink
  0x00000000010ea168 - 0x00000000010ea168 is __TEXT.__gosymtab
  0x00000000010ea180 - 0x0000000001148940 is __TEXT.__gopclntab
  0x0000000001149000 - 0x0000000001149020 is __DATA.__go_buildinfo
  0x0000000001149020 - 0x0000000001149178 is __DATA.__nl_symbol_ptr
  0x0000000001149180 - 0x00000000011574a4 is __DATA.__noptrdata
  0x00000000011574c0 - 0x000000000115e8f0 is .data
  0x000000000115e900 - 0x000000000118c170 is .bss
  0x000000000118c180 - 0x00000000011912f0 is __DATA.__noptrbss
(gdb) b *0x1065600
Breakpoint 1 at 0x1065600: file /usr/local/Cellar/go/1.16/libexec/src/runtime/rt0_darwin_amd64.s, line 8.

m0

m0 是 Go Runtime 所创建的第一个系统线程,一个 Go 进程只有一个 m0,也叫主线程。

从多个方面来看:

  • 数据结构:m0 和其他创建的 m 没有任何区别。
  • 创建过程:m0 是进程在启动时应该汇编直接复制给 m0 的,其他后续的 m 则都是 Go Runtime 内自行创建的。
  • 变量声明:m0 和常规 m 一样,m0 的定义就是 var m0 m,没什么特别之处。

g0

g 一般分为三种,分别是:

  • 执行用户任务的叫做 g。
  • 执行 runtime.main 的 main goroutine。
  • 执行调度任务的叫 g0。。

g0 比较特殊,每一个 m 都只有一个 g0(仅此只有一个 g0),且每个 m 都只会绑定一个 g0。在 g0 的赋值上也是通过汇编赋值的,其余后续所创建的都是常规的 g。

从多个方面来看:

  • 数据结构:g0 和其他创建的 g 在数据结构上是一样的,但是存在栈的差别。在 g0 上的栈分配的是系统栈,在 Linux 上栈大小默认固定 8MB,不能扩缩容。而常规的 g 起始只有 2KB,可扩容。
  • 运行状态:g0 和常规的 g 不一样,没有那么多种运行状态,也不会被调度程序抢占,调度本身就是在 g0 上运行的。
  • 变量声明:g0 和常规 g,g0 的定义就是 var g0 g,没什么特别之处。

go语言并发编程实战

g0 和 m0 运行时系统中的每个 M 都会拥有一个特殊的 G,一般称为 M 的 g0。M 的 g0 管辖的 内存称为 M 的调度栈。可以说,M 的 g0 对应于操作系统为相应线程创建的栈。因此,M 的调度栈也可以称为 OS 线程栈或系统栈(可参看 runtime.systemstack 函数)。 M 的 g0 不是由 Go 程序中的代码(更确切地说是 go 语句)间接生成的,而是由 Go 运行时系统在初始化 M 时创建并分配给该 M 的。M 的 g0 一般用于执行调度、垃圾回收、 栈管理等方面的任务。顺便提一下,M 还会拥有一个专用于处理信号的 G,称 为 gsignal。 它的栈可称为信号栈。系统栈和信号栈不会自动增长,但一定会有足够的空间执行代码。 除了 g0 之外,其他由 M 运行的 G 都可以视作用户级别的 G,简称用户 G,而它们 的 g0 和 gsignal 都可以称为系统 G。Go 运行时系统会进行切换,以使每个 M 都可以交替 运行用户 G 和它的 g0。这就是我在前文中说“每个 M 都会运行调度程序”的原因。与用 户 G 不同,g0 不会被阻塞,也不会包含在任何 G 队列或列表中。此外,它的栈也不会在 垃圾回收期间被扫描。 除了每个 M 都有属于它自己的 g0 之外,还存在一个 runtime.g0。runtime.g0 用于执 行引导程序,它运行在 Go 程序拥有的第一个内核线程中,这个内核线程也称为 runtime.m0。runtime.m0 和 runtime.g0 都是静态分配的,因此引导程序也无需为它们分 配内存。runtime.m0 的 g0 即 runtime.g0。

gdb调试

文本调试

gdb main

tui调试

gdb main -tui
常用参数

测试常用参数:

list l

run 执行

start 开启调试

break b 设置断点

next n 下一行

s step 步入

continue 执行到下一个断点

quit

详细参数查找输入help + command

参考:GDB调试入门指南

delve调试

见dlv安装和使用, 感觉比gdb好用一些,mac上也能使用