为什么编译器对原始/std数组使用XMM寄存器,而对向量不使用?

Why do compilers use XMM registers for raw/std arrays but not vectors?

本文关键字:向量 寄存器 XMM std 数组 为什么 编译器 原始      更新时间:2023-10-16

更新:似乎这是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很好,因为它没有所有这些问题。它有一种类型的数组可以在堆栈上分配,即堆,它可以做(或不)绑定检查,并且矢量化器可以工作,因为它们不必处理疯狂的指针。

现在,回到你的问题:为什么编译器对原始/std数组使用XMM寄存器,而不是向量?

并不总是这样,你可以在gcc中看到。原因是为c++编写一个优化编译器是一件非常痛苦的事情,而且很多"基本"的优化仍然没有在许多编译器中完成。

(*):在某些特殊情况下它们仍然是有用的。