用一个真实的多包多文件模块验证 Go 的初始化顺序:依赖的包先初始化(b → a → c → main);一个包内先按“声明顺序里最靠前的就绪变量”初始化所有变量、再按文件名顺序执行所有 init(同一文件里按书写顺序);go run 命令行里文件顺序会改变 init 顺序;GODEBUG=inittrace=1 打印每个包的 init 耗时;初始化循环与对 init 的非法使用的编译错误。
在 Go 1.27.1 里验证常量与 iota:iota 的跳过、位标志、一行多个、隐式重复;无类型常量精确计算(常量 0.1 + 0.2 == 0.3 为 true,变量为 false,1 << 100 >> 98 = 4);整数常量除法;1.5 * time.Second 是编译错误(truncated to int64);无类型整数常量的上限用二分法实测是 1 << 511;溢出、类型不匹配、非常量移位等编译错误。
在 Go 1.27.1 里验证 unsafe.Pointer 的用法与风险:合法的类型转换、unsafe.Add 与 Offsetof;uintptr 不是引用——递归撑大 goroutine 栈后局部变量的地址变了,unsafe.Pointer 被同步改写而 uintptr 变成悬空地址,堆对象 3 次 GC 后地址不变;go vet 的 unsafeptr 警告与 -d=checkptr=1 的运行时检查(未对齐、指针运算越界会 fatal,目标类型不含指针的未对齐与跨原对象的转换不报)。
在 Go 1.27.1(arm64)上量 reflect 的成本:按下标读字段 2.26–3.61 ns(直接读 1.90 ns),按名字 FieldByName 29.61 ns;reflect.Value.Call 调函数 121.20 ns(直接调 1.96 ns,约 62 倍)、调方法 140.11 ns,每次 MethodByName 335.39 ns;reflect.DeepEqual 180.26 ns(手写比较 4.55 ns);只取类型信息的 reflect.TypeOf().Kind() 与类型断言一样快;并统计各操作的分配次数。
在 Go 1.27.1(arm64 macOS,系统 clang)上量 cgo 的成本:一次空的 C 函数调用 21.22 ns(Go 调用 1.97 ns,约 10.8 倍);批量处理才划算(C 对 4096 字节求和 72.03 ns,Go 循环 1266.25 ns);go tool cgo 生成的包装只是调用 runtime.cgocall;100 个 goroutine 阻塞在 C 里会多创建 98 个线程(Go 的 time.Sleep 只多 1 个);CGO_ENABLED=0、交叉编译与 Go 指针传递规则的限制。
在 Go 1.27.1 里验证 go:embed:string、[]byte、embed.FS 三种变量;目录嵌入默认跳过 . 与 _ 开头的文件,all: 前缀才包含;embed.FS 只读、按名字排序、修改时间为零(http.FS 因此没有 Last-Modified);ReadFile 拷贝内容(2 次分配,57 ns),string 变量零成本;嵌入的数据在可执行文件里:[]byte 在数据段、string 在只读段,各占一份(1 MiB 文件两种声明共增加 2.02 MiB);各种写错时的编译错误。
用 go list 在不同 GOOS / GOARCH / -tags 下验证 Go 1.27.1 的构建约束:_GOOS、_GOARCH 文件名后缀与 //go:build 表达式共同决定哪些文件参与编译;名字里没有下划线的 windows.go 不受约束;//go:build ignore 的文件只在 go run 显式给出时才被使用;旧的 // +build 仍然生效;unix 标签覆盖哪些系统;go vet 能发现写错位置和两种写法不一致。
在一个本机 file:// GOPROXY 里搭出 4 个模块的多个版本,真实验证最小版本选择:构建列表取“需求图里出现过的最大版本”(d v1.4.0),而不是仓库里最新的版本(e 有 v1.3.0 却选 v1.2.0);go mod tidy 写出间接依赖;go get 升级与降级会牵连其它模块(降级 d 到 v1.1.0 使 b 降级、c 被移除);go mod why、exclude 与 replace 的效果。
用本机 file:// 模块仓库与两个本地模块验证 go.work 工作区:不动 app/go.mod 就让 app 使用本地修改过的 lib(replace 方案要改 go.mod);go work init/use/edit/sync 的用法;工作区模式不允许 GOFLAGS=-mod=mod;工作区里版本选择取所有模块需求的并集最大值(e v1.3.0),lib 单独构建却是 e v1.1.0;GOWORK=off 回到发布的版本;go mod tidy 不受工作区影响。
在 Go 1.27.1 里实测二进制体积:hello world 1.75–2.32 MiB,加 encoding/json 3.59 MiB,加 net/http 约 7.9 MiB;-ldflags="-s -w" 统一小 33%–35%;pclntab 与代码一样大;panic 的栈回溯靠 pclntab,所以剥离符号表后仍有函数名;按包统计 runtime 占 42%;通过 reflect.Value.MethodByName 调用方法会一次多约 0.9 MiB 并保留所有导出方法(每个约 470 字节);各平台 hello world 的大小。
用 go build -gcflags=-m=2 读 Go 1.27.1 的内联诊断:预算 80,add 4、sum 16、big 62、recursive 71 可以内联,defer、recover、go 语句与代价超出的 caller(96)、main(310)不能;一连串 x += i 最多 26 条(代价 80);内联的收益在这个微基准里体现在逃逸分析上(返回指针的小函数内联后 0 次分配,关闭内联后 1 次分配);标准库的小函数可以被内联。
在 Go 1.27.1 里用汇编里的 runtime.panicBounds 调用点数验证边界检查消除:range、i < len(s)、i&7、uint 比较、倒序循环、clampN 都没有检查;i < n(n 来自参数)、i%8、s[i:j]、下标来自另一个切片仍有检查;_ = s[3] 把 4 处合成 1 处,带 if 的 _ = s[n-1] 反而无效;关掉所有检查(-B)在顺序求和里只快 0.3%–0.8%。