Go语言必知必会100问题-21 切片初始化方法及最佳实践

切片初始化

切片使用内置的make函数进行初始化,初始化需要提供两个参数,分别是切片的长度和容量(可选)。如果这两个参数设置的不合理,会使得后续对切片的操作非常低效。下面来看看怎么设置这两个参数是合适的。

假设我们要实现一个转换函数(convert),将Foo切片映射到Bar切片,并且两个切片将具有相同数量的元素。下面是第一个实现版本:

func convert(foos []Foo) []Bar {bars := make([]Bar, 0)for _, foo := range foos {bars = append(bars, fooToBar(foo))}return bars
}
问题分析

上述实现中,首先通过make初始化了一个大小为0的空切片,然后通过append向里面添加元素,在添加第一个元素的时候会分配一个大小为1的底层数组。每次当底层数组满时会创建一个容量加倍的数组。所以在添加第三个、第五个和第九个元素时,由于当前数组已满而创建另一个数组的逻辑会重复多次。假设向切片添加1000个元素,根据Go切片扩容算法,差不多要分配10次底层数组,并将总共1000多个元素从一个数组复制到另一个数组。这会导致GC需要付出额外的努力来清理不在使用的数组。如何进行优化呢?有两种不同的方法。

如何优化?
方法1

方法一代码逻辑与上述一致,唯一区别是一开始将切片的容量设置为给定要装的元素数量。代码如下:

func convert(foos []Foo) []Bar {n := len(foos)bars := make([]Bar, 0, n)for _, foo := range foos {bars = append(bars, fooToBar(foo))}return bars
}

对比上述两个版本,可以看到唯一的变化是bars创建时其容量设置为n. 在内部,Go预分配了一个包含n个元素的数组,因此最多可以添加n个元素。从而大大减少分配的数量。

方法2

除了上面的优化方法,还有一种优化方法,就是一开始分配切片的长度为foos的长度,实现代码如下。通过循环给切片bars中每个位置赋值元素,不能通过append向里面添加元素,因为一开始bars中已有了n个元素,并且值为int类型的默认值0.

func convert(foos []Foo) []Bar {n := len(foos)bars := make([]Bar, n)for i, foo := range foos {bars[i] = fooToBar(foo)}return bars
}
三种方法性能测试对比

上述三种实现,哪种实现方法最优呢?通过benckmark测试,向切片中添加一百万个元素,测试结果如下,分别对应上面三种实现。可看到给定预期大小的容量或长度(上面的实现2和实现3)比不分配任何长度和容量快大约400%。后两种方法相比,给定固定长度的实现比固定容量的块大约4%,因为它避免了频繁的调用append函数开销,相比直接赋值,调用函数会慢一点。

BenchmarkConvert_EmptySlice-4        22     49739882 ns/op
BenchmarkConvert_GivenCapacity-4     86     13438544 ns/op
BenchmarkConvert_GivenLength-4       91     12800411 ns/op
特殊情况1:优先方法2

