[转载] 6 个技巧,提升 C++11 的 vector 性能
转载:https://www.sohu.com/a/120595688_465979
Vector 就像是 C++ STL 容器的瑞士军刀。Bjarne Stoutsoup 有一句话 – “一般情况下,如果你需要容器,就用 vector”。像我们这样的普通人把这句话当作真理,只需要照样去做。然而,就像其它工具一样,vector 也只是个工具,它能提高效率,也能降低效率。
这篇文章中我们可以看到 6 种优化使用 vector 的方法。我们会在最常见的使用 vector 的开发任务中看到有效的方法和无效的方法,并以此衡量有效使用 vector 会带来怎样的性能提升,并试图理解为什么能得到这样的性能提升。
性能测试的搭建和方法:
所有测试都在我的 Surface Book 中运行,这台笔记本拥有主频 2.6Ghz 的酷睿 i7 处理器,8 GB 内存,安装了 Windows 10 操作系统并使用 VS2015 C++ 编译器编译运行。
我们会使用 Stopwatch。这个工具由 Kjell 创建,在 可以找到。
我们会运行每个测试 100 次,然后计算平均运行时间来作为依据。运行测试的代码在。你可以自由下载,用于在你自己的系统中评估 vector 的性能。那里提供的代码段只反映了一次循环,这让事件变得简单。
我们在 vector 中存入 TestStruct 结构的数据,并使用 FillVector() 来填充 vector。它们的定义如下。
马上开始在 C++ 11 中优化 vector 用法的介绍。
#1 提前分配足够的空间以避免不必要的重新分配和复制周期
程序员喜欢使用 vector,因为他们只需要往向容器中添加元素,而不用事先操心容器大小的问题。但是,如果由一个容量为 0 的 vector 开始,往里面添加元素会花费大量的运行性能。如果你之前就知道 vector 需要保存多少元素,就应该提前为其分配足够的空间。
这里有一个简单的示例,往 vector 里添加 1 万个测试结构的实例——先进行不预分配空间的测试再进行有预分配的测试。
在我的计算机中,未预分配空间的情况用了 5145 微秒(us),而预分配了空间的情况下只用了 1279 微秒,性能提高了 75.14%!!!
这个情况在 Scott Meyers 的书中得到了很好的解释,这本书叫 :
“对于 vector 和 string,在需要更多空间的时候,会做与 realloc 等效的事情。这种类似 realloc 的操作有4个步骤:
1. 分别一个新的内存块,其容量是容器当前容量的数倍。多数实现中,vector 和 string 容量的提升因子在 1.5 和 2 之间。
2. 从容器原来占用的内存中将元素拷贝到新分配的内存中。
3. 释放原有内存中的对象。
4. 释放原有内存。
有了所有这些操作:分配、回收、拷贝和释放,如果说这些步骤(对于性能)极其昂贵,你一点都不应该感到惊讶。当然,你肯定不希望频繁的进行这样的操作。如果这还没有打动你,那么想想每次进行这些步骤的时候,vector 和 string 中所有的迭代器、指针和引用都会失效。这意味着一个简单的插入操作,对于其它使用了当前 vector 或 string 中的迭代器、指针或引用的数据结构,都有可能引起对它们进行更新。”
2.使用 shrink_to_fit() 释放 vector 占用的内存, – clear() 或 erase() 不会释放内存。
与大家所想的相反,使用 erase() 或 clear() 从 vector 中删除元素并不会释放分配给 vector 的内存。做个简单的实验就可以证明这一点。我们往一个 vector 中添加 100 个元素,然后在这个 vector 上调用 clear() 和 erase()。然后我们可以让 capacity() 函数告诉我们为这个容器分配的内存可以存入多少元素。
下面是输出:
从上面的输出可以看到,erase() 或 clear() 不会减少 vector 占用的内存。如果在代码中到达某一点,不再需要 vector 时候,请使用 std::vector::shrink_to_fit() 方法释放掉它占用的内存。
请注意,shrink_to_fit() 可能没有被所有编译器供应商完全支持。这种情况下,可以使用“Swap 惯用法”来清空 vector,代码如下:
container<T>( c ).swap( c ); // shrink-to-fit 惯用法,用于清空存储空间
container<T>().swap( c ); // 用于清空所有内容和存储空间的惯用法
如果你对此感兴趣,请查看“”一书的条款# 82,其中有针对 swap 惯用法的细节介绍。
3. 在填充或者拷贝到 vector 的时候,应该使用赋值而不是 insert() 或push_back().
从一个 vector 取出元素来填充另一个 vector 的时候,常有三种方法 – 把旧的 vector 赋值给新的 vector,使用基于迭代器的 std::vector::insert() 或者使用基于循环的 std::vector::push_back()。这些方法都展示在下面:
这是它们的性能:
赋值: 589.54 us
insert(): 1321.27 us
push_back(): 5354.70 us
我们看到 vector 赋值比 insert() 快了 55.38%,比 push_back() 快了 89% 。
为什么会这样???
赋值非常有效率,因为它知道要拷贝的 vector 有多大,然后只需要通过内存管理一次性拷贝 vector 内部的缓存。
所以,想高效填充 vector,首先应尝试使用 assignment,然后再考虑基于迭代器的 insert(),最后考虑 push_back。当然,如果你需要从其它类型的容器拷贝元素到 vector 中,赋值的方式不可行。这种情况下,只好考虑基于迭代器的 insert()。
4. 遍历 std::vector 元素的时候,避免使用 std::vector::at() 函数。
遍历 vector 有如下三种方法:
使用迭代器
使用 std::vector::at() 成员函数
使用下标 – [ ] 运算符
下面展示了每种用法:
输出是:
显而易见,用 std::vector::at() 函数访问 vector 元素是最慢的一个。
5. 尽量避免在 vector 前部插入元素
任何在 vetor 前部部做的插入操作其复杂度都是 O(n) 的。在前部插入数据十分低效,因为 vector 中的每个元素项都必须为新的项腾出空间而被复制。如果在 vector 前部连续插入多次,那可能需要重新评估你的总体架构。
做个有趣的尝试,下面是在 std::vector 前部做插入和在 std::list 前部部做插入的对比:
如果我运行这个测试10,其中使用一个包含100个元素的vector,那么输出结果如下:
在 list 前部部插入操作比在 vector 前部部快大约58836%。不用感到奇怪,因为在 list 前部做元素插入的算法,其复杂度为 O(1)。显然,vector 包含元素越多,这个性能测试的结果会越差。
6. 在向 vector 插入元素的时候使用 emplace_back() 而不是 push_back()。
几乎赶上 C++11 潮流的每个人都明确地认同“安置”这种往 STL 容器里插入元素的方法。理论上来说,“安置”更有效率。然而所有实践都表明,有时候性能差异甚至可以忽略不计。
思考下面的代码:
如果运行100次,会得到这样的输出:
可以清楚的看到,“安置”函数比插入函数性能更好 – 但只有 177 微秒的差距。在所有情况下,他们大致是相当的。
仅在以下情况下,Emplacement 函数可能会更快:
要添加的值是在 vector 中构造的,而不是赋值的。
传递的参数类型与 vector 中保存的类型不同。例如,如果一个向量包含 std :: string,但我们传递一个字符串值到该 vector。
即使上述两个条件都不成立,如本例所示的,你也不要因为在插入时使用 emplacement 而掉以轻心。
更多关于 emplacement vs. insertion 的详细信息,请查看 Scott Meyer 的““一书中的条目#42。
结语
与任何第三方统计数据一样,你不应盲目地依赖此处提供的结果和建议。在不同的操作系统、处理器体系结构和编译器设置上测试时,你可能遇到很多不确定因素。因此,你需要根据实际数据,自己做出衡量。
[转载] 6 个技巧,提升 C++11 的 vector 性能的更多相关文章
- [转载]Js小技巧||给input type=“password”的输入框赋默认值
http://www.cnblogs.com/Raywang80s/archive/2012/12/06/2804459.html [转载]Js小技巧||给input type="passw ...
- 【转】提升你的Java应用性能:改善数据处理
提升你的Java应用性能:改善数据处理 作者:贾小骏 发布于07月26日 10:17 许多应用程序在压力测试阶段或在生产环境中都会遇到性能问题.如果我们看一下性能问题背后的原因,会发现很多是由数据处 ...
- C++11在时空性能方面的改进
C++11在时空性能方面的改进 这篇我们聊聊C++11在时间和空间上的改进点: 主要包括以下方面: 新增的高效容器:array.forward_list以及unordered containers: ...
- C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路
C#不用union,而是有更好的方式实现 用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C# ...
- 提升 web 应用程序的性能(一)
提升 web 应用程序的性能,找出瓶颈,加快客户端内容的速度. 作为 web 用户,我们知道页面加载或刷新的速度对其成功至关重要.本文将帮助您更好地理解影响 web 应用程序性能的因素.学 ...
- 开启 NFS 文件系统提升 Vagrant 共享目录的性能
Vagrant 默认的 VirtualBox 共享目录方式读写性能表现并不好,好在 Vagrant 支持 NFS 文件系统方式的共享,我们可以启用 NFS 提升性能 开启方法 首先要把虚拟机的网络设置 ...
- 【C/C++开发】容器set和multiset,C++11对vector成员函数的扩展(cbegin()、cend()、crbegin()、crend()、emplace()、data())
一.set和multiset基础 set和multiset会根据特定的排序准则,自动将元素进行排序.不同的是后者允许元素重复而前者不允许. 需要包含头文件: #include <set> ...
- 11个显著提升 ASP.NET 应用程序性能的技巧——第1部分
[编者按]本文出自站外作者 Brij Bhushan Mishra ,Brij 是微软 MVP-ASP.NET/IIS.C# Corner MVP.CodeProject Insider,前 Code ...
- (转载)DataTable使用技巧总结
在项目中经常用到DataTable,如果DataTable使用得当,不仅能使程序简洁实用,而且能够提高性能,达到事半功倍的效果,现对DataTable的使用技巧进行一下总结. 一.Da ...
- [转载]8 种提升 ASP.NET Web API 性能的方法
http://www.oschina.net/translate/8-ways-improve-asp-net-web-api-performance 英文原文:8 ways to improve A ...
随机推荐
- docker基础镜像java版本选择和推荐
背景 在编写dockerfile时,基础镜像要么太大,要么缺少jdk:dockerhub中的openjdk五花八门,不知道选择哪个 解决方案 我在项目中通常选择 openjdk作为基础镜像 FROM ...
- 暑假集训CSP提高模拟 ∫[0,6] (x^2)/6 dx
\[\text{暑假集训CSP提高模拟}\int^{6}_{0}\frac{x^{2}}{6}dx \] 关于这个东西怎么求的良心教程 含义:求出 \(f(x)=\frac{x^{2}}{6}\) 在 ...
- 《Neo4j 图数据库扩展指南:APOC和ALGO》
https://detail.tmall.com/item.htm?spm=a2e2i.11532906.0.d2960ced2.f27a6abbrEMtHp&id=622478213458 ...
- 一款基于Uniapp开发的开源低代码平台
rtvue-lowcode低代码开发平台 rtvue-lowcode一款基于uniapp框架和uview组件库的低代码开发平台,项目提供可视化拖拽编辑器,采用MIT开源协议,适用于app.小程序等项目 ...
- 日干算命api接口_json数据_性格/爱情/事业/财运/健康运势免费接口
该API接口基于传统的八字学原理,通过用户提供的日干信息,为用户提供性格.爱情.事业.财运和健康等多方面的运势分析和建议.以下是该接口的详细介绍: 一.功能概述 性格分析:根据用户的日干信 ...
- Android Qcom USB Driver学习(七)
最近遇到了USB 插拔后,系统重启的问题,抓取串口log发现如下问题,log中查看trace分析就是空指针造成的panic Unable to handle kernel read from unre ...
- 简述 JavaScript脚本的执行原理?
js 是一种动态 . 弱类型 . 基于原型的语言 ,通过浏览器可以直接执行: 当浏览器遇到 <script></script>标记时 , 会执行标记之间的js 代码:然后js ...
- day01-markdown学习
markdown学习 标题 两个#号 三级标题 四级标题 字体 hello world!两个*加粗 hello world!一个*斜体 hello world!三个*斜体加粗 hello world! ...
- 在 K8s 集群上部署 RabbitMQ 实战
作者:老Z,云原生爱好者,目前专注于云原生运维,KubeSphere Ambassador. 前言 知识点 定级:入门级 RabbitMQ 单节点安装部署 RabbitMQ 集群安装部署 GitOps ...
- 鸿蒙Flutter实战:01-搭建开发环境
鸿蒙Flutter实战:01-搭建开发环境 准备工作 1.安装 DevEco Studio NEXT IDE, 注意版本应该是 Next,当前最新的是 Beta3 2.安装Git, 如果要同时适配安卓 ...