c++ 高效使用vector(面试)

文章目录

    • 1.善用Reserve提前分配足够的空间
    • 2. 使用 shrink_to_fit() 释放 vector 占用的内存, – clear() 或 erase() 不会释放内存
    • 3 在填充或者拷贝到 vector 的时候,应该使用赋值而不是 insert() 或push_back()
    • 4 遍历 std::vector 元素的时候,避免使用 std::vector::at() 函数
    • 5 尽量避免在 vector 前部插入元素
    • 6 在向 vector 插入元素的时候使用 emplace_back() 而不是 push_back()
    • 7 释放vector
    • 参考资料

我们在 vector 中存入 TestStruct 结构的数据,并使用FillVector()来填充 vector。它们的定义如下。我们会使用 Stopwatch(用于计时)。这个工具由 Kjell 创建,在 https://github.com/KjellKod/Stopwatch 可以找到。

// Test struct to be inserted/removed from vector
struct BigTestStruct
{int iValue = 1;float fValue;long lValue;double dValue;char cNameArr[10];int iValArr[100];
};
// Helper function to populate the test vectors
void FillVector(vector<BigTestStruct>& testVector)
{for (int i = 0; i < 10000; i++){BigTestStruct bt;testVector.push_back(bt);}
}

马上开始在 C++ 11 中优化 vector 用法的介绍

1.善用Reserve提前分配足够的空间

提前分配足够的空间以避免不必要的重新分配和复制周期

程序员喜欢使用 vector,因为他们只需要往向容器中添加元素,而不用事先操心容器大小的问题。但是,如果由一个容量为 0 的 vector 开始,往里面添加元素会花费大量的运行性能。如果你之前就知道 vector 需要保存多少元素,就应该提前为其分配足够的空间

这里有一个简单的示例,往 vector 里添加 1 万个测试结构的实例——先进行不预分配空间的测试再进行有预分配的测试

vector<BigTestStruct> testVector1;
vector<BigTestStruct> testVector2;
sw.Restart();
FillVector(testVector1);
cout << "Time to Fill Vector Without Reservation:" << sw.ElapsedUs() << endl;
sw.Restart();
testVector2.reserve(10000);
FillVector(testVector2);
cout << "Time to Fill Vector With Reservation:" << sw.ElapsedUs() << endl;

在我的计算机中,未预分配空间的情况用了 5145 us,而预分配了空间的情况下只用了 1279us,性能提高了 75.14%!!!

对于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() 函数告诉我们为这个容器分配的内存可以存入多少元素。

FillVector(testVector1);
size_t capacity = testVector1.capacity();
cout << "Capacity Before Erasing Elements:" << capacity << endl;testVector1.erase(testVector1.begin(), testVector1.begin() + 3); //
capacity = testVector1.capacity();
cout << "Capacity After Erasing 3 elements Elements:" << capacity << endl;
testVector1.clear();
capacity = testVector1.capacity();
cout << "Capacity After clearing all emements:" << capacity << endl;
testVector1.shrink_to_fit();
capacity = testVector1.capacity();
cout << "Capacity After shrinking the Vector:" << capacity << endl;

下面是输出:

Capacity Before Erasing Elements:12138
Capacity After Erasing 3 elements Elements:12138
Capacity After clearing all emements:12138
Capacity After shrinking the Vector:0

从上面的输出可以看到,erase() 或 clear() 不会减少 vector 占用的内存。如果在代码中到达某一点,不再需要 vector 时候,请使用std::vector::shrink_to_fit()方法释放掉它占用的内存。std::vector::shrink_to_fit(): 作用减少容器的容量以适应真实存放数据的大小并销毁超出容量的所有元素

请注意,shrink_to_fit() 可能没有被所有编译器供应商完全支持。这种情况下,可以使用“Swap 惯用法”来清空 vector,代码如下

container<T>( c ).swap( c ); // shrink-to-fit 惯用法,用于清空存储空间
container<T>().swap( c );    // 用于清空所有内容和存储空间的惯用法 

3 在填充或者拷贝到 vector 的时候,应该使用赋值而不是 insert() 或push_back()

从一个 vector 取出元素来填充另一个 vector 的时候,常有三种方法 – 把旧的 vector 赋值给新的 vector,使用基于迭代器的 std::vector::insert() 或者使用基于循环的 std::vector::push_back()。这些方法都展示在下面:

