为什么编译器将XMM寄存器用于raw / std数组但不使用向量?

时间:2014-06-01 17:46:27

标签: c++ arrays performance vector stl

更新:似乎这是MSVC的一个错误,但它也发生在ICC版本14上,并且启用了最高优化(/ o3)。

UPDATE2:随着ICC的优化关闭,我得到了:

  • std :: array 159,000
  • raw array 117,000
  • vector 162,313

我使用下面的代码比较std :: array vs raw array vs std :: vector的性能。我已经在Win 7 64上使用MSVC 2012编译器和英特尔编译器vs 14进行了测试,并进行了64位编译。 CPU是Intel第3代。

结果是(一致地):

  • std :: array~35,600
  • raw array~35,600
  • vector~40,000

当我检查程序集时,编译器为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();
}

1 个答案:

答案 0 :(得分:4)

以下是我的计算机上的结果,使用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没有区别。对于英特尔编译器,std :: vector比其他的慢。如果检查汇编代码,就会发现循环

for(int i=0; i<noElements; i++){
    vec[i] = i;
}

为C数组,std :: array而不是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 :: array),std :: array或C-array会将std :: vector吹出water,因为分配时间(在std :: vector的堆上)很重要。

所以要吸取的教训是:

  • 在C ++ 11中,忘记了C-arrays(*)

  • 将std :: array用于小数组(其大小在编译时已知)和std :: vector用于大数组

  • 如果你需要一个巨大的堆栈,很可能你做的事情很愚蠢

  • Fortran岩石,因为它没有所有这些问题。它有一种类型的数组thtat可以在堆栈上分配,堆可以执行(或不执行)绑定检查以及哪些矢量化器可以工作,因为它们不必处理疯狂的指针。

现在,回到你的问题:为什么编译器将XMM寄存器用于raw / std数组但不使用向量?

通过gcc可以看到并非总是如此。原因是为C ++编写优化编译器是一个巨大的痛苦,许多编译器仍然没有做很多“基本”优化。

(*):仍然存在有用的极端情况。