为什么vector始终比C数组慢,至少在这种情况下呢?

时间:2015-06-10 23:43:19

标签: c++ arrays vector

我试图使用Eratosthenes' Sieve算法找到所有不大于n的质数,我有以下代码,在矢量和C数组中实现了筛,我发现差不多在所有的时间里,C阵列总是更快。

使用vector:

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数组:

int countPrimes_array(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;
}

测试代码:

clock_t t;
t = clock();
int a;
for(int i=0; i<10; ++i)a = countPrimes_vector(8000000); 
t = clock() - t;
cout<<"time for vector = "<<t<<endl;

t = clock();
int b;
for(int i=0; i<10; ++i)b = countPrimes_array(8000000); 
t = clock() - t;
cout<<"time for array = "<<t<<endl;

输出:

 time for vector = 32460000
 time for array = 29840000

我已经测试了很多次,而C数组总是更快。它背后的原因是什么?

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

修改

正如以下评论所示,在启用优化-O2-O3(最初使用g++ test.cpp编译)后,vector和C数组之间的时差为不再有效,在某些情况下vector比C数组快。

1 个答案:

答案 0 :(得分:7)

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

以下代码显示两者之间的性能更接近,差异可能只是缺少样本(ideone的超时限制为5秒)。

#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);
}

现场演示:http://ideone.com/0Y9gQx

time for carray = 2251ms
time for vector = 2254ms

虽然在某些运行中,carray慢了1-2毫秒。同样,共享资源上的样本不足。

---编辑---

在您的主要评论中,您要问&#34;为什么优化会产生影响&#34;。

std::vector<bool> v = { 1, 2, 3 };
bool b[] = { 1, 2, 3 };

我们有3个元素的两个&#34;数组,所以请考虑以下内容:

v[10]; // illegal!
b[10]; // illegal!

STL的调试版本通常可以在运行时捕获这个(并且在某些情况下,编译时)。数组访问可能只会导致错误的数据或崩溃。

此外,STL是使用许多小成员函数调用实现的,例如size(),因为vector是一个类,[]实际上是通过函数调用(operator[]来实现的。{ {1}})。

编译器可以消除其中许多,但这种优化。如果你没有优化,那么

std::vector<int> v;
v[10];

做了类似的事情:

int* data() { return M_.data_; }

v.operator[](size_t idx = 10) {
    if (idx >= this->size()) {
        raise exception("invalid [] access");
    }
    return *(data() + idx);
}

即使数据是&#34; inlinable&#34;函数,为了使调试更容易,未经优化的代码将其保留为此。当您使用优化进行构建时,编译器会认识到这些函数的实现是如此微不足道,它可以将它们的实现替换为调用站点,并且它很快就会将上述所有内容简化为更像数组访问的操作。

例如,在上述情况下,可以先将operator[]减少为

v.operator[](size_t idx = 10) {
    if (idx >= this->size()) {
        raise exception("invalid [] access");
    }
    return *(M_.data_ + idx);
}

由于没有调试的编译可能会删除边界检查,因此它变为

v.operator[](size_t idx = 10) {
    return *(M_.data_ + idx);
}

所以现在内联可以减少

x = v[1];

x = *(v.M_.data_ + 1); // comparable to v.M_.data_[1];

是一个很小的惩罚。 c数组涉及内存中的数据块和一个适合指向块的寄存器的局部变量,你的引用直接相对于:

但是,使用矢量,你有一个矢量对象,它是一个指向数据的指针,一个大小和一个容量变量:

vector<T>  // pseudo code
{
    T* ptr;
    size_t size;
    size_t capacity;
}

如果你在计算机器指令,矢量将有3个变量来初始化和管理。

写作时

x = v[1];

鉴于上面的向量近似,你要说的是:

T* ptr = v.data();
x = ptr[1];

但是编译器在构建优化时通常足够聪明,可以识别它可以在循环之前执行第一行,但这往往会花费一个寄存器。

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

因此,您可能每次迭代测试功能或在现代处理器上查看少量机器指令,可能需要几纳秒或两秒的额外停机时间。