从保留的向量读取比从非保留向量读取更快

Reading from a vector that was reserved is faster than reading from a non-reserved vector?

本文关键字:向量 读取 保留      更新时间:2023-10-16

>我有以下代码

#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,

  1. 我有一个"cout <<tot"(但此处未包含它(,因此不应对其进行优化。

  2. 如果我将所有 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;
}