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

时间:2016-09-27 18:41:57

标签: c++ performance

我有以下代码

#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

但是,如果我删除 points.reserve(sz); ,我会得到结果

  

填写:8.38341

     

阅读:1.6607

我明白为什么填充需要更长的时间,但为什么阅读这么慢?

编辑: 1.我正在使用xcode,LLVM 8.0,-O3,

  1. 我有一个“cout&lt;&lt; tot”(但这里没有包含它),所以不应该优化它。

  2. 如果我把所有的cout延迟到最后(通过在变量中保存“填充”的时间),我仍然会在“阅读”时间上有所不同。

3 个答案:

答案 0 :(得分:1)

这是一个疯狂的猜测,但可能是因为缓存效应。当您在reserve()之后不断向向量添加数据时,某些数据可能会保留在缓存中(回写缓存)。如果不加保留添加数据(),则必须重新定位数据,并且可以使用高速缓存直写副本完成向量数据的重定位。您可以通过首先使用一个读取通道预热缓存然后对第二次读取进行计时来测试这一点。

答案 1 :(得分:1)

可能发生的是您正遭受分页开销。这可以解释为什么你的结果难以重现。

如果我们假设每个条目只有24个字节,那么你的向量有1亿个条目,占用大约2.4 GB的内存。

在第一次运行时,(使用points.reserve(sz);)所有这些内存都会立即分配,并且您的计算机可能有足够的RAM来满足请求,所以一切都按照预期的顺路发生。

在第二轮中,(没有 points.reserve(sz);)向量开始变小,并且不断增长。 vector的实现使用数组,因此当它想要调整大小时,它会分配一个新数组,复制旧数据,然后释放旧数组。 (而不是做realloc(),这可能会在某种情况下远远地发生原因。)因此,每次调整数组大小时,都需要更多的内存,而我们留下的旧内存是高度分散的,所以它不会被重用,除非我们第一次内存耗尽,但现代机器往往会在他们承认内存不足之前点击页面文件。

因此,在最终分配发生时,您可能已经耗尽了物理内存,并且您的逻辑内存正在被分页。在这种情况下,在最后一次调整大小期间复制矢量可能会在颠簸的情况下发生:在复制完成之前,需要对页面中的一些页面进行页面调出以便为新页面腾出空间这也将是你的载体的一部分。

然后,当您尝试读取整个向量时,需要将这些页面中的每一个页面重新打包一次,从而延迟。

答案 2 :(得分:0)

代码不使用汇总值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;
}