为啥向量总是比 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<bool>
甚至比您的解释所表明的还要不同。它甚至没有data()
成员函数。特别是,你说“当你写x = vector_bools[1];
时你实际上是在说:bools* bools_ptr = vector_bools.data(); x = bools_ptr[1];
”的段落完全是假的
公平点,本,我会尝试纠正和/或表明我是近似的。
@BenVoigt 已适应 int
并更清楚地表明我正在接近。以上是关于为啥向量总是比 C 数组慢,至少在这种情况下?的主要内容,如果未能解决你的问题,请参考以下文章