Go高性能编程概览(1/2)
文章目录
【注意】最后更新于 July 22, 2021,文中内容可能已过时,请谨慎使用。
这篇博客是对 <Go 语言高性能编程>的读后总结, 原文中会有 demo 代码, 会有 benchmark 分析, 这里会尽可能的简短的去表达
字符串 string
临时拼接短的字符串, 可以用 +
的方式, 用 fmt.Sprintf()
都行, 但是因为字符串不可变,这些拼接都是生成一个新的字符串对象, 因此会占用 2N 体积的内存
针对大文本的字符串操作, 必须使用 strings.Builder
, bytes.Buffer
, []byte
这种切片拼接的方式
切片 slice
切片和数组类似, 区别就是会动态扩容, 1,2,4,8…..直到 1024 后按1.25的系数扩充
且切片拼接/向后追加是 O(N) 的时间复杂度, 随机插入/删除 的时间复杂度是 O(N), 增删比较频繁的还是建议使用链表
最大的大坑就是从同一个切片中出来的子切片是共享同一片内存的, 这就意味着:
|
|
此时如果 arrB
一直在使用中, 或者发生内存逃逸, 那么是整块 arrA
的内存进行变动, 如果你不确定这块内存什么时候会被释放, 就使copy
来处理切片
for 和 range 性能比较
由于 range
的特性, 它迭代的过程中是会进行<值拷贝>, 因此针对复杂结构的切片来说, for
是更好的选择, 或者只用range
迭代下标
|
|
如果被迭代的对象是 int
, byte
, 指针 这种固定内存大小的数据, for
和range
其实相差不大
反射 relect
反射能让静态语言有一些动态的写法在里面, 但是非必要情况尽量少用反射,
一是因为逻辑不直观, 不利于后续维护;
二是性能略有损耗, 同时 reflect.New(typ).Elem().Field(0).SetString("name")
和 reflect.New(typ).Elem().FieldByName("Name").SetString("name")
相比, 前者按下标来读取值位置, 要不根据 key 名读取性能高数倍, 因为 后者会遍历所有 key 名比对后找到对应下标再做更改
优化一下上述的问题,就使用临时缓存, 如示例:
|
|
空结构 struct
空结构体的这个特性真的很不错, 因为struct{}
是 不-占-内-存 的! 这个特性用来当占位符是再好不过了, 举个例子就是使用 map
来做集合:
|
|
这个特性配合内存对齐来说就很好
内存对齐
cpu从内存中读取对象时并不是逐个访问字节, 而是按字长来一块一块的读取, golang的官方文档内存对齐规则是:
- 对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1。
- 对于 struct 结构体类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
- 对于 array 数组类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐倍数。
- 没有任何字段的空 struct{} 和没有任何元素的 array 占据的内存空间大小为 0,不同的大小为 0 的变量可能指向同一块地址。
举例来说:
|
|
一句话总结就是: 对象的类型定义要像金字塔一样, 字段类型的尺寸从上到下依次增大, 这样内存对齐时不会浪费空间(话说这个可以在编译阶段优化一下吧?)
这时的struct{}
这个类型也需要小小的注意一下
|
|
文章作者 GPF
上次更新 2021-07-22 (2e7eb79)