一些问题的答案

知乎上有一个问题,有哪些优秀的 Go 面试题,其中有一个高赞答案很[敏感词],从大多数问题上可以看出作者想要问别人什么,对其它应聘者的要求是对常见的性能指标要敏感。

不过我觉得,即使是做优化,很多时候也应该是去做整体优化,而不是在所有地方全部抠细节,那我们程序全用汇编+优化指令写得了。

当然这么说也很空洞,我简单整理一下这些问题的答案,可能其中有些疏漏,如果读到这篇文章的你在某些问题的答案上有不同的见解,欢迎在评论里讨论和补充~

  1. 1.9/1.10中,time.Now()返回的是什么时间?这样做的决定因素是什么?
    是想考 monotonic time 和 wall time 吗?
    也可能是想考 vdso 的 syscall

  2. golang 的 sync.atomic 和 C++11 的 atomic 最显著的在 golang doc 里提到的差别在哪里,如何解决或者说规避?
    memory order,golang doc 里并没有和 c艹对比? issue 里有提到 Go 的 memory order 等同于 seq_cst 反而好写 lockfree 程序,因为在 seq_cst 的内存模型下,原子操作的 load 和 store 之间可以保证线性的执行关系。只不过性能差一些,不知道作者说的需要解决的问题是啥问题?
    相关 issue 在这里:这里

  3. 到 1.11 为止,sync.RWMutex 最主要的性能问题最容易在什么常见场景下暴露。有哪些解决或者规避方法?
    是想考 lock contention 和 read lock 的 cache contention?
    对锁进行优化一般思路是进行锁的粒度拆分。
    runtime 里的优化手段一般是按照 p 来进行拆分,比如 timer bucket 的拆分。内存分配时每个 p 上都有各种乱七八糟的 cache,本地获取基本可以无锁,获取不到的时候,才会进全局抢锁,思路都差不多。
    用户态无法获得 p.id,但可以进行简单的锁粒度拆分,类似 github 上开源的 concurrentmap,或者 sync.Map 的实现。一些场景可以尝试用 atomic.CAS 替代读写锁。

  4. 如何做一个逻辑正确但 golang 调度器(1.10)无法正确应对,进而导致无法产生预期结果的程序。调度器如何改进可以解决此问题?
    不会扩栈的死循环在触发 GC 的时候都有可能有问题。1.13 开始引入基于信号的调度。

  5. 列出下面操作延迟数量级(1ms, 10us或100ns等),cgo 调用 c 代码,c 调用 go 代码,channel在单线程单 case 的 select 中被选中,high contention 下对未满的 buffered channel 的写延迟。
    cgo 基本是几十~100ns 级别。
    其它需要 bench 一下

  6. 如何设计实现一个简单的 goroutine leak 检测库,可用于在测试运行结束后打印出所测试程序泄露的 goroutine 的 stacktrace 以及 goroutine 被创建的文件名和行号。
    参考 pingcap 的小哥们跑测试前检查一次所有的 g,跑完之后检查一次。然后类似 pprof 可以把全局所有的 g 的 stack 打出来,把那些多出来的 g 的 stack 打印出来应该就行了。

  7. 选择三个常见 golang 组件(channel, goroutine, [], map, sync.Map等),列举它们常见的严重伤害性能的 anti-pattern。
    同时 select 很多 channel 的时候,并发较高时会有性能问题。因为 select 本质是按 chan 地址排序,顺序加锁。lock1->lock2->lock3->lock4
    活跃 goroutine 数量较多时,会导致全局的延迟不可控。比如 99 分位惨不忍睹。
    slice append 的时候可能触发扩容,初始 cap 不合适会有大量 growSlice。
    map hash collision,会导致 overflow bucket 很长,但这种基本不太可能,hash seed 每次启动都是随机的。此外,map 中 key value 超过 128 字节时,会被转为 indirectkey 和 indirectvalue,会对 GC 的扫描阶段造成压力,如果 k v 多,则扫描的 stw 就会很长。
    sync.Map 有啥性能问题?写多读少的时候?

  8. 一个 C/C++ 程序需要调用一个 go 库,某一 export 了的 go 函数需高频率调用,且调用间隔需要调用根据 go 函数的返回调用其它C/C++函数处理,无法改变调用次序、相互依赖关系的前提下,如何最小化这样高频调用的性能损耗?
    放弃治疗,用 go 重写

  9. 不考虑调度器修改本身,仅考虑 runtime 库的 API 扩展,如果要给调度器添加 NUMA ,runtime 库需要添加哪些函数,哪些函数接口必须改动。
    不知道。但我觉得提问者是想炫耀自己看过这个文档:numa-aware go scheduler。既然这么了解,那为啥提问者不给官方提个 proposal 并且把这个功能实现了呢?说不定还能进 Go 官方开发组哦~

  10. stw 的 pause 绝大多数情况下在 100us 量级,但有时跳增一个数量级。描述几种可能引起这一现象的触发因素和他们的解决方法。
    看不出来想考什么。
    gc 的触发频率和 stw 时间和系统类型业务逻辑强相关。作者的这个 100us 也不准确,比如我们这里的就是 1ms stw。
    可能出问题的:
    map 元素中有大量的指针。
    请求 qps 突增,导致堆上的小对象太多。sync.Pool 简单重用就好,虽然 sync.Pool 也比较挫。
    gcwaiting 阶段,有 g 一直不退出。

  11. 已经对 GC 做了较充分优化的程序,在无法减小内存使用量的情况下,如何继续显著减低 GC 开销。
    已经做了充分优化。要降低 GC 开销。。。。。感觉逻辑有问题呢
    把能变成值类型的指针类型全变掉减小 gc scan 开销么?不知道想考什么。
    或者 GOGC=off 弃疗,手动狗头

  12. 有一个常见说法是“我能用 channel 简单封装出一个类似 sync.Pool 功能的实现”。在多线程、high contention、管理不同资源的前提下,两者各方面性能上有哪些显著不同。
    sync.Pool 在每次 gc 时都会清理掉所有对象,在 1.13 之前没什么好办法。
    具体的区别还是需要 bench 一下。

  13. 为何只有一个 time.Sleep(time.Millisecond) 循环的 go 程序 CPU 占用显著高于同类C/C++程序?或请详述只有一个 goroutine 的 Go 程序,每次 time.Sleep(time.Millisecond) 调用 runtime 所发生的事情。
    time.Sleep requires 7 syscalls,官方有 issue 来说明这件事情:这里
    C++ 的 time.Sleep 具体操作之后补充~
    原作者在知乎上给出了其遇到的场景,https://zhuanlan.zhihu.com/p/45959147

  14. 一个 Go 程序如果尝试用 fork() 创建一个子进程,然后尝试在该子进程中对Go程序维护的一个数据结构串行化以后将串型化结果保存到磁盘。上述尝试会有什么结果?
    不知道,need test

  15. 请列举两种不同的观察 GC 占用 CPU 程度的方法,观察方法无需绝对精确,但需要可实际运用于 profiling 和 optimization。
    pprof 的 cpuprofile 火焰图
    启动时传入环境变量 Debug=gctrace
    go tool trace,可以看到 p 绑定的 g 实际的 GC 动作和相应时长,以及阻塞时间

  16. GOMAXPROCS 与性能的关系如何?在给定的硬件、程序上,如何设定 GOMAXPROCS 以期获得最佳性能?
    一般情况下是 = NumCPU,后续版本官方都计划把这个选项干掉了,不知道提问者问这个问题有什么意义。

  17. 一个成熟完备久经优化的 Golang 后台系统,程序只有一个进程,由 golang 实现。其核心处理的部分由数十个 goroutine 负责处理数万 goroutine 发起的请求。由于无法设定 goroutine 调度优先级,使得核心处理的那数十个 goroutine 往往无法及时被调度得到 CPU,进而影响了处理的延迟。有什么改进办法?
    参考毛毛哥答案,把发任务和处理任务拆进程。

  18. 列举几个近期版本 runtime 性能方面的 regression。
    很多,有的很扯。
    比如 1.9 的时候 http 库的作者自己改代码的时候也没有做 benchmark。
    个人感觉考这个也没啥意义,翻翻每个版本的更新日志和官方的 benchmark 就能看到,可能提问者是想看看应聘人是否一直在跟进 Go 本身的更新。