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

use*_*112 5 c++ arrays performance stl vector

更新:似乎这是MSVC的一个错误,但它也发生在ICC版本14,最高优化(/ o3)打开.

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

  • std :: array 159,000
  • 原始阵列117,000
  • 矢量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
  • 原始阵列~35,600
  • 矢量~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();
}
Run Code Online (Sandbox Code Playgroud)

Ins*_*oop 5

这是我的计算机上的结果,使用 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
Run Code Online (Sandbox Code Playgroud)

如您所见,gcc 4.9 没有区别。对于英特尔编译器, std::vector 比其他编译器慢。如果你检查汇编代码,你会发现循环

for(int i=0; i<noElements; i++){
    vec[i] = i;
}
Run Code Online (Sandbox Code Playgroud)

为 C 数组、std::array 向量化,但不为 std::vector 向量化。如果你问英特尔编译器为什么,你只会得到

main-array.cpp(23): (col. 9) remark: loop was not vectorized: existence of vector dependence
Run Code Online (Sandbox Code Playgroud)

当然,这个循环没有依赖,只是编译器搞不清楚。向量化对于标准库来说是一种痛苦,其中所有这些容器都需要访问元素的方法,而这些方法隐藏了一些指针运算。它使优化成为编写优化编译器的人的噩梦。因此,您将在此处看到的内容在很大程度上取决于您使用的编译器。最有可能的是,您会发现版本之间的变化。

您对“完美”编译器的期望几乎与 gcc 相同。您不应该发现 C 数组和 std::array 之间有任何区别(它们都在堆栈上分配),并且在处理大数组时,您不应该看到 C 数组和 std::vector 之间有任何区别分配时间不是 CPU 花费时间的地方。另一方面,如果您比较小数组(例如一些大小为 3 的 std::array)的大量分配和释放,则 std::array 或 C-array 会将 std::vector 吹出水,因为分配时间(在 std::vector 的堆上)将很重要。

所以要学习的教训是:

  • 在 C++11 中,忘记 C 数组 (*)

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

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

  • Fortran 之所以能成功,是因为它没有所有这些问题。它有一种类型的数组,可以在堆栈、堆上分配,它可以执行(或不)边界检查,并且向量化器可以工作,因为它们不必处理疯狂的指针。

现在,回到您的问题:为什么编译器将 XMM 寄存器用于原始/标准数组而不是向量?

正如您在 gcc 中看到的那样,情况并非总是如此。原因是为 C++ 编写优化编译器是一个巨大的痛苦,许多“基本”优化在许多编译器中仍未完成。

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