原文: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 有如下三种方法:

  1. 使用迭代器

  2. 使用 std::vector::at() 成员函数

  3. 使用下标 – [ ] 运算符

下面展示了每种用法:

输出是:

显而易见,用 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 函数可能会更快:

  1. 要添加的值是在 vector 中构造的,而不是赋值的。

  2. 传递的参数类型与 vector 中保存的类型不同。例如,如果一个向量包含 std :: string,但我们传递一个字符串值到该 vector。

即使上述两个条件都不成立,如本例所示的,你也不要因为在插入时使用 emplacement 而掉以轻心。

更多关于 emplacement vs. insertion 的详细信息,请查看 Scott Meyer 的““一书中的条目#42。

结语

与任何第三方统计数据一样,你不应盲目地依赖此处提供的结果和建议。在不同的操作系统、处理器体系结构和编译器设置上测试时,你可能遇到很多不确定因素。因此,你需要根据实际数据,自己做出衡量。

提升vector性能的几个技巧的更多相关文章

  1. 提升Web性能的8个技巧总结

    提升Web性能的8个技巧总结 在互联网盛行的今天,越来越多的在线用户希望得到安全可靠并且快速的访问体验.针对Web网页过于膨胀以及第三脚本蚕食流量等问题,Radware向网站运营人员提出以下改进建议, ...

  2. 25个增强iOS应用程序性能的提示和技巧(高级篇)(1)

    25个增强iOS应用程序性能的提示和技巧(高级篇)(1) 2013-04-16 14:56 破船之家 beyondvincent 字号:T | T 在开发iOS应用程序时,让程序具有良好的性能是非常关 ...

  3. 25个增强iOS应用程序性能的提示和技巧(中级篇)(3)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(3) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  4. 25个增强iOS应用程序性能的提示和技巧(中级篇)(2)

    25个增强iOS应用程序性能的提示和技巧(中级篇)(2) 2013-04-16 14:42 破船之家 beyondvincent 字号:T | T 本文收集了25个关于可以提升程序性能的提示和技巧,分 ...

  5. 25个增强iOS应用程序性能的提示和技巧--中级篇

    25个增强iOS应用程序性能的提示和技巧--中级篇 标签: ios性能优化内存管理 2013-12-13 10:55 738人阅读 评论(0) 收藏 举报  分类: IPhone开发高级系列(34)  ...

  6. 25个增强iOS应用程序性能的提示和技巧(初级篇)

    25个增强iOS应用程序性能的提示和技巧(初级篇) 标签: ios内存管理性能优化 2013-12-13 10:53 916人阅读 评论(0) 收藏 举报  分类: IPhone开发高级系列(34)  ...

  7. 增强iOS应用程序性能的提示和技巧(25个)

    转自 http://www.cocoachina.com/newbie/basic/2013/0522/6259.html 在开发iOS应用程序时,让程序具有良好的性能是非常关键的.这也是用户所期望的 ...

  8. (转)25个增强iOS应用程序性能的提示和技巧--初级篇

    在开发iOS应用程序时,让程序具有良好的性能是非常关键的.这也是用户所期望的,如果你的程序运行迟钝或缓慢,会招致用户的差评.然而由于iOS设备的局限性,有时候要想获得良好的性能,是很困难的.在开发过程 ...

  9. 25个增强iOS应用程序性能的提示和技巧(0基础篇)

    在开发iOS应用程序时,让程序具有良好的性能是非常关键的. 这也是用户所期望的,假设你的程序执行迟钝或缓慢,会招致用户的差评.然而因为iOS设备的局限性,有时候要想获得良好的性能,是非常困难的. 在开 ...

随机推荐

  1. JAVA如何解压缩ZIP文档

    代码片段: package org.yu.units; import java.io.Closeable; import java.io.File; import java.io.FileInputS ...

  2. Moya 与 RxSwift 使用

    如在OC中使用AFNetworking一般,Swift我们用Alamofire来做网络库.而Moya在Alamofire的基础上又封装了一层: 1.关于moya moya 官方说moya有以下特性-_ ...

  3. OI 数论整理

    1.素数: 质数(prime number)又称素数,有无限个.一个大于1的自然数,除了1和它本身外,不能被其他自然数整除,换句话说就是该数除了1和它本身以外不再有其他的因数;否则称为合数. 2016 ...

  4. Java面试题集(五)

    三.开源框架 什么是mybaties? Mybaties是支持普通sql查询,存储过程和高级映射的优秀持久层框架.Mybaties消除了几乎所有的jdbc代码和参数的手工设置以及结果集的检索.Myba ...

  5. 【转】UITableViewCell自适应高度 UILabel自适应高度和自动换行

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {     ...

  6. Linux 系统的常用命令之 rm ,rm -rf , rm -f 以及rm 命令的其他参数命令

    1.rm -rf * 删除当前目录下的所有文件,这个命令很危险,应避免使用. 所删除的文件,一般都不能恢复! 2.rm -f 其中的,f参数 (f --force ) 忽略不存在的文件,不显示任何信息 ...

  7. numpy数组之读写文件

    目录 通过 numpy 读写 txt 或 csv 文件 通过 numpy 读写 npy 或 npz 文件 读写 npy 文件 读写 npz 文件 通过 h5py 读写 hdf5 文件 简单读取 通过切 ...

  8. raspberry pi系统安装

    1.格式化SD卡,用SDFormatter 2.解压下载的操作系统 3.复制操作系统到SD卡(要放在根目录,把最外面的文件夹路径去掉) 4.把SD卡插入raspberry pi,接上电源 5.在启动界 ...

  9. How to Upgrade PIP in Windows

    https://datatofish.com/upgrade-pip/ 这个网站写的非常的全

  10. Spring的Hello World工程

    通过Spring的Hello World工程研究以下几个点: 0.如何创建工程及引入依赖. 1.通过Spring的beans.xml实现依赖注入,动态创建实例. 2.了解Spring的工作原理. 具体 ...