-
Notifications
You must be signed in to change notification settings - Fork 1
/
go优化总结.txt
19 lines (13 loc) · 3.63 KB
/
go优化总结.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
【来源包括很多人,感谢他们:Sunface撩技术 】
一、Channels
Channel在Go语言中受到了很多的关注,因为它是一个方便的并发工具,但是了解它对性能的影响也很重要。在大多数场景下它的性能已经“足够好”了,但是在某些延时敏感的场景中,它可能会成为瓶颈。Channel并不是什么黑魔法。在Channel的底层实现中,使用的还是锁。在没有锁竞争的单线程应用中,它能工作的很好,但是在多线程场景下,性能会急剧下降。我们可以很容易的使用无锁队列ring buffer来替代channel的功能。
二、defer
defer是提高可读性和避免资源未释放的非常有用的关键字。derfer并不是一个零成本的关键字,一个互斥锁在循环体中加锁后它确实对性能产生了影响,通常要由开发者在性能和代码的易读性上做权衡。优化从来都是需要成本的。
三、Refection与JSON
Reflection通常是缓慢的,应当避免在延迟敏感的服务中使用。JSON是一种常用的数据交换格式,但Go的encoding/json库依赖于反射来对json进行序列化和反序列化。使用ffjson(译者注:easyjson会更快),我们可以通过使用代码生成的方式来避免反射的使用。(ffjson)生成的JSON序列化和反序列化比基于反射的标准库速度快38%左右。当然,如果我们对编解码的性能要求真的很高,我们应该避免使用JSON。MessagePack是序列化代码一个更好的选择。
四、内存管理
Go实际上不暴露堆或直接堆栈分配给用户。事实上,“heap”和“stack”这两个词没有出现在Go语言规范的任何地方。避免堆分配可以成为优化的主要方向。通过在栈中分配空间(即多使用A{}的方式创建对象,而不是使用new(A)的方式),我们避免了昂贵的malloc调用。自然,通过指针传值比通过对象传世要快,因为前者需要复制唯一的一个指针,而后者需要复制整个对象。然而,heap空间分配的最大的问题在于GC(垃圾回收)。如果我们生成了很多生命周期很短的对象,我们会触发GC工作,可以使用sync.Pool的作用是复用垃圾回收操作之间的内存
五、False Sharing
当性能真的很重要时,你必须开始硬件层次的思考。字段的大小都是8byte,同时被多个线程并发访问和修改来实现队列的插入和删除操作,因为这些字段在内存中是连续存放的,它们仅仅使用了16byte的内存,它们很可能被存放在同一个cache line中。因此修改其中的任何一个字段都会导致其它字段缓存被淘汰,也就意味着接下来的读取操作将会变慢。也就是说,在ring buffer中添加和删除元素会导致很多的CPU缓存失效。我们可以给结构体的字段直接增加padding.每一个padding都跟一个CPU cache line一样大,这样就能确保ring buffer的字段被缓存在不同的cache line中。False-sharing是一个非常现实的问题。根据并发和内存争的情况,添加Padding以减轻其影响。这些数字可能看起来微不足道的,但它已经起到优化作用了,特别是在考虑到在时钟周期的情况下。
六、无锁共享
无锁的数据结构对充分利用多核心是非常重要的。考虑到Go致力于高并发的使用场景,它不鼓励使用锁。它鼓励更多的使用channel而不是互斥锁。这就是说,标准库确实提供了常用的内存级别的原子操作, 如atomic包。它提供了原子比较并交换,原子指针访问。然而,使用原子包在很大程度上是不被鼓励的