vector<BigTestStruct> sourceVector, destinationVector;
FillVector(sourceVector);
// Assign sourceVector to destination vector
sw.Restart();
destinationVector = sourceVector;
cout << "Assigning Vector :" << sw.ElapsedUs() << endl;
//Using std::vector::insert()
vector<BigTestStruct> sourceVector1, destinationVector1;
FillVector(sourceVector1);
sw.Restart();
destinationVector1.insert(destinationVector1.end(),sourceVector1.begin(),sourceVector1.end());
cout << "Using insert() :" << sw.ElapsedUs() << endl;
这是它们的性能:赋值: 589.54 usinsert(): 1321.27 uspush_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)使用下标 –[ ]运算符

下面展示了每种用法:

//Using an iterator
vector<BigTestStruct> testVectorSum;
FillVector(testVectorSum);
sw.Restart();
int sum = 0;
for (auto it = testVectorSum.begin(); it != testVectorSum.end(); ++it)
{sum = sum + it->iValue;
}
cout << "Using Iterator:" << sw.ElapsedUs() << endl;//Using the at() member function
sw.Restart();
sum = 0;
for (unsigned i = 0; i < testVectorSum.size(); ++i)
{sum = sum + testVectorSum.at(i).iValue;
}
cout << "Using at() :" << sw.ElapsedUs() << endl;// Using the subscript notation
sw.Restart();
sum = 0;
for (unsigned i = 0; i < testVectorSum.size(); ++i)
{sum = sum + testVectorSum[i].iValue;
}
cout << "Using subscripting:" << sw.ElapsedUs() << endl;

输出是:

Using Iterator:0
Using at() :3.73
Using subscripting:0

显而易见,用std::vector::at()函数访问 vector 元素是最慢的一个。

5 尽量避免在 vector 前部插入元素

任何在 vector 前部部做的插入操作其复杂度都是 O(n) 的。在前部插入数据十分低效,因为 vector 中的每个元素项都必须为新的项腾出空间而被复制。如果在 vector 前部连续插入多次,那可能需要重新评估你的总体架构。

做个有趣的尝试,下面是在 std::vector 前部做插入和在 std::list 前部部做插入的对比:

vector<BigTestStruct> sourceVector3, pushFrontTestVector;
FillVector(sourceVector3);
list<BigTestStruct> pushFrontTestList;
//Push 100k elements in front of the new vector -- this is horrible code !!! 
sw.Restart();
for (unsigned i = 1; i < sourceVector3.size(); ++i)
{pushFrontTestVector.insert(pushFrontTestVector.begin(), sourceVector3[i]);
}
cout << "Pushing in front of Vector :" << sw.ElapsedUs() << endl;
// push in front of a list
sw.Restart();
for (unsigned i = 0; i < sourceVector3.size(); ++i)
{pushFrontTestList.push_front(sourceVector3[i]);
}
cout << "Pushing in front of list :" << sw.ElapsedUs() << endl;

如果我运行这个测试10,其中使用一个包含100个元素的vector,那么输出结果如下:

Average of Pushing in front of Vector :11999.4
Average of Pushing in front of list :20.36

list 前部部插入操作比在 vector 前部部快大约58836%。不用感到奇怪,因为在 list 前部做元素插入的算法,其复杂度为 O(1)。显然,vector 包含元素越多,这个性能测试的结果会越差。

6 在向 vector 插入元素的时候使用 emplace_back() 而不是 push_back()

几乎赶上 C++11 潮流的每个人都明确地认同“安置”这种往 STL 容器里插入元素的方法。理论上来说,“安置”更有效率。然而所有实践都表明,有时候性能差异甚至可以忽略不计。

思考下面的代码:

vector<BigTestStruct> sourceVector4, pushBackTestVector, emplaceBackTestVector;
FillVector(sourceVector4);
//Test push back performance
sw.Restart();
for (unsigned i = 0; i < sourceVector4.size(); ++i)
{pushBackTestVector.push_back(sourceVector4[i]);
}
cout << "Using push_back :" << sw.ElapsedUs() << endl;
//Test emplace_back()
sw.Restart();
for (unsigned i = 0; i < sourceVector4.size(); ++i)
{emplaceBackTestVector.emplace_back(sourceVector4[i]);
}
cout << "Using emplace_back :" << sw.ElapsedUs() << endl;

如果运行100次,会得到这样的输出:

Average Using push_back :5431.58
Average Using emplace_back :5254.64

可以清楚的看到,“安置”函数比插入函数性能更好 – 但只有 177 微秒的差距。在所有情况下,他们大致是相当的。

仅在以下情况下,Emplacement 函数可能会更快:

  • 要添加的值是在 vector 中构造的,而不是赋值的。
  • 传递的参数类型与 vector 中保存的类型不同。例如,如果一个向量包含 std :: string,但我们传递一个字符串值到该 vector。

