为什么编译器对原始/std数组使用XMM寄存器,而对向量不使用?
Why do compilers use XMM registers for raw/std arrays but not vectors?
更新:似乎这是MSVC的错误,但它也发生在ICC版本14中,最高优化(/o3)打开。
UPDATE2:使用ICC关闭优化后,我得到:
- 159000 <<li> std::数组/gh>
- raw array 117,000 162313 <<li>向量/gh>
我正在使用下面的代码比较std::array与raw数组与std::vector的性能。我已经测试了使用MSVC 2012编译器和英特尔编译器vs 14,在Win 7 64上,64位编译。CPU是Intel第三代。
结果是(一致地):
- std::数组~ 35600
- raw array ~35,600 向量~ 40000
当我检查汇编时,编译器为std::array
和原始数组选择XMM寄存器,因此可能会发生某种SIMD处理?然而,对于std::vector
,使用常规的r8-r15寄存器。
假设我上面是正确的,为什么XMM寄存器不用于std::vector?
下面是完整的测试代码(您需要增加默认堆栈保留大小):#include <iostream>
#include <vector>
#include <array>
const unsigned int noElements = 10000000;
const unsigned int noIterations = 500;
void testVector(){
volatile unsigned long long sum = 0;
unsigned long long start = 0;
unsigned long long finish = 0;
unsigned int x;
unsigned int y;
std::vector<unsigned int> vec;
vec.resize(noElements);
start = __rdtscp(&x);
for(int i=0; i<noIterations; i++){
for(int i=0; i<noElements; i++){
vec[i] = i;
}
for(int i=0; i<noElements; i++){
sum += (3 * vec[i]);
}
}
finish = __rdtscp(&y);
std::cout << "std::vector:t" << (finish - start)/1000000 << std::endl;
}
void testRawArray(){
volatile unsigned long long sum = 0;
unsigned long long start = 0;
unsigned long long finish = 0;
unsigned int x;
unsigned int y;
unsigned int myRawArray[noElements];
start = __rdtscp(&x);
for(int i=0; i<noIterations; i++){
for(int i=0; i<noElements; i++){
myRawArray[i] = i;
}
for(int i=0; i<noElements; i++){
sum += (3 * myRawArray[i]);
}
}
finish = __rdtscp(&y);
std::cout << "raw array: t" << (finish - start)/1000000 << std::endl;
}
void testStdArray(){
volatile unsigned long long sum = 0;
unsigned long long start = 0;
unsigned long long finish = 0;
unsigned int x;
unsigned int y;
std::array<unsigned int, noElements> myStdArray;
start = __rdtscp(&x);
for(int i=0; i<noIterations; i++){
for(int i=0; i<noElements; i++){
myStdArray[i] = i;
}
for(int i=0; i<noElements; i++){
sum += (3 * myStdArray[i]);
}
}
finish = __rdtscp(&y);
std::cout << "std::array: t" << (finish - start)/1000000 << std::endl;
}
int main(){
testStdArray();
testRawArray();
testVector();
}
以下是在我的计算机上使用gcc 4.9和Intel c++编译器14的结果。我已将代码更改为noElements = 1000000和noIterations = 1000。此外,我还使用了std::chrono::steady_clock
来为循环计时。
fayard@speed:Desktop$ uname -a
Darwin speed.home 13.2.0 Darwin Kernel Version 13.2.0: Thu Apr 17 23:03:13 PDT 2014; root:xnu-2422.100.13~1/RELEASE_X86_64 x86_64
fayard@speed:Desktop$ g++-4.9 --version
g++-4.9 (Homebrew gcc49 4.9.0 --enable-all-languages) 4.9.0
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
fayard@speed:Desktop$ g++-4.9 -std=c++11 -Ofast main-array.cpp -o main
fayard@speed:Desktop$ ./main
std::array: 1891738
raw array: 1889974
std::vector: 1891721
fayard@speed:Desktop$ icpc --version
icpc (ICC) 14.0.3 20140415
Copyright (C) 1985-2014 Intel Corporation. All rights reserved.
fayard@speed:Desktop$ icpc -std=c++11 -Ofast main-array.cpp -o main
fayard@speed:Desktop$ ./main
std::array: 1896141
raw array: 1886859
std::vector: 2135880
可以看到,在gcc 4.9中没有区别。对于Intel编译器,std::vector比其他的要慢。如果检查汇编代码,您会发现循环
for(int i=0; i<noElements; i++){
vec[i] = i;
}
对c数组std::数组进行了向量化,但对std::vector不进行向量化。如果你问英特尔编译器为什么,你只会得到
main-array.cpp(23): (col. 9) remark: loop was not vectorized: existence of vector dependence
当然,在这个循环中没有依赖关系,但是编译器无法发现它。向量化对于标准库来说是一个麻烦,因为所有这些容器都需要方法来访问元素,而这些方法隐藏了一些指针运算。它使优化成为编写优化编译器的人的噩梦。因此,您在这里看到的内容在很大程度上取决于您使用的编译器。最有可能的是,你会发现版本之间的变化。
你应该期望从一个"完美"的编译器,几乎是相同的时间,作为gcc。您不应该发现c数组和std::array之间有任何区别(它们都是在堆栈上分配的),并且当您处理大型数组时,您不应该看到c数组和std::vector之间有任何区别,因为分配时间不是CPU花费时间的地方。另一方面,如果比较大量的小数组(例如一些大小为3的std::数组)的分配和回收,std::数组或c -数组将使std::vector相形见绌,因为std::vector的分配时间(在堆上为std::vector分配的时间)将非常重要。
所以我们要吸取的教训是:
在c++ 11中,忘记C-arrays (*)
对于小数组使用std::array(其大小在编译时已知),对于大数组使用std::vector
如果你需要一个巨大的堆栈,很可能你正在做一些愚蠢的事情
Fortran很好,因为它没有所有这些问题。它有一种类型的数组可以在堆栈上分配,即堆,它可以做(或不)绑定检查,并且矢量化器可以工作,因为它们不必处理疯狂的指针。
并不总是这样,你可以在gcc中看到。原因是为c++编写一个优化编译器是一件非常痛苦的事情,而且很多"基本"的优化仍然没有在许多编译器中完成。
(*):在某些特殊情况下它们仍然是有用的。
- 本质:使用__128寄存器
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- 使用英特尔 PIN 修改寄存器
- AVX 指令中寄存器和指针之间的客观差异
- 如何确定我的处理器有多少个 AVX 寄存器?
- 除非使用某些寄存器,否则函数挂钩会崩溃
- 寄存器上的管道计算
- 其中关于内存和寄存器的左值和右值
- 有没有办法强制C++编译器将变量存储在寄存器中?
- "变量":函数中函数作用域不允许初始化的自动或寄存器变量'naked'
- Atmel Studio:返回一个包含数组的寄存器
- 使用 googletest 测试嵌入式C++代码时处理外设寄存器的重复符号
- 有没有一种优雅的方法可以使用向量修改器并获得新的向量,而不是更改原始向量
- 移位寄存器74HC595输出电流
- 超过255的Modbus寄存器无法访问SimpleModbus
- 如何在程序集函数中将元素数组作为参数传递时转发 ARM 寄存器的地址指针
- xmm 寄存器中的__m128何时?
- 将向量装入SSE寄存器
- 为什么编译器对原始/std数组使用XMM寄存器,而对向量不使用?