当前位置: 首页 > news >正文

GOLANG SLICE 切片扩容

切片扩容是非常影响性能的操作,当初始的cap设置的比较小,而实际的元素非常多的时候,性能损耗就更突出了。

很多时候,我们对这个功能点不够重视,经常也会写一些没有声明 cap 容量的变量声明,然后直接调用 append,逻辑上是没有问题的,但是当 append 的数量足够多的时候,性能问题就突出来了。

live := make([]BigObject, 0)		// 缺少声明cap
live = append(live, BigObject{})

在初始化切片时设置了cap为2,但实际上元素有600个,这时切片容量的增长就称为了一个性能的损耗点。这种情况,底层会按照2倍的扩容方式,2、4、8、16…,总共会扩容9次,每次扩容过程,旧数据内存都会拷贝到新数据的空间。

这种2倍的增长模式,也不是固定的,具体的增长算法,依赖我们预期(这里我叫 eCap,expect capacity)的值,当达到阈值1024,会采用两种不同的增长策略。

引申一个想法

如果初始化切片设置的cap比较小,实际要存储的元素个数也比较小,我们如何评估影响呢?这让我联想到了时间复杂度,时间复杂度评估的就是耗时和数据量N的关系,关注的是一种变化,而非某个具体的N是多少。

所以,创建切片的时候,合理地预估容量非常重要。slice底层的切片扩容机制这里简单说明一下:

扩容的参考依据有两个,当前切片的容量,这里称为 oCap(old capacity)。满足当前切片需求的最小容量,这里称为 eCap。当 oCap 小于 1024时,按照 2 倍 oCap进行扩容,当容量大于 1024时,按照 1/4 oCap的容量增长,这里的 1/4 是具有叠加效应的。我们来实现一下伪代码,假设新的切片容量为 nCap:

if oCap < 1024 {
    nCap = 2 * oCap
} else {
    nCap = oCap
    for oCap < eCap {
        nCap = 1/4 * nCap + nCap
    }
}

对比的看一下 go1.18 下的源代码:

对比一下上面我自己想的伪代码,有一些考虑不完善的地方。

  1. 缺少了 eCap 和2倍 oCap 容量的比较逻辑
  2. oCap值可能是负值,这回导致伪代码的 for 循环变成死循环
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
	newcap = cap
} else {
	const threshold = 256
	if old.cap < threshold {
		newcap = doublecap
	} else {
		// Check 0 < newcap to detect overflow
		// and prevent an infinite loop.
		for 0 < newcap && newcap < cap {
			// Transition from growing 2x for small slices
			// to growing 1.25x for large slices. This formula
			// gives a smooth-ish transition between the two.
			newcap += (newcap + 3*threshold) / 4
		}
		// Set newcap to the requested cap when
		// the newcap calculation overflowed.
		if newcap <= 0 {
			newcap = cap
		}
	}
}

切片的扩容逻辑比较简单,也没有什么高深的地方。留一个思考题,指定 slice 中的一个下标,将下标前的部分和下标后的部分颠倒?

相关文章:

  • 并发编程Bug起源:可见性、有序性和原子性问题
  • LastPass 开发者系统被黑已窃取源代码
  • 设计模式摘要
  • 2.Hive表结构变更时,滥用MSCK REPAIR TABLE语句,导致变更语句执行时间过长
  • [I2C]I2C通信协议详解(一) --- 什么是I2C
  • 寄——在外拼搏的你一路平安,早日团圆
  • C++11之右值引用:移动语义和完美转发(带你了解移动构造函数、纯右值、将亡值、右值引用、std::move、forward等新概念)
  • 【手把手带你学JavaSE】第八篇:抽象类和接口
  • 18年程序员生涯,读了200多本编程书,挑出一些精华分享给大家
  • 广播解决方案:Livemind Recorder:录音机
  • 罗克韦尔AB PLC(RSLogix 5000)在线修改程序的具体方法示例
  • 2020 关于Map Map,String> Map,Object>的简单使用
  • 2019蓝桥杯省赛---java---C---5(最大降雨量)
  • 一键下载小说(二):如何在Django中部署
  • Java线程基础-CountDownLatch-批量执行多线程完成,再由主线程发起
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【EOS】Cleos基础
  • Android组件 - 收藏集 - 掘金
  • angular2 简述
  • echarts花样作死的坑
  • express + mock 让前后台并行开发
  • FastReport在线报表设计器工作原理
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JAVA并发编程--1.基础概念
  • PAT A1017 优先队列
  • spring security oauth2 password授权模式
  • Transformer-XL: Unleashing the Potential of Attention Models
  • vue--为什么data属性必须是一个函数
  • 阿里云前端周刊 - 第 26 期
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 七牛云假注销小指南
  • 前端面试总结(at, md)
  • 深度学习入门:10门免费线上课程推荐
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 数据仓库的几种建模方法
  • MPAndroidChart 教程:Y轴 YAxis
  • 大数据全解:定义、价值及挑战
  • 扩展资源服务器解决oauth2 性能瓶颈
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​水经微图Web1.5.0版即将上线
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #pragma once与条件编译
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (层次遍历)104. 二叉树的最大深度
  • (二)丶RabbitMQ的六大核心
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (推荐)叮当——中文语音对话机器人
  • ******之网络***——物理***
  • ./configure、make、make install 命令
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .NET 8.0 发布到 IIS
  • .NET Core 项目指定SDK版本