std::vector 的性能不佳是否是由于未调用 realloc 对数次数所致?

2024-01-08

EDIT:我又添加了两个基准测试,以比较 realloc 与 C 数组的使用以及 Reserve() 与 std::vector 的使用。从最后的分析看来,realloc 的影响很大,即使只调用了 30 次。检查文档,我猜这是因为 realloc 可以返回一个全新的指针,复制旧的指针。 为了完成该场景,我还添加了用于在初始化期间完全分配数组的代码和图表。区别于reserve()是有形的。

编译标志:只有图中描述的优化,使用 g++ 编译,仅此而已。

原问题:

我做了一个基准std::vector与新/删除数组相比,当我添加 10 亿个整数时,第二个代码比使用向量的代码快得多,尤其是在打开优化的情况下。

我怀疑这是由于向量内部调用realloc太多次造成的。如果向量每次被填充时大小不会增加一倍(这里数字 2 没有什么特别的,重要的是它的大小呈几何级数增长),就会出现这种情况。 在这种情况下,对 realloc 的调用只会是O(log n)代替O(n).

如果这就是导致第一个代码缓慢的原因,我如何告诉 std::vector 以几何级数增长?

请注意,在这种情况下调用reserve一次可以工作,但在提前不知道push_back的数量的更一般情况下则不行。

黑线

#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;

    std::vector <int> b(size);
    for(int i = 0; i < size; i++) {
        b[i]=i;
    }    
    return 0;
}

蓝线

#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;    
    std::vector <int> b;
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

绿线

#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;
    std::vector <int> b;
    b.reserve(size);
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

red line

int main(int argc, char * argv[]) {
    const int size = 1000000000;
    int * a = new int [size];
    for(int i = 0; i < size; i++) {
        a[i] = i;
    }
    delete [] a;   
    return 0;
}

橙色线

#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;
    int * a = (int *)malloc(size*sizeof(int));
    int next_power = 1;
    for(int i = 0; i < size; i++) {
        a[i] = i;
        if(i == next_power - 1) {
            next_power *= 2;
            a=(int*)realloc(a,next_power*sizeof(int));
        }
    }
    free(a);
    return 0;
}

编辑:检查.capacity()正如所建议的,我们看到增长确实是指数级的。那么为什么向量这么慢呢?


优化后的C风格数组被优化为无。

上神螺栓 https://godbolt.org/g/nUGn9c:

xorl %eax, %eax
retq

这就是程序。

每当你的程序优化到接近 0 秒时,你就应该考虑这种可能性。

优化器发现您没有对分配的内存进行任何操作,注意到未使用的分配内存可能具有零副作用,并消除了分配。

并且写入内存然后不读取它也具有零副作用。

相比之下,编译器很难证明向量的分配是无用的。也许编译器开发人员可以教它识别未使用的 std 向量,就像识别未使用的原始 C 数组一样,但这种优化确实是一个极端情况,根据我的经验,它会导致很多分析问题。

请注意,任何优化级别的带有保留的向量与未优化的 C 风格版本的速度基本上相同。

在C风格的代码中,唯一需要优化的是“不做任何事情”。在矢量代码中,未优化的版本充满了额外的堆栈帧和调试检查,以确保您不会超出范围(如果超出范围,则干净地崩溃)。

请注意,在 Linux 系统上,分配大块内存除了修改虚拟内存表外不会执行任何操作。只有当内存被触及时,它才会真正为你找到一些归零的物理内存。

如果没有保留,std 向量必须猜测初始的小尺寸,调整其大小并复制它,然后重复。这会导致 50% 的性能损失,这对我来说似乎是合理的。

有了保留,它实际上就完成了工作。这项工作只需不到 5 秒。

通过推回添加到向量确实会导致它呈几何级数增长。几何增长导致每条数据生成 2-3 个副本的渐近平均值。


至于 realloc,std::vector 确实not重新分配。它分配一个新的缓冲区,并复制旧数据,然后丢弃旧数据。

Realloc 尝试增大缓冲区,如果不能,则按位复制缓冲区。

对于按位复制类型来说,这比 std 向量更有效。我敢打赌 realloc 版本实际上永远不会复制;总是有内存空间可以将向量增长到其中(在实际程序中可能并非如此)。

标准库分配器中缺少 realloc 是一个小缺陷。你必须为它发明一个新的 API,因为你希望它能够用于非按位复制(类似于“尝试增加分配的内存”,如果失败,则由你来增加分配)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

std::vector 的性能不佳是否是由于未调用 realloc 对数次数所致? 的相关文章

随机推荐