在Golang遍历map时,如何避免陷入性能陷阱?

摘要:最近一直在重构优化老系统,所以性能优化相关的文章会比较多。 这次的是有关循环处理map时的性能优化。预分配内存之类的大家都知道的就不多说了,今天来讲点大伙不知道的。 要讲的一共有三点,而且都和循环处理map有关。 不要用for-range循
最近一直在重构优化老系统,所以性能优化相关的文章会比较多。 这次的是有关循环处理map时的性能优化。预分配内存之类的大家都知道的就不多说了,今天来讲点大伙不知道的。 要讲的一共有三点,而且都和循环处理map有关。 不要用for-range循环清空map 这里要讨论的“清空”是指删除map中所有键值对,但保留map里已分配的内存供下次复用。 如果只是想释放map并且不再需要复用,那么map1 = nil或者map2 = map[T]U{}就足够了。 内置函数delete可以帮我们实现删除键值对但保留它们在map中的内存空间,通常我们会这么写: for key := range Map1 { delete(Map1, key) } 这种模式化的代码太常见,以至于go编译器专门对其做了优化,只要形式上符合上述代码片段的,编译器都会把循环优化成runtime.mapclear(Map1),使用runtime内置的map清理函数将map清空,这比靠循环遍历删除要快很多倍。 看到这里你可能会说这不是挺好吗,为什么不让用了呢? 因为现在有更好的替代方案了——内置函数clear。 clear应用在map上时本身就会调用runtime.mapclear(...),在性能上和循环方法大致一样而且只快不慢。因为两者最终生成代码差不多,性能测试也就没多大意义了,所以这里不做性能测试。 clear还有另一个好处,它更容易让包含它的函数被内联。 这是什么意思呢?go的编译器实际上在编译时要分很多个步骤,粗略地讲go代码在真正开始生成机器码之前,得先经过内联 -> 逃逸分析 -> 语法树改写这样几个阶段。上文说的for循环删除优化在语法树改写这个阶段完成。 这就带来了一个问题,相比一个简单的clear函数调用,编译器认为for循环这种操作更“重量级”,一个函数拥有的“重”操作越多,那么这个函数被内联优化的可能性就越小。 我们看个例子: func RangeClearMap() { for k := range bigMap { delete(bigMap, k) } for k := range bigPtrMap { delete(bigPtrMap, k) } for k := range smallMap { delete(smallMap, k) } for k := range smallPtrMap { delete(smallPtrMap, k) } for k := range bigMapIntKey { delete(bigMapIntKey, k) } for k := range bigPtrMapIntKey { delete(bigPtrMapIntKey, k) } for k := range smallMapIntKey { delete(smallMapIntKey, k) } for k := range smallPtrMapIntKey { delete(smallPtrMapIntKey, k) } } func BuiltinClearMap() { clear(bigMap) clear(bigPtrMap) clear(smallMap) clear(smallPtrMap) clear(bigMapIntKey) clear(bigPtrMapIntKey) clear(smallMapIntKey) clear(smallPtrMapIntKey) } 同样是清空8个map,一个用循环,一个用内置函数。我们看下内联分析的结果: 其中cost就是衡量一个函数里的操作有多“重”的数值标准,超过一定的cost,函数就无法内联。可以看到,使用循环会比使用clear内置函数重整整四倍。虽然最后因为两个函数都很简单所以被内联展开,但碰上更复制一点的函数,显然使用clear能有更多的冗余。 尽管编译器最终会把两者优化成一样的对runtime的map清理函数的调用,但对for循环的优化在内联处理之后,因此for不仅让代码更长,也更容易错失内联优化的机会,而失去内联优化进而会影响逃逸分析从而损失更多性能,你可以在我以前的博客里看到内联和逃逸分析对内联的影响。 clear()是go1.21添加的,因此只要你在用的go版本大于等于1.21,我推荐你尽量使用clear而不是for-range循环来清空map。 遍历访问map时的陷阱 遍历处理map中的元素也是常见操作,不过不像循环删除,编译器在这种代码上并没有什么优化。
阅读全文