一些问题的答案
知乎上有一个问题,有哪些优秀的 Go 面试题,其中有一个高赞答案很[敏感词],从大多数问题上可以看出作者想要问别人什么,对其它应聘者的要求是对常见的性能指标要敏感。
不过我觉得,即使是做优化,很多时候也应该是去做整体优化,而不是在所有地方全部抠细节,那我们程序全用汇编+优化指令写得了。
当然这么说也很空洞,我简单整理一下这些问题的答案,可能其中有些疏漏,如果读到这篇文章的你在某些问题的答案上有不同的见解,欢迎在评论里讨论和补充~
-
1.9/1.10中,time.Now()返回的是什么时间?这样做的决定因素是什么?
是想考 monotonic time 和 wall time 吗?
也可能是想考 vdso 的 syscall -
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 在这里:这里 -
到 1.11 为止,sync.RWMutex 最主要的性能问题最容易在什么常见场景下暴露。有哪些解决或者规避方法?
是想考 lock contention 和 read lock 的 cache contention?
对锁进行优化一般思路是进行锁的粒度拆分。
runtime 里的优化手段一般是按照 p 来进行拆分,比如 timer bucket 的拆分。内存分配时每个 p 上都有各种乱七八糟的 cache,本地获取基本可以无锁,获取不到的时候,才会进全局抢锁,思路都差不多。
用户态无法获得 p.id,但可以进行简单的锁粒度拆分,类似 github 上开源的 concurrentmap,或者 sync.Map 的实现。一些场景可以尝试用 atomic.CAS 替代读写锁。 -
如何做一个逻辑正确但 golang 调度器(1.10)无法正确应对,进而导致无法产生预期结果的程序。调度器如何改进可以解决此问题?
不会扩栈的死循环在触发 GC 的时候都有可能有问题。1.13 开始引入基于信号的调度。 -
列出下面操作延迟数量级(1ms, 10us或100ns等),cgo 调用 c 代码,c 调用 go 代码,channel在单线程单 case 的 select 中被选中,high contention 下对未满的 buffered channel 的写延迟。
cgo 基本是几十~100ns 级别。
其它需要 bench 一下 -
如何设计实现一个简单的 goroutine leak 检测库,可用于在测试运行结束后打印出所测试程序泄露的 goroutine 的 stacktrace 以及 goroutine 被创建的文件名和行号。
参考 pingcap 的小哥们跑测试前检查一次所有的 g,跑完之后检查一次。然后类似 pprof 可以把全局所有的 g 的 stack 打出来,把那些多出来的 g 的 stack 打印出来应该就行了。 -
选择三个常见 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 有啥性能问题?写多读少的时候? -
一个 C/C++ 程序需要调用一个 go 库,某一 export 了的 go 函数需高频率调用,且调用间隔需要调用根据 go 函数的返回调用其它C/C++函数处理,无法改变调用次序、相互依赖关系的前提下,如何最小化这样高频调用的性能损耗?
放弃治疗,用 go 重写 -
不考虑调度器修改本身,仅考虑 runtime 库的 API 扩展,如果要给调度器添加 NUMA ,runtime 库需要添加哪些函数,哪些函数接口必须改动。
不知道。但我觉得提问者是想炫耀自己看过这个文档:numa-aware go scheduler。既然这么了解,那为啥提问者不给官方提个 proposal 并且把这个功能实现了呢?说不定还能进 Go 官方开发组哦~ -
stw 的 pause 绝大多数情况下在 100us 量级,但有时跳增一个数量级。描述几种可能引起这一现象的触发因素和他们的解决方法。
看不出来想考什么。
gc 的触发频率和 stw 时间和系统类型业务逻辑强相关。作者的这个 100us 也不准确,比如我们这里的就是 1ms stw。
可能出问题的:
map 元素中有大量的指针。
请求 qps 突增,导致堆上的小对象太多。sync.Pool 简单重用就好,虽然 sync.Pool 也比较挫。
gcwaiting 阶段,有 g 一直不退出。 -
已经对 GC 做了较充分优化的程序,在无法减小内存使用量的情况下,如何继续显著减低 GC 开销。
已经做了充分优化。要降低 GC 开销。。。。。感觉逻辑有问题呢
把能变成值类型的指针类型全变掉减小 gc scan 开销么?不知道想考什么。
或者 GOGC=off 弃疗,手动狗头 -
有一个常见说法是“我能用 channel 简单封装出一个类似 sync.Pool 功能的实现”。在多线程、high contention、管理不同资源的前提下,两者各方面性能上有哪些显著不同。
sync.Pool 在每次 gc 时都会清理掉所有对象,在 1.13 之前没什么好办法。
具体的区别还是需要 bench 一下。 -
为何只有一个 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 -
一个 Go 程序如果尝试用 fork() 创建一个子进程,然后尝试在该子进程中对Go程序维护的一个数据结构串行化以后将串型化结果保存到磁盘。上述尝试会有什么结果?
不知道,need test -
请列举两种不同的观察 GC 占用 CPU 程度的方法,观察方法无需绝对精确,但需要可实际运用于 profiling 和 optimization。
pprof 的 cpuprofile 火焰图
启动时传入环境变量 Debug=gctrace
go tool trace,可以看到 p 绑定的 g 实际的 GC 动作和相应时长,以及阻塞时间 -
GOMAXPROCS 与性能的关系如何?在给定的硬件、程序上,如何设定 GOMAXPROCS 以期获得最佳性能?
一般情况下是 = NumCPU,后续版本官方都计划把这个选项干掉了,不知道提问者问这个问题有什么意义。 -
一个成熟完备久经优化的 Golang 后台系统,程序只有一个进程,由 golang 实现。其核心处理的部分由数十个 goroutine 负责处理数万 goroutine 发起的请求。由于无法设定 goroutine 调度优先级,使得核心处理的那数十个 goroutine 往往无法及时被调度得到 CPU,进而影响了处理的延迟。有什么改进办法?
参考毛毛哥答案,把发任务和处理任务拆进程。 -
列举几个近期版本 runtime 性能方面的 regression。
很多,有的很扯。
比如 1.9 的时候 http 库的作者自己改代码的时候也没有做 benchmark。
个人感觉考这个也没啥意义,翻翻每个版本的更新日志和官方的 benchmark 就能看到,可能提问者是想看看应聘人是否一直在跟进 Go 本身的更新。