既然方法2(给定容量大小)没有方法3(给定长度大小)高效,为什么在Go项目中看到还有方法2在被使用呢?下面的程序是来自Pebble项目中的一个具体示例,Pebble 是 Cockroach Labs 开发的开源键值存储(https://github.com/cockroachdb/pebble)。在Pebble中有一个collectAllUserKeys函数,它循环遍历输入的结构体切片,并返回一个[]byte的切片,返回的切片大小是结构体切片的两倍。

func collectAllUserKeys(cmp Compare,tombstones []tombstoneWithLevel) [][]byte {keys := make([][]byte, 0, len(tombstones)*2)for _, t := range tombstones {keys = append(keys, t.Start.UserKey)keys = append(keys, t.End)}// ...
}

在上面的代码中,有意识选择使用的是给定的容量并通过append向里面添加元素。为啥要选择这种方法呢?前面不是说方法3效率更高吗?如果我们使用给定的长度而不是容量,则实现代码如下。因为要通过索引给切片中的元素赋值,程序看起来更复杂些。鉴于此功能对性能不敏感,优先考虑了代码的可读性,所以采用了上面的实现。

func collectAllUserKeys(cmp Compare,tombstones []tombstoneWithLevel) [][]byte {keys := make([][]byte, len(tombstones)*2)for i, t := range tombstones {keys[i*2] = t.Start.UserKeykeys[i*2+1] = t.End}// ...
}
特殊情况2:大小不确定如何初始化

有些情况下不能准确知道切片的大小,这时候怎么处理比较好。例如,下面代码中,输出切片的大小依赖于条件函数 something(foo), 这种情况下,在初始化bars切片的时候,是初始化为空,还是设置为固定大小的长度或容量呢?

func convert(foos []Foo) []Bar {// bars initializationfor _, foo := range foos {if something(foo) {// Add a bar element}}return bars
}

没有严格的规定,这是一个传统的软件问题:需要权衡考虑CPU和内存,是开大一点内存还是多耗一点CPU. 例如,如果 something(foo) 在99%的情况下都为true,考虑使用给定长度或容量来初始化。所以这种情况要具体问题具体分析,没有统一的标准。

思考总结

总结,将一种切片类型转换为另一种切片类型是Go中经常遇到的操作。通过前面的分析,如果提前已知道切片的长度是多少,就不要创建一个大小为0的空切片,采用分配给定容量或给定长度对切片进行初始化是最佳选择。具体是采用给定容量还是给定长度,要根据实际情况进行选择,采用给定容量代码的可阅读性更好,采用给定长度程序的性能往往更高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/739973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工作实践-07】uniapp关于单位rpx坑

问题:在浏览器页面退出登录按钮上“退出登录”字样消失,而在手机端页面正常;通过查看浏览器页面的HTML代码,发现有“退出登录”这几个字,只不过由于样式问题,这几个字被挤到看不见了。 样式代码中有一行为&#xff1a…

Midjourney绘图欣赏系列(十一)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子,它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同,Midjourney 是自筹资金且闭源的,因此确切了解其幕后内容尚不…

Linux常见指令总结

ls:显示当前目录下文件列表 常用的命令行参数: -l 显示更多的文件属性 -a 显示所有的文件/目录(包括隐藏的) -d 只显示目录 ps:参数可以叠加使用。 例如:ls -la 显示所有文件…

wait 和 notify方法

目录 1.1 wait()方法 wait 做的事情: wait 结束等待的条件: 1.2 notify()方法 1.3notifyAll方法 1.4wait()和sleep()对比 由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序. 完成这个协调…

利用matlab处理netcdf文件中time变量的格式转换问题

我们通常读取的科研数据具有时间维度,因而通常用于数据运算的时候,最常使用的是(2003.567)等双精度格式的年份。本专栏提供了一个将nc文件中提供的时间变量的年-月-日转成-年。但是nc文件提供的time变量通常是以下两种格式&#x…

C语言入门学习 --- 2.分支与循环语句

第二章分支与循环语句 2.分支与循环语句 分支语句 ifswitch 循环语句 whiledo whilefor goto语句 2.1分支语句(选择结构) 2.1.1 什么是选择? 例:如果你努力,也许会成功。如果你不努力,你永远不会成功。这就是选择 2.1.2 if语句…

重学SpringBoot3-日志Logging

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-日志Logging 引言默认日志配置日志门面如何实现默认配置 自定义日志配置日志级别日志分组日志格式日志输出自定义 Logback 配置切换日志框架 日志使用方…

数据结构 - 链表 (四)

这篇博客将介绍带头循环的双向链表,实现链表的头部插入/删除,尾部插入/删除,查找,以及任意位置的插入删除。 1.结构 带头循环的双向链表的结构如下图所示,一个结点内部包含数据,以及分别指向前一个以及后…

LabVIEW质谱仪开发与升级

LabVIEW质谱仪开发与升级 随着科技的发展和实验要求的提高,传统基于VB的质谱仪系统已经无法满足当前的高精度和高效率需求。这些系统通常存在着功能不全和操作复杂的问题,影响了科研和生产的进度。为了解决这些问题,开发了一套基于LabVIEW开…

`PF_NETLINK` 是用于与内核通信的Socket族之一

PF_NETLINK 是用于与内核通信的Socket族之一。在Linux系统中,Netlink是一种用于内核与用户空间进程之间通信的机制,而PF_NETLINK Socket族则用于创建与Netlink通信相关的Socket。通过Netlink Socket,用户空间程序可以与内核进行双向通信&…

16. C++标准库

C标准库兼容C语言标准函数库,可以在C标准库中直接使用C语言标准函数库文件,同时C标准库增加了自己的源代码文件,新增文件使用C编写,多数代码放在std命名空间中,所以连接C标准库文件后还需要 using namespace std;。 【…

Python算法题集_搜索旋转排序数组

Python算法题集_搜索旋转排序数组 题33:搜索旋转排序数组1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【二分法区间判断】2) 改进版一【二分找分界标准二分法】3) 改进版二【递归实现二分法】 4. 最优算法5. 相关资源 本文为Pytho…

