C++ 为什么向量总是比C数组慢,至少在这种情况下是这样?

C++ 为什么向量总是比C数组慢,至少在这种情况下是这样?,c++,arrays,vector,C++,Arrays,Vector,我试图用Eratosthenes的筛子算法找到所有不大于n的素数,我有以下代码,用向量和C数组实现筛子,我发现几乎在所有的时间里,C数组总是更快 使用向量: int countPrimes_vector(int n) { int res = 0; vector<char>bitmap(n); memset(&bitmap[0], '1', bitmap.size() * sizeof( bitmap[0]));

我试图用Eratosthenes的筛子算法找到所有不大于n的素数,我有以下代码,用向量和C数组实现筛子,我发现几乎在所有的时间里,C数组总是更快

使用向量:

int countPrimes_vector(int n) {                  
    int res = 0; 
    vector<char>bitmap(n);
    memset(&bitmap[0], '1', bitmap.size() * sizeof( bitmap[0]));
    //vector<bool>bitmap(n, true); Using this one is even slower!!

    for (int i = 2; i<n; ++i){

        if(bitmap[i]=='1')++res;
        if(sqrt(n)>i)
        {
             for(int j = i*i; j < n; j += i) bitmap[j] = '0';
        }
    }

    return res;
} 
我已经测试过很多次了,C数组总是更快。这背后的原因是什么

我经常听说vector和C数组的性能是一样的,vector应该始终用作标准容器。这句话是真的,或者至少一般来说是真的?在什么情况下应首选C阵列

编辑:


正如下面的评论所表明的,在打开optimization-O2或-O3之后,最初它是用g++test.cpp编译的,向量和C数组之间的时间差不再有效,在某些情况下向量比C数组快

您的比较包含不一致性,这可以解释差异,另一个因素可能是编译没有充分优化的结果。一些实现在STL的调试构建中有很多额外的代码,例如MSVC对向量元素访问进行边界检查,这会显著降低调试构建的速度

下面的代码显示了两者之间更接近的性能,区别可能只是缺少示例,ideone的超时限制为5s

#include <vector>
#include <cmath>
#include <cstring>

int countPrimes_vector(int n) {  
    int res = 0; 
    std::vector<bool> bitmap(n, true);
    for (int i = 2; i<n; ++i){
        if(bitmap[i])
          ++res;
        if(sqrt(n)>i)
        {
             for(int j = i*i; j < n; j += i) bitmap[j] = false;
        }
    }
    return res;
}

int countPrimes_carray(int n) {  
    int res = 0; 
    bool* bitmap = new bool[n];
    memset(bitmap, true, sizeof(bool) * n);
    for (int i = 2; i<n; ++i){

        if(bitmap[i])++res;
        if(sqrt(n)>i)
        {
             for(int j = i*i; j < n; j += i) bitmap[j] = false;
        }
    }
    delete []bitmap;
    return res;
}

#include <chrono>
#include <iostream>

using namespace std;

void test(const char* description, int (*fn)(int))
{
    using clock = std::chrono::steady_clock;
    using ms = std::chrono::milliseconds;

    auto start = clock::now();

    int a;
    for(int i=0; i<9; ++i)
        a = countPrimes_vector(8000000); 

    auto end = clock::now();
    auto diff = std::chrono::duration_cast<ms>(end - start);

    std::cout << "time for " << description << " = " << diff.count() << "ms\n";
}

int main()
{
    test("carray", countPrimes_carray);
    test("vector", countPrimes_vector);
}
尽管在一些跑步中,卡雷速度慢了1-2毫秒。同样,共享资源上的样本不足

--编辑--

在你的主要评论中,你会问为什么优化可以带来不同

std::vector<bool> v = { 1, 2, 3 };
bool b[] = { 1, 2, 3 };
STL的调试版本通常可以在运行时捕捉到这一点,在某些情况下,还可以在编译时捕捉到这一点。阵列访问可能只会导致坏数据或崩溃

此外,STL是使用许多小成员函数调用来实现的,比如大小,并且由于vector是一个类,[]实际上是通过函数调用操作符[]来实现的

编译器可以消除其中许多,但这是优化。如果不优化,那么

std::vector<int> v;
v[10];
即使数据是一个可内联的函数,为了使调试更容易,未优化的代码将其保留如下。当您使用优化进行构建时,编译器会意识到这些函数的实现非常简单,只需将它们的实现替换到调用站点中即可,并很快将上述所有操作简化为更类似于数组访问的操作

例如,在上述情况下,它可能首先将运算符[]减少为

由于在不进行调试的情况下编译可能会删除边界检查,因此

v.operator[](size_t idx = 10) {
    return *(M_.data_ + idx);
}
所以现在内联线可以减少

x = v[1];

有一个很小的惩罚。c数组包含内存中的数据块和一个本地变量,该变量装入指向该块的寄存器,您的引用与该块直接相关:

但是,对于向量,您有一个向量对象,它是指向数据的指针、大小和容量变量:

vector<T>  // pseudo code
{
    T* ptr;
    size_t size;
    size_t capacity;
}
根据上面的向量近似值,你可以这样说:

T* ptr = v.data();
x = ptr[1];
但是,编译器在使用优化构建时通常足够聪明,能够识别出它可以在循环之前完成第一行,但这往往需要花费一个寄存器

T* ptr = v.data(); // in debug, function call, otherwise inlined.
for ... {
    x = ptr[1];
}

因此,您可能在测试函数的每次迭代中看到更多的机器指令,或者在现代处理器上看到更多的机器指令,可能需要一到两纳秒的额外时间。

您的比较包含不一致性,这可以解释差异,另一个因素可能是编译时没有充分优化的结果。一些实现在STL的调试构建中有很多额外的代码,例如MSVC对向量元素访问进行边界检查,这会显著降低调试构建的速度

下面的代码显示了两者之间更接近的性能,区别可能只是缺少示例,ideone的超时限制为5s

#include <vector>
#include <cmath>
#include <cstring>

int countPrimes_vector(int n) {  
    int res = 0; 
    std::vector<bool> bitmap(n, true);
    for (int i = 2; i<n; ++i){
        if(bitmap[i])
          ++res;
        if(sqrt(n)>i)
        {
             for(int j = i*i; j < n; j += i) bitmap[j] = false;
        }
    }
    return res;
}

int countPrimes_carray(int n) {  
    int res = 0; 
    bool* bitmap = new bool[n];
    memset(bitmap, true, sizeof(bool) * n);
    for (int i = 2; i<n; ++i){

        if(bitmap[i])++res;
        if(sqrt(n)>i)
        {
             for(int j = i*i; j < n; j += i) bitmap[j] = false;
        }
    }
    delete []bitmap;
    return res;
}

#include <chrono>
#include <iostream>

using namespace std;

void test(const char* description, int (*fn)(int))
{
    using clock = std::chrono::steady_clock;
    using ms = std::chrono::milliseconds;

    auto start = clock::now();

    int a;
    for(int i=0; i<9; ++i)
        a = countPrimes_vector(8000000); 

    auto end = clock::now();
    auto diff = std::chrono::duration_cast<ms>(end - start);

    std::cout << "time for " << description << " = " << diff.count() << "ms\n";
}

int main()
{
    test("carray", countPrimes_carray);
    test("vector", countPrimes_vector);
}
尽管在一些跑步中,卡雷速度慢了1-2毫秒。同样,共享资源上的样本不足

--编辑--

在你的主要评论中,你会问为什么优化可以带来不同

std::vector<bool> v = { 1, 2, 3 };
bool b[] = { 1, 2, 3 };
STL的调试版本通常可以在运行时捕捉到这一点,在某些情况下,还可以在编译时捕捉到这一点。阵列访问可能只会导致坏数据或崩溃

此外,STL是使用许多小成员函数调用来实现的,比如大小,并且由于vector是一个类,[]实际上是通过函数调用操作符[]来实现的

编译器可以消除其中许多,但这是优化。如果不优化,那么

std::vector<int> v;
v[10];
即使数据是一个可内联的函数,为了使调试更容易,未优化的代码将其保留如下。当您使用优化进行构建时,编译器会意识到这些函数的实现非常琐碎,以至于无法实现 n只需将它们的实现替换到调用站点中,就可以很快将上述所有操作简化为更类似于阵列访问的操作

例如,在上述情况下,它可能首先将运算符[]减少为

由于在不进行调试的情况下编译可能会删除边界检查,因此

v.operator[](size_t idx = 10) {
    return *(M_.data_ + idx);
}
所以现在内联线可以减少

x = v[1];

有一个很小的惩罚。c数组包含内存中的数据块和一个本地变量,该变量装入指向该块的寄存器,您的引用与该块直接相关:

但是,对于向量,您有一个向量对象,它是指向数据的指针、大小和容量变量:

vector<T>  // pseudo code
{
    T* ptr;
    size_t size;
    size_t capacity;
}
根据上面的向量近似值,你可以这样说:

T* ptr = v.data();
x = ptr[1];
但是,编译器在使用优化构建时通常足够聪明,能够识别出它可以在循环之前完成第一行,但这往往需要花费一个寄存器

T* ptr = v.data(); // in debug, function call, otherwise inlined.
for ... {
    x = ptr[1];
}

因此,您可能会在测试函数的每次迭代中看到更多的机器指令,或者在现代处理器上看到更多的机器指令,可能需要一纳秒或两纳秒的额外时间。

没有数据结构比不上数组,因为它很简单?您使用了哪些编译器选项进行编译?优化设置对于这类问题非常重要。此外,我还认为,对于您获得的额外安全性和易用性而言,您所经历的性能下降非常小。这些代码并不等效。在一种情况下,使用值49表示逻辑True,使用值48表示false。在另一种情况下,使用1并将其与0进行比较。你到底为什么要这样做。在没有启用优化的情况下进行性能测试在很大程度上是毫无意义的,因为你测试的是生成的易于调试的可执行文件,而不是旨在以最高效率运行的可执行文件。@Allankunzi另一个区别是在某些实现上vector::operator[]将对未优化的构建执行范围检查,并且您将在性能测试中为此付出巨大的代价。没有数据结构比不上数组,因为它很简单?您使用什么编译器选项编译?优化设置对于这类问题非常重要。此外,我还认为,对于您获得的额外安全性和易用性而言,您所经历的性能下降非常小。这些代码并不等效。在一种情况下,使用值49表示逻辑True,使用值48表示false。在另一种情况下,使用1并将其与0进行比较。你到底为什么要这样做。在没有启用优化的情况下进行性能测试在很大程度上是毫无意义的,因为你测试的是生成的易于调试的可执行文件,而不是旨在以最高效率运行的可执行文件。@Allankunzi另一个区别是在某些实现上vector::operator[]将对未优化的构建执行范围检查,您将在性能测试中为此付出巨大的代价。只需添加一个数据点:对于g++4.9和10个连续测试,我发现平均时间之间的差异在0.01标准偏差之内,所以我们肯定是在讨论随机波动。向量比你的解释更不一样。它甚至没有数据成员函数。特别是,当你写x=vector_bools[1]时,你说的段落;你实际上是在说:bools*bools\u ptr=vector\u bools.data;x=布尔斯_ptr[1];完全是假的公平点,Ben,我将尝试纠正和/或表明我是近似值。@BenVoigt适应int和更清晰的指示,表明我是近似值。只需添加一个数据点:通过g++4.9和10个连续测试,我得到平均时间之间的差在0.01标准偏差之内,所以我们肯定是在讨论随机波动。向量比你的解释更不一样。它甚至没有数据成员函数。特别是,当你写x=vector_bools[1]时,你说的段落;你实际上是在说:bools*bools\u ptr=vector\u bools.data;x=布尔斯_ptr[1];完全是假的,Ben,我会尝试纠正和/或表明我是近似值。@BenVoigt适应int,更清楚地表明我是近似值。
T* ptr = v.data(); // in debug, function call, otherwise inlined.
for ... {
    x = ptr[1];
}