从保留的向量读取比从非保留向量读取更快
Reading from a vector that was reserved is faster than reading from a non-reserved vector?
>我有以下代码
#include <chrono>
#include <iostream>
#include <vector>
int main() {
struct Point {
double x, y, z;
};
const size_t sz = 1'000'000'00;
auto start = std::chrono::steady_clock::now();
std::vector<Point> points;
points.reserve(sz);
for (size_t i = 0; i < sz; ++i) {
const double d_val = i;
points.push_back({d_val, 2.0*d_val, 3*d_val});
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end-start;
std::cout << "filling: " << diff.count() << std::endl;
double tot{0};
for (const auto& p : points)
tot += p.x + p.y + p.z;
diff = std::chrono::steady_clock::now()-end;
std::cout << "reading: " << diff.count() << std::end;
std::cout << tot;
return 0;
}
我得到的结果就在附近
填充物: 1.78711
读数: 0.233211
但是,如果我删除积分.reserve(sz(;我得到结果
填充物: 8.38341
阅读: 1.6607
我明白为什么填充需要更长的时间,但为什么阅读速度要慢得多?
编辑:1. 我正在使用 xcode, LLVM 8.0, -O3,
我有一个"cout <<tot"(但此处未包含它(,因此不应对其进行优化。
如果我将所有 cout 延迟到最后(通过节省"填充"变量的时间(,我仍然会在"阅读"时间上有所不同。
这是一个疯狂的猜测,但可能是因为缓存效应。当您在 reserve(( 之后继续向向量添加数据时,某些数据可能会保留在缓存中(回写缓存(。如果您继续添加数据而不添加 reserve((,则必须重新定位数据,并且矢量数据的这种重新定位可以通过缓存直写复制来完成。您可以通过先使用一次读取传递预热缓存,然后对第二次读取进行计时来测试这一点。
可能发生的情况是,您正在遭受分页开销的困扰。 这可以解释为什么你的结果如此难以重现。
您的向量有 1 亿个条目,如果我们假设每个条目只有 24 个字节,则占用大约 2.4 GB 的内存。
在第一次运行时,(使用 points.reserve(sz);
(所有这些内存都会立即分配,并且您的计算机可能有足够的 RAM 来满足请求,因此一切都按预期沿着快乐的道路发生。
在第二次运行中,(没有points.reserve(sz);
(向量开始很小,并不断增长。 vector
的实现使用数组,因此当它想要调整大小时,它会分配一个新数组,复制旧数据,然后释放旧数组。 (而不是做realloc()
可以想象,就地发生的可能性很小。 因此,每次调整数组大小时,都需要更多内存,并且我们留下的旧内存是高度碎片化的,因此除非我们首先耗尽内存,否则它不会被重用,但现代机器往往会在承认内存不足之前点击分页文件。
因此,在进行最终分配时,您可能已经用完了物理内存,并且正在对逻辑内存进行分页。 在这种情况下,上次调整大小期间矢量的复制可能会在抖动条件下发生:在复制完成之前,需要对作为矢量一部分的某些页面进行分页,以便为新页面腾出空间,这些页面也将成为矢量的一部分。
然后,当您尝试读取整个向量时,这些页面中的每一个都需要分页一次,从而延迟。
代码不使用聚合值tot
。编译器可以自由地优化整个 for 循环。
试试这个:
int main()
{
struct Point {
double x, y, z;
};
const size_t sz = 1'000'000'00;
auto start = std::chrono::steady_clock::now();
std::vector<Point> points;
points.reserve(sz);
for (size_t i = 0; i < sz; ++i) {
const double d_val = i;
points.push_back({d_val, 2.0*d_val, 3*d_val});
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end-start;
std::cout << "filling: " << diff.count() << std::endl;
double tot{0};
for (const auto& p : points)
tot += p.x + p.y + p.z;
diff = std::chrono::steady_clock::now()-end;
// <== here
std::cout << tot << std::endl;
// <== here
std::cout << "reading: " << diff.count() << std::endl;
}
- C++:如何读取分离变量,然后读取向量
- C++读取行到类向量
- 如何从文件中读取两个字符串和数字数组,并将它们存储在对象向量中
- 从流到邻接列表的向量读取图形
- 尝试将字符串从文件读取到无符号字符向量中
- 如何在 c++ 中从字符串向量中读取
- 将结构向量保存到文件中,并从C++文件中读取结构向量
- C++,从文件读取到结构,然后读取到向量(结构被推入向量太多次,而不仅仅是一次)
- 从.txt文件读取到C++中的双精度向量
- 用rapidjson读取子对象向量
- 对唯一 ptr 无效读取的引用向量
- 如何从输入文件中读取字符并将其存储到向量中?
- 如何使用 cin 将整数从控制台读取到向量中
- 通过将文本文件读取为字符串/向量来计算加权/未加权 GPA
- 以C++为单位将已知数量的整数输入正确读取到向量中
- C 从文件到向量读取
- 使用向量读取输入文件
- 在 TheADS 中的向量读取之间插入元素
- 从向量读取并分配给对象的成员
- 从保留的向量读取比从非保留向量读取更快