append 扩容机制

209 阅读6分钟

1. 最简单的扩容机制——追加一个元素且在 1024 字节以内

  • 如果新的大小在 cap 大小以内自然不需要考虑扩容问题
  • 如果超出 cap 则 cap 翻倍

2. 追加一个元素超过 1024 字节

  • cap 超过 1024 字节,扩容则变为 1.25 倍而不再是 2 倍(当然这只是之前版本,现在 go 的切片扩容已经不会直接由 2 倍 降低 为 1.25 倍,而是更平滑的降低)

3. 复杂扩容——追加多个元素

  • 当追加的最终元素大小,超过了 cap 的二倍时,则扩容为原大小直接加追加大小,如再两个元素后再加 3 个元素,新的 cap 则为 5

以上三种规则也是我们大部分人所认为的常识


OK 到这里为止 go 的切片扩容机制真的只是如此吗 ?当然不是如果你去尝试我所说的第 3 个总结的话,自然就发现问题了,如下

package main

import "fmt"

func main() {
   e := []int32{1, 2}
   fmt.Println("cap of e before:", cap(e))
   e = append(e, 4, 5, 6)
   fmt.Println("cap of e after:", cap(e))
}

输出:

image.png

很显然输出并非我们所想的

我们所总结的规则来自于 go 扩容机制 runtime/slice包中的 growslice 源代码,只是我们所总结的只是 go 扩容机制的第一步,也就是他所期望的 cap 大小,真正分配时又会有其他的影响因素,例如在分配时 span class ,也就是 go 内存分配机制的 span 大小种类,

例如我们上面的代码, 我们期望分到 5 个元素的长度,其中 int 类型占 8 个字节,也就是分得 40 个字节的内存,ok ,那我们 span 的种类有哪些呢,如下图

67中不同大小的span代码注释如下(版本1.11):

// class  bytes/obj  bytes/span  objects  tail waste  max waste
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         32        8192      256           0     46.88%
//     4         48        8192      170          32     31.52%
//     5         64        8192      128           0     23.44%
//     6         80        8192      102          32     19.07%
//     7         96        8192       85          32     15.95%
//     8        112        8192       73          16     13.56%
//     9        128        8192       64           0     11.72%
//    10        144        8192       56         128     11.82%
//    11        160        8192       51          32      9.73%
//    12        176        8192       46          96      9.59%
//    13        192        8192       42         128      9.25%
//    14        208        8192       39          80      8.12%
//    15        224        8192       36         128      8.15%
//    16        240        8192       34          32      6.62%
//    17        256        8192       32           0      5.86%
//    18        288        8192       28         128     12.16%
//    19        320        8192       25         192     11.80%
//    20        352        8192       23          96      9.88%
//    21        384        8192       21         128      9.51%
//    22        416        8192       19         288     10.71%
//    23        448        8192       18         128      8.37%
//    24        480        8192       17          32      6.82%
//    25        512        8192       16           0      6.05%
//    26        576        8192       14         128     12.33%
//    27        640        8192       12         512     15.48%
//    28        704        8192       11         448     13.93%
//    29        768        8192       10         512     13.94%
//    30        896        8192        9         128     15.52%
//    31       1024        8192        8           0     12.40%
//    32       1152        8192        7         128     12.41%
//    33       1280        8192        6         512     15.55%
//    34       1408       16384       11         896     14.00%
//    35       1536        8192        5         512     14.00%
//    36       1792       16384        9         256     15.57%
//    37       2048        8192        4           0     12.45%
//    38       2304       16384        7         256     12.46%
//    39       2688        8192        3         128     15.59%
//    40       3072       24576        8           0     12.47%
//    41       3200       16384        5         384      6.22%
//    42       3456       24576        7         384      8.83%
//    43       4096        8192        2           0     15.60%
//    44       4864       24576        5         256     16.65%
//    45       5376       16384        3         256     10.92%
//    46       6144       24576        4           0     12.48%
//    47       6528       32768        5         128      6.23%
//    48       6784       40960        6         256      4.36%
//    49       6912       49152        7         768      3.37%
//    50       8192        8192        1           0     15.61%
//    51       9472       57344        6         512     14.28%
//    52       9728       49152        5         512      3.64%
//    53      10240       40960        4           0      4.99%
//    54      10880       32768        3         128      6.24%
//    55      12288       24576        2           0     11.45%
//    56      13568       40960        3         256      9.99%
//    57      14336       57344        4           0      5.35%
//    58      16384       16384        1           0     12.49%
//    59      18432       73728        4           0     11.11%
//    60      19072       57344        3         128      3.57%
//    61      20480       40960        2           0      6.87%
//    62      21760       65536        3         256      6.25%
//    63      24576       24576        1           0     11.45%
//    64      27264       81920        3         128     10.00%
//    65      28672       57344        2           0      4.91%
//    66      32768       32768        1           0     12.50%

很显然我们没有 40 字节大小的 span ,所以向上取整,得到一个 48 字节大小的 span ,也就是最终分得 cap 长度为 6

到这里就结束了吗,当然不是,如果你好奇的话,你继续扩大追加的元素个数,你会发现扩容机制再次发生改变,也就是说,span class 的大小也只是其中的一个影响因素

到这里为止,就直接公布我所认为的扩容机制应该如何处理:

答案就是:去纠结它的扩容确切值并没什么必要

理由:在扩容的容量确定上,相对比较复杂,它与CPU位数、元素大小、是否包含指针、追加个数 等都有关系。当我们看完扩容源码逻辑后,发现去纠结它的扩容确切值并没什么必要。

在实际使用中,如果能够确定切片的容量范围,比较合适的做法是:切片初始化时就分配足够的容量空间,在append追加操作时,就不用再考虑扩容带来的性能损耗问题。

优秀文章: zhuanlan.zhihu.com/p/393750584