为啥 std::vector 的速度是原始数组的两倍?包含完整代码
Posted
技术标签:
【中文标题】为啥 std::vector 的速度是原始数组的两倍?包含完整代码【英文标题】:Why is std::vector twice as fast as raw array? Complete code included为什么 std::vector 的速度是原始数组的两倍?包含完整代码 【发布时间】:2014-04-06 22:36:13 【问题描述】:结果:
向量时间:7051
阵列时间:18944
我为此使用了 MSVC 发布模式,编译为 32 位。
在此测试之前,我查看了向量的 GCC 源代码并感到惊讶,因为我认为 operator[]
检查了数组越界,但事实并非如此。不过,没想到向量这么快?!
完整代码:
#include <iostream>
#include <vector>
int main()
const int size = 10000;
unsigned long long my_array[size];
std::vector<unsigned long long> my_vec;
my_vec.resize(size);
//Populate containers
for(int i=0; i<size; i++)
my_vec[i] = i;
my_array[i] = i;
//Initialise test variables
unsigned long long sum = 0;
unsigned long long time = 0;
unsigned long long start = 0;
unsigned long long finish = 0;
//Time the vector
start = __rdtsc();
for(int i=0; i<size; i++)
sum += my_vec[i];
finish = __rdtsc();
time = finish - start;
std::cout << "Vector time: " << time << " " << sum << std::endl;
sum = 0;
//Time the array
start = __rdtsc();
for(int i=0; i<size; i++)
sum += my_array[i];
finish = __rdtsc();
time = finish - start;
std::cout << "Array time: " << time << " " << sum << std::endl;
int t = 8;
std::cin >> t;
return 0;
【问题讨论】:
当我测试它时,数组总是更快。向量时间:83755,数组时间:69753。当然,执行之间的值会有所不同,但不会太大。 尝试恢复这些测试。惊喜! @user997112:在向量的情况下,您在填充后立即读取它。在数组的情况下,在数组填充和数组读取之间有一个完整的向量读取循环。 也许比另一种更快的测试更好的方法是创建两个单独的程序,其中一个严格来说是standard C array
,另一个严格来说是std::vector
。从算法上讲,这两个程序都必须运行完全相同的测试。
除非您将进程固定到特定内核,否则您不能信任 TSC 测量结果,即使这样您也需要确保您使用的内在函数也省略了阻止指令的指令(例如 CPUID)在执行管道中重新排序(因此您尝试测量的代码相对于 TSC 读取进行序列化)。这个基准是不可信的。
【参考方案1】:
以下使用的是 MSVC 2013。
对于向量:
0019138E mov edi,edi
for (int i = 0; i<size; i++)
00191390 lea ecx,[ecx+20h]
sum += my_vec[i];
00191393 movdqu xmm0,xmmword ptr [ecx-20h]
00191398 paddq xmm1,xmm0
0019139C movdqu xmm0,xmmword ptr [ecx-10h]
001913A1 paddq xmm2,xmm0
001913A5 dec esi
001913A6 jne main+0F0h (0191390h)
对于数组:
0019142D lea ecx,[ecx]
for (int i = 0; i<size; i++)
00191430 lea ecx,[ecx+20h]
sum += my_array[i];
00191433 movdqu xmm0,xmmword ptr [ecx-30h]
00191438 paddq xmm1,xmm0
0019143C movdqu xmm0,xmmword ptr [ecx-20h]
00191441 paddq xmm2,xmm0
00191445 dec esi
00191446 jne main+190h (0191430h)
如您所见,内部循环是相同的。实际上,我怀疑这是一个硬件问题,我交换了两个循环,并且数组的输出速度更快到相同的幅度(所以实际上,在现实世界中,两者都没有比另一个更快或更慢)。
我预测这是某种 CPU 缓存行为: https://en.wikipedia.org/wiki/CPU_cache
【讨论】:
缓存将充满第一个数据结构的内容 - 所以当第二个执行时它会遇到缓存未命中的负载 第一个数据结构也会经历缺失(来自其他用户空间程序或其他),所以这不是直接的结果。确认后,我会立即跟进一些更多信息。 我认为这可能与my_vec[i] = i;
之类的行以及它们对缓存的排序影响有关,但我无法确认。我也无法确认这是 CPU 节点/缓存模式(或类似)问题,因为两个页面都是读/写的(我在程序集中确认了)。
我刚刚尝试了向量、数组、向量,我得到了:9028、13222、11502(当我重复时,我得到了类似的结果,第一个总是比第二个快得多 2)【参考方案2】:
我们有两个数组,每个数组 80,000 字节。首先,160,000 字节并行填充数据。然后读取其中的 80,000 个,然后读取其他 80,000 个。假设缓存为 128,000 字节:
在读取向量的前 32,000 个字节时,数据不会被缓存。接下来的 48,000 个字节被缓存。现在缓存包含所有向量和数组的最后 48,000 字节。但是数组中的字节是最旧的,所以当数组从头开始读取时,它末尾的数据会被丢弃。所以从数组中读取的所有内容都是未缓存的。
因此,对于向量,我们有 32,000 字节的未缓存读取和 48,000 字节的缓存读取,而数组有 80,000 字节的未缓存读取。
这是针对 128,000 字节的缓存大小。其他尺寸会有所不同。 但随后可能会发生完全不同的事情。您的代码可以在运行时切换到不同的处理器,此时一个处理器可能必须将数据写入主存储器,而另一个处理器读取它。在另一个方向上,操作系统可能刚刚意识到正在进行一些操作,并将处理器从省电模式切换到某种加速模式。
进行一次测量并从中得出结论,这些都没有考虑在内。
【讨论】:
但是向量在堆上,所以数组和向量不共享同一个页面。因此;它们之间没有缓存共享。像这样修改代码:unsigned long long *my_array = new unsigned long long[size];
NOT 会改变性能。
@kvanberendonck:gnasher729 没有声称有任何缓存共享,除了所有进程当然共享相同的完整处理器缓存这一事实。
我现在意识到我误解了,但我的解释是他现在原因缓存在进程开始时是“无丢失”/新鲜的。是这样吗?缓存不会从计算机上运行的所有其他内容中填充,因此在第一个和第二个循环中都会有相同的未命中率?
好吧,他的(隐含的)假设是在整个代码块的运行期间没有中断(因此中间的缓存不会受到任何干扰),并且他也忽略了中间的输出调用。但他不需要在一开始就假设一个干净的缓存,因为填充数组和向量的循环(在计时开始之前运行)会将任何已经被到达代码块时在那里。
令人费解的是,删除顶部填充数组的循环(将它们带入缓存)对计时没有影响。还尝试在两个循环之间添加std::this_thread::yield();
以排除操作系统因素的成本,但这似乎也没有任何区别。以上是关于为啥 std::vector 的速度是原始数组的两倍?包含完整代码的主要内容,如果未能解决你的问题,请参考以下文章
将 std::vector<int> 从原始内存转换为数组[重复]
为啥使用 std::vector 而不是 realloc? [关闭]