go 程序怎么跑起来的

使用go build -x 观察编译连接过程

1go build -x hello.go 

可执行文件

Linux 的可执行文件 ELF(Executable and Linkable Format) 为例,

ELF 由几部分构成:

  • ELF header
  • Section header
  • Sections

操作系统执行可执行文件的步骤

解析 ELF header,加载文件到内存,从entry point 开始执行代码

readelf 配合dlv 找到程序入口

通过readelf -H中的entry找到程序⼊⼝

 1[ubuntu@us_inner ~ 14:31:07]$readelf -h ./hello
 2ELF Header:
 3  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
 4  Class:                             ELF64
 5  Data:                              2's complement, little endian
 6  Version:                           1 (current)
 7  OS/ABI:                            UNIX - System V
 8  ABI Version:                       0
 9  Type:                              EXEC (Executable file)
10  Machine:                           Advanced Micro Devices X86-64
11  Version:                           0x1
12  Entry point address:               0x45c020  (入口地址)
13  Start of program headers:          64 (bytes into file)
14  Start of section headers:          456 (bytes into file)
15  Flags:                             0x0
16  Size of this header:               64 (bytes)
17  Size of program headers:           56 (bytes)
18  Number of program headers:         7
19  Size of section headers:           64 (bytes)
20  Number of section headers:         23
21  Section header string table index: 3

在dlv调试器中b *entry_addr找到代码位置

1sudo /usr/local/go/bin/go  get github.com/go-delve/delve/cmd/dlv
2sudo /usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv
3
4
5[ubuntu@us_inner ~ 14:39:41]$/home/ubuntu/go/bin/dlv exec ./hello
6Type 'help' for list of commands.
7(dlv) b *0x45c020
8Breakpoint 1 set at 0x45c020 for _rt0_amd64_linux() /usr/local/go/src/runtime/rt0_linux_amd64.s:8

go中的四座大山

Runtime构成

Scheduler、Netpoll、内存管理、垃圾回收 等,把这几块啃了,那么go就很简单了。

go调度器

https://www.figma.com/proto/gByIPDf4nRr6No4dNYjn3e/bootstrap?page-id=242%3A7&nodeid=242%3A215&viewport=516%2C209%2C0.07501539587974548&scaling=scale-down-width

队列

P的本地runnext字段-> P的local run queue -> global run queue,多级队列减少锁竞争。

用户新创建的一定赋给runnext,重试到成功为止

调度循环:

线程M在持有P的情况下不断消费运⾏队列中的G的过程。

sysmon

⼀个后台⾼优先级循环,执⾏时不需要绑定任何的P

负责:

  • 检查是否已经没有活动线程,如果是,则崩溃
  • 轮询netpoll
  • 剥离在syscall上阻塞的M的P
  • 发信号,抢占已经执⾏时间过⻓的G

处理阻塞

dlv 代码调试

todo

go语法背后到秘密

语法分析 https://astexplorer.net

1GOSSAFUNC=funcname go builld x.go
2# GOSSAFUNC=main go builld x.go

编译过程 https://godbolt.org

1go tool compile -S ./hello.go | grep "hello.go:5"
2
3# 该命令会生成.o的目标文件,并把目标的汇编内容输出

编译反编译工具

编译工具

1go tool compile -S ./hello.go | grep "hello.go:5"

反编译工具

看在runtime 执行了哪些函数

1go tool objdump ./hello  

sp: 指向当前函数栈顶

https://golang.org/ref/spec

p 本地队列 最多256个

性能调优

提前压测

锁临界区有慢操作,锁力度问题

同步改异步

json库,请求的cpu被优化了

map中含有指针,会给gc造成压力

调大gogc

逃逸分析

 1[ubuntu@us01 ~ 13:09:46]$sudo `which go` build -gcflags="-m -l" main.go
 2# command-line-arguments
 3./main.go:4:6: can inline main
 4./main.go:5:14: make([]int, 10240) escapes to heap
 5[ubuntu@us01 ~ 13:10:02]$cat main.go
 6package main
 7
 8func main(){
 9	var s1 =make([]int,10240)
10	println(s1[0])
11}

-m 打印出逃逸分析信息,-l 表示禁止内联(更好的观察逃逸)

benchmark

https://www.cnblogs.com/jiujuan/p/14604609.html

io如何模拟

  • time.Sleep
  • 不要做mock,直接压测更好

pprof

线上一定要开启

压测看goroutine数,线程数,gc频率,goroutne在干嘛的

cpu,内存

cpu使用太高

  • 应用逻辑导致
    • json 序列化
      • 使用优化的json库代替标准库
      • 使用二进制编码代替json编码
      • 干掉序列化,采用共享内存ipc通信
    • md5算hash成本太高,使用cityhash,murmurhash
  • gc使用cpu过高
    • 减少堆上分配
      • sync.Pool进行堆对象重用
      • map->slice
      • 指针-> 非指针对象
      • 多个小对象->合并一个大对象
    • offheap
    • 降低gc频率
      • 修改GOGC
      • make全局大slice
  • 调度相关函数使用cpu过高
    • 尝试使用goutine pool减少goroutine的创建和销毁
    • 控制最大goroutine数量

内存使用过高

  • 堆内存使用过多
    • sync.Pool对象复用
    • offheap
  • goroutine栈占用过多内存
    • 减少goroutine数量
      • goroutine pool

语言外优化

  • 拆进程
  • 水平扩容

go编译器

1# go tool compile 命令编译 unsafe.go 件,会得ࡠ一个扩名为 .o 的目标文件
2go tool compile unsafe.go
3#  ls -l unsafe.a

垃圾回收

 1package main
 2import (
 3	"fmt"
 4	"os"
 5	"runtime"
 6	"runtime/trace"
 7	"time"
 8)
 9func printStats(mem runtime.MemStats) {
10	runtime.ReadMemStats(&mem)
11	fmt.Println("mem.Alloc:", mem.Alloc)
12	fmt.Println("mem.TotalAlloc:", mem.TotalAlloc)
13	fmt.Println("mem.HeapAlloc:", mem.HeapAlloc)
14	fmt.Println("mem.NumGC:", mem.NumGC)
15	fmt.Println("-----")
16}
17
18func main(){
19	f, err := os.Create("/tmp/traceFile.out")
20	if err != nil {
21		panic(err)
22	}
23	defer f.Close()
24	err = trace.Start(f)
25	if err != nil {
26		fmt.Println(err)
27		return
28	}
29	defer trace.Stop()
30	var mem runtime.MemStats
31	printStats(mem)
32	for i := 0; i < 10; i++ {
33		s := make([]byte, 50000000)
34		if s == nil {
35			fmt.Println("Operation failed!")
36		}
37	}
38	printStats(mem)
39}
1GODEBUG=gctrace=1 go run xxx.go