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

Posted

技术标签:

【中文标题】为啥向量总是比 C 数组慢,至少在这种情况下?【英文标题】:why vector is always slower than C array, at least in this case?为什么向量总是比 C 数组慢,至少在这种情况下? 【发布时间】:2015-06-10 23:43:19 【问题描述】:

我正在尝试使用 Eratosthenes'Sieve 算法找到所有不大于 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 数组:

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

编辑:

如下cmets提示,开启优化-O2-O3后(原来是用g++ test.cpp编译的),vector与C数组的时间差不再有效,在某些场合@ 987654332@ 比 C 数组快。

【问题讨论】:

没有数据结构比不上数组,就这么简单? 您使用哪些编译器选项进行编译?优化设置对于这类问题非常重要。此外,我认为您所经历的轻微(实际上并不多)性能下降对于您获得的额外安全性和易用性而言非常小。 代码不等价。在一种情况下,您使用值 49 来表示逻辑真,使用 48 来表示假。在另一种情况下,您使用 1 并将其与 0 进行比较。到底为什么要这样做。 在未启用优化的情况下进行性能测试在很大程度上是没有意义的,因为您正在测试生成的可执行文件易于调试,而不是旨在以最高效率运行的可执行文件。 @Allanqunzi 另一个区别是,在某些实现中,vector::operator[] 将对未优化的构建执行范围检查,而您将在性能测试中为此付出巨大的代价。 【参考方案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);

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

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

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

--- 编辑 ---

在您的主要 cmets 中,您会问“为什么优化可以有所作为”。

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

我们有两个由 3 个元素组成的“数组”,因此请考虑以下内容:

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

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

此外,STL 是通过对 size() 之类的许多小型成员函数调用来实现的,并且因为 vector 是一个类,所以 [] 实际上是通过函数调用 (operator[]) 实现的。

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

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

尽管 data 是一个“可内联”函数,但为了使调试更容易,未优化的代码将其保留为 this。当您使用优化构建时,编译器会认识到这些函数的实现是如此微不足道,它可以将它们的实现替换为调用站点,并很快将上述所有内容简化为更像数组访问的操作。

比如上面这种情况,可能先将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-array 涉及内存中的数据块和适合指向该块的寄存器的单个局部变量,您的引用直接相对于该块:

不过,有了向量,你就有了一个向量对象,它是一个指向数据的指针、一个大小和一个容量变量:

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];

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

【讨论】:

只是添加一个数据点:通过 g++4.9 和 10 次连续测试,我得到平均时间之间的差异在 0.01 个标准差之内,所以我们肯定是在谈论随机波动。跨度> vector&lt;bool&gt; 甚至比您的解释所表明的还要不同。它甚至没有data() 成员函数。特别是,你说“当你写x = vector_bools[1];时你实际上是在说:bools* bools_ptr = vector_bools.data(); x = bools_ptr[1];”的段落完全是假的 公平点,本,我会尝试纠正和/或表明我是近似的。 @BenVoigt 已适应 int 并更清楚地表明我正在接近。

以上是关于为啥向量总是比 C 数组慢,至少在这种情况下?的主要内容,如果未能解决你的问题,请参考以下文章

向量初始化比数组慢...为啥?

在这种特殊情况下,为啥 gccgo 比 gc 慢?

C语言全排列问题为啥用指针比数组慢很多?

c++ 向量比我的动态数组慢?

为啥 scipy 的稀疏 csr_matrix 的向量点积比 numpy 的密集数组慢?

为啥向量长度 SIMD 代码比普通 C 慢