Go语言的优化方法有很多,以下是其中的一些常见方法:
- 减少内存分配和垃圾回收的频率。这可以通过使用对象池、复用对象、避免使用反复创建和销毁对象的 API 等方式来实现。
- 使用并发和并行技术。Go 语言天生支持并发,可以使用 goroutine 和 channel 来实现并发编程。此外,Go 语言也提供了一些并行编程的库,如 sync 和 atomic。
- 使用高效的数据结构和算法。对于需要频繁访问和修改的数据结构,使用高效的数据结构和算法可以提高程序的性能。
- 避免过度的内存分配。过度的内存分配会导致垃圾回收的频率增加,从而影响程序的性能。可以使用数组代替切片,避免使用字符串拼接等方式来避免过度的内存分配。
- 使用编译器的优化选项。Go 语言的编译器提供了一些优化选项,如 -gcflags="-m -l -B",可以通过这些选项来优化程序的性能。
- 使用性能分析工具。Go 语言的标准库提供了一些性能分析工具,如 pprof 和 trace,可以用来诊断程序的性能问题。
- 使用调试技巧。在开发过程中,使用调试技巧可以帮助发现程序的性能问题。可以使用 Go 语言提供的调试工具,如 delve,来进行调试。
-
由于滴滴二面的时候提到了GC,吟唱完之后面试官问我实践开发中怎么减少GC的次数来提高性能,只答出来一个有的时候可以传值代替穿指针(原因不解释了),面试官问还有吗,彻底不会了,正好参加字节这个青训营,有一节课讲到这里了,做一下笔记吧,要不又白看了。
1.1 内存分配—分块
首先讲一讲go语言是怎么做对象的内存分配的,他是把内存分成不同大小的块,然后看对象的大小来决定要把它放进多大的块里,再把这些块分类,一类是没有指针的对象(不需要GC),一类是有指针的对象(需要GC)。所以根据上面这些条件找出一个合适的块,就完成了一次内存分配
1.2 Go内存管理优化
要进行优化,就先要知道他有什么问题和我们常用的场景有哪些:
- 对象分配是非常高频的操作,每秒分配GB级别的内存
- 小对象占比高,需要对小对象进行特定的优化
- Go本身的内存分配路径非常长,非常耗时
1.3 优化方案:Balance GC
- 把每个g(goroutine)都绑定一大块内存(1KB),称作goroutine allocation buffer(GAB)
- 然后再这一块大内存上进行小对象的分配,这样就不会和别的冲突,减短了分配路径
- 进而可以进行指针对象风格的分配,如下图:
当然这个大块内存的分配走的还是正常的分配路径,但是里面的小对象分配会简单高效很多,所以本质上是将多个小对象的分配合并成了一个大对象的分配。
这又引出了另外一个问题,就是有的时候一大块内存中只有几个小对象,这导致了整块内存都不能释放,下面是解决方案:
当GBA的数量到一个值的时候,对GBA进行清理,会把散落的对象集中起来,然后把原来的内存释放掉。
总结:
Go对象分配的性能问题:
- 分配路经过长
- 小对象居多
解决方案Balance GC:
- 指针碰撞风格的内存分配
- 实现了copying GC(就是上面那张图)
接下来回到问题本身,怎么优化
- 首先想到的就是暴力做法,直接把执行GC的阈值降低就行了,简单来说就是让GC执行的更频繁,这样内存也不会爆的太快。
- 对象池技术,对象池是预先分配和重用对象的集合,避免频繁地创建和销毁对象。通过使用对象池,可以减少新对象的分配次数,从而减少 GC 的压力。
- 使用并发和异步技术:通过并发和异步的设计,可以更好地隐藏 GC 的开销。例如,使用 Goroutine 进行并发处理,利用通道(Channel)进行数据传递,将 GC 的负担分摊到不同的时间段和线程中。