即使上述两个条件都不成立,如本例所示的,你也不要因为在插入时使用 emplacement 而掉以轻心。
更多关于 emplacement vs. insertion 的详细信息,请查看 Scott Meyer 的“Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14“一书中的条目#42。

7 释放vector

在某个时间点后再也不会用到vector了,可以释放它所占用的空间及其成员所占用的空间

int main() {std::vector<int> randy;for (int i = 0; i < 20; i++) {randy.push_back(i);}std::cout << "randy size: " << randy.size() << " capacity: " << randy.capacity()<< std::endl;randy.clear();std::cout << "after clear, randy size: " << randy.size() << " capacity: " << randy.capacity()<< std::endl;std::vector<int>().swap(randy);std::cout << "swap with empty vector, randy size: " << randy.size() << " capacity: " << randy.capacity()<< std::endl;return 0;
}

运行结果:

randy size: 20 capacity: 32
after clear, randy size: 0 capacity: 32
swap with empty vector, randy size: 0 capacity: 0

真正释放内存是在vector的析构函数里进行的,所以一旦超出vector的作用域,首先它所保存的所有对象会被析构,然后会调用allocator中的deallocate函数回收对象本身的内存。

上述释放内存代码 std::vector<int>().swap(randy);可以替换为以下形式:

{std::vector<int> randy;for (int i = 0; i < 20; i++) {randy.push_back(i);}
} // 离开作用域,randy会调用自己的析构函数释放内存

如果vector内存的是指针,需要先释放每个指针所指内存,再释放vector

  std::vector<Randy *> sanjie(20, new Randy());for (size_t i = 0; i < 20; i++) {delete sanjie[i];sanjie[i] = nullptr;}randy.clear();std::vector<Randy *>().swap(sanjie);

参考资料

(1) 高效使用vector
(2) 6 个技巧,提升 C++11 的 vector 性能

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

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

相关文章

Maven多模块管理(转载)

注意&#xff1a;父模块需设定打包方式为pom https://cloud.tencent.com/developer/article/1667275 dependencyManagement 统一管理子类依赖版本 在父类maven中加入&#xff0c;不会继承给子类&#xff0c;只能规定子类的依赖版本&#xff0c;子类加入dependence后无需写入 …

【前端学习】—JS判断数据类型的方式有哪些(八)

【前端学习】—JS判断数据类型的方式有哪些&#xff08;八&#xff09; 一、JS中判断数据类型的场景 二、JS中有哪些数据类型 三、JS判断数据类型的方式有哪些 const arr[]; const object{};const number1; const stringstring;//typeofconst typetypeof arr; console.log(type…

从头开始机器学习:神经网络

一、说明 如果你还没有做过逻辑回归&#xff0c;你会在这里挣扎。我强烈建议在开始之前查看它。您在逻辑回归方面的能力将影响您学习神经网络的难易程度和速度。 二、神经网络简介 神经网络是一个神经元网络。这些神经元是逻辑回归函数&#xff0c;它们被链接在一起形成一个网络…

只会Python,怎么用PC控制无人机自动飞行?

PC-SDK是阿木实验室 (AMOVLAB) 为了简化开源飞控的控制协议MAVLink&#xff0c;优化和维护的一个基于PC电脑运行MAVSDK(支持Windows和Ubuntu)的Python SDK库。 相对于传统的无人机控制开发&#xff0c;开发者无需掌握C/C语言和ROS等相关知识&#xff0c;只要学会Python编程及懂…

Gin:获取本机IP,获取访问IP