Qt使用Q_DECLARE_INTERFACE Q_INTERFACES宏实现接口类使用qobject_cast进行类型转换

在写抽象类或者接口的时候&#xff0c;肯定是不能继承QObject的 但是又想使用qobject_cast进行类型转换&#xff0c;使用以下办法就能实现 #ifndef FACTORYINTERFACE_H__ #define FACTORYINTERFACE_H__ #include <QObject> class FactoryInterface{ public:FactoryInterf…

【C++进阶】C++多态概念详解

C多态概念详解 一&#xff0c;多态概念二&#xff0c;多态的定义2.1 多态构成的条件2.2 什么是虚函数2.3 虚函数的重写2.3.1 虚函数重写的特例2.3.2 override和final 2.4 重载和重写&#xff08;覆盖&#xff09;和重定义&#xff08;隐藏&#xff09;的区别 三&#xff0c;抽象…

salesforce Multi-Line Layout中公式字段不能显示吗

在Salesforce的多行布局中&#xff0c;公式字段是可以显示的。但是&#xff0c;有一些限制和注意事项&#xff1a; 默认情况下&#xff0c;公式字段可能不会显示&#xff1a; 公式字段在多行布局中默认是不包含的。您可能需要手动编辑多行布局&#xff0c;将公式字段添加到布局…

QGIS 开发之旅一《二次开发环境搭建》

1、 安装QT 下载QT Index of /new_archive/qt 我选择的版本是 Qt5.14.2 2、安装VS2017 Downloads & Keys - Visual Studio Subscriptions。下载后选择windows通用平台开发和C 开发就可以了。 3、安装插件QT vs tools 搜索 qt vs tools&#xff0c;选择第一个安装 …

3642. 最大公约数和最小公倍数 考研上机真题

输入两个正整数 m和 n&#xff0c;求其最大公约数和最小公倍数。 输入格式 一行&#xff0c;两个整数 m和 n。 输出格式 一行&#xff0c;输出两个数的最大公约数和最小公倍数。 数据范围 1≤n,m≤10000 输入样例&#xff1a; 5 7输出样例&#xff1a; 1 35 #include…

框架和函数库的区别

框架和函数库在软件开发中各自扮演着重要的角色&#xff0c;但它们之间存在一些关键的区别。 首先&#xff0c;从目的上来看&#xff0c;框架旨在提供一个完整的解决方案和开发规范&#xff0c;使开发者能够高效地构建应用程序。它实现了大部分功能&#xff0c;并定义了开发人员…

【工具篇】Unity翻书效果插件Book-Page Curl Pro教程

目录 一.切换目标页 二.自动翻书到目标页 三.动态添加书页 四.按钮控制翻页

Python合并两张图片 | 先叠透明度再合并 (附Demo)

目录 前言正文 前言 用在深度学习可增加噪音&#xff0c;增加数据集等 推荐阅读&#xff1a;Pytorch 图像增强 实现翻转裁剪色调等 附代码&#xff08;全&#xff09; 正文 使用Pillow库来处理图像&#xff08;以下两张图来自网络&#xff09; 图一&#xff1a; 图二&…