GO语言高质量编程——工程实践 | 青训营笔记

41 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

本文记录的是录播视频中遇到的不会的知识点,并不会一贯地按着ppt来罗列一些知识点,仅仅代表我个人的一些观点与看法,欢迎各位大佬指点问题。

高质量编程———性能优化建议

1.1 简介

image.png

1.2 使用性能量化工具Benchmark

image.png

image.png

1.3 有关slice

  • 预分配内存

image.png

image.png

  • 陷阱:大内存未释放

image.png

1.4 聊聊map

  • 预分配内存

image.png

分析如下:

  1. 不断向map中添加元素会触发map的扩容
  2. 提前分配好空间可以减少内存拷贝和Rehash的消耗
  3. 建议根据实际需求提前预估好所需内容容量

1.5 有关字符串的处理

  • 用strings.builder

内部原理: 根据用法说明,我们通过调用 string.Builder 的写入方法来写入内容,然后通过调用 String() 方法来获取拼接的字符串。那么 string.Builder 是如何组织这些内容的呢?

通过 slice

string.Builder 通过使用一个内部的 slice 来存储数据片段。当开发者调用写入方法的时候,数据实际上是被追加(append)到了其内部的 slice 上。

image.png

高效使用strings.builder

strings.Builder 是通过其内部的 slice 来储存内容的。当你调用写入方法的时候,新的字节数据就被追加到 slice 上。如果达到了 slice 的容量(capacity)限制,一个新的 slice 就会被分配,然后老的 slice 上的内容会被拷贝到新的 slice 上。当 slice 长度很大时,这个操作就会很消耗资源甚至引起 内存问题。我们需要避免这一情况。

关于 slice,Go 语言提供了 make([]TypeOfSlice, length, capacity) 方法在初始化的时候预定义它的容量。这就避免了因达到最大容量而引起扩容。

strings.Builder 同样也提供了 Grow() 来支持预定义容量。当我们可以预定义我们需要使用的容量时,strings.Builder 就能避免扩容而创建新的 slice 了。

func (b *Builder) Grow(n int)

当调用 Grow() 时,我们必须定义要扩容的字节数(n)。 Grow() 方法保证了其内部的 slice 一定能够写入 n 个字节。只有当 slice 空余空间不足以写入 n 个字节时,扩容才有可能发生。举个例子:

  • builder 内部 slice 容量为 10。
  • builder 内部 slice 长度为 5。
  • 当我们调用 Grow(3) => 扩容操作并不会发生。因为当前的空余空间为 5,足以提供 3 个字节的写入。
  • 当我们调用 Grow(7) => 扩容操作发生。因为当前的空余空间为 5,已不足以提供 7 个字节的写入。

关于上面的情形,如果这时我们调用 Grow(7),则扩容之后的实际容量是多少?

17 还是 12?

实际上,是 27strings.Builder 的 Grow() 方法是通过 current_capacity * 2 + n (n 就是你想要扩充的容量)的方式来对内部的 slice 进行扩容的。所以说最后的容量是 10*2+7 = 27

当你预定义 strings.Builder 容量的时候还要注意一点。调用 WriteRune() 和 WriteString() 时,rune 和 string 的字符可能不止 1 个字节。因为,你懂的,UTF-8 的原因。