获取本机IP func GetLocalIP() []string {var ipStr []stringnetInterfaces, err : net.Interfaces()if err ! nil {fmt.Println("net.Interfaces error:", err.Error())return ipStr}for i : 0; i < len(netInterfaces); i {if (netInterfaces[i].Flags & ne…

leetcode 1143. 最长公共子序列、1035. 不相交的线、53. 最大子数组和

1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些…

wsl使用vscode连接,远程安装C/C++ 拓展时,报错

报错内容&#xff1a; EACCES: permission denied, rename /home/wen/.vscode-server/extensions/.b61b1c7c-f703-4dfd-bdc5-d9a00681c4b7 -> /home/wen/.vscode-server/extensions/ms-vscode.cpptools-1.17.5-linux-x64 解决办法&#xff1a; 升级wsl到wsl2就好了。 &a…

Vue-router快速入门 是什么 如何跳转 如何传值的问题

3.1 Vue-router是什么 Vue-router:Vue.js 的官方路由为 Vue.js 提供富有表现力、可配置的、方便的路由 官网&#xff1a;https://router.vuejs.org/zh/ 作用&#xff1a; 1.实现vue页面(组件)的跳转 2.可以在跳转的时候携带参数 3.2 Vue3使用Vue-router(静态路由) 基于Vu…

C# CodeFormer Inpainting 人脸填充

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace CodeFormer_D…

UnitTesting 单元测试

1. 测试分为两种及详细介绍测试书籍: 1.1 Unit Test : 单元测试 - test the business logic in your app : 测试应用中的业务逻辑 1.2 UI Test : 界面测试 - test the UI of your app : 测试应用中的界面 1.3 测试书籍网址:《Testing Swift》 https://www.hackingwithswift.c…

MySQL——六、库表操作(下篇)

MySQL 一、INSERT语句二、REPLACE语句三、UPDATE语句四、delete和TRUNCATE语句五、MySQL用户授权1、密码策略2、用户授权和撤销授权 一、INSERT语句 #在表里面插入数据&#xff1a;默认情况下&#xff0c;一次插入操作只插入一行 方式1&#xff1a; INSERT [INTO] 表名 [(colu…

uni-app小程序使用DCloud(插件市场)流程

一、DCloud&#xff08;插件市场&#xff09; DCloud 是uni-app官方插件市场&#xff0c;里面有官方、团队、个人发布的众多插件&#xff0c;包括uni-ui、uni-pay 等。而像uni-ui这种大型组件库都有官方文档可参考&#xff0c;但一些团队或个人发布的小型插件没有文档&#xf…

数据结构-快速排序Java实现

目录 一、引言二、算法步骤三、原理演示第一步&#xff1a;选择基准元素第二步&#xff1a;分区&#xff08;Partition&#xff09;第三步&#xff1a;递归第四步&#xff1a;合并 四、代码实战五、结论 一、引言 快速排序的核心思想是选择一个基准元素&#xff0c;将数组分为两…

垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程

文章目录 &#x1f34a; 垃圾回收器、垃圾回收算法、空间分配担保&#x1f389; Serial&#x1f389; ParNew&#x1f389; Parallel scavenge&#x1f389; 复制算法&#x1f389; 分代收集算法&#x1f389; 进入老年代的几种情况&#x1f4dd; 空间分配担保 &#x1f389; S…

超火的双臂烹饪机器人Project YORI,分分钟成为你的专属大厨!

原创 | 文 BFT机器人 当前行业内有两种通用的烹饪自动化方法&#xff1a;一种是“制造一个可以在普通厨房中运作的烹饪机器人&#xff0c;因为每个人都有厨房”&#xff0c;这听起来很不错&#xff0c;但接下来你就必须使你的烹饪机器人能够在厨房环境中正常运行&#xff0c;这…

docker 复习

文章目录 1. docker 基础1.1 docker 安装配置镜像加速器拉取镜像的仓库&#xff1a; docker 部署Mysql 镜像docker 命令的详细解释docker 常见命令docker 数据卷docker 相关命令总结 2.自定义镜像2.1 dockerfile2.2 try 构建一个Java镜像&#xff0c;并部署2.3 总结: 3. docker…

物流监管:智慧仓储数据可视化监控平台

随着市场竞争加剧和市场需求的不断提高&#xff0c;企业亟需更加高效、智能且可靠的仓储物流管理方式&#xff0c;以提升企业的物流效率&#xff0c;减少其输出成本&#xff0c;有效应对市场上的变化和挑战。 图扑自研 HT for Web 产品搭建的 2D 智慧仓储可视化平台&#xff0c…

2023年各省市区CMMI奖励补贴政策,最高150万元!!!

2023年CMMI资质认证补贴政策已落实&#xff0c;全国各省市政府相继给了很多补贴政策支持企业办理CMMI认证&#xff0c;有不少地区都是可以领取CMMI资质补贴的&#xff0c;来看看你企业所在地有没有补贴吧&#xff01; 北京 北京市 对服务外包企业取得的国际资质认证、或因数字…

Databend 开源周报第 115 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 聚合索引 Data…

[计算机提升] 系统及用户操作

1.4 系统及用户操作 1.4.1 系统操作 1.4.1.1 开机、关机、重启 在Windows系统中&#xff0c;开机&#xff08;Power On&#xff09;&#xff0c;关机&#xff08;Shutdown&#xff09;和重启&#xff08;Restart&#xff09;是指计算机的不同电源控制操作。 开机&#xff1a;…