std :: array vs array performance

Arc*_*yno 53 c++ c++11 stdarray

如果我想构建一个非常简单的数组

int myArray[3] = {1,2,3};
Run Code Online (Sandbox Code Playgroud)

我应该用std::array吗?

std::array<int, 3> a = {{1, 2, 3}};
Run Code Online (Sandbox Code Playgroud)

使用std :: array比使用常规的有什么好处?性能更高吗?更容易处理复制/访问?

Mik*_*our 73

使用std::array超过常规的优点是什么?

它具有友好的值语义,因此可以通过值传递给函数或从函数返回.它的界面使得查找大小更加方便,并使用基于STL样式迭代器的算法.

性能更高吗?

它应该完全一样.根据定义,它是一个包含数组作为唯一成员的简单聚合.

更容易处理复制/访问?

是.


vso*_*tco 26

A std::array是一个非常薄的C型数组包装器,基本上定义为

template<typename T, size_t N>
class array
{
public:
    T _data[N];
    T& operator[](size_t);
    const T& operator[](size_t) const;
    // other member functions and typedefs
};
Run Code Online (Sandbox Code Playgroud)

它是一个聚合,它允许你几乎像一个基本类型使用它(即你可以传值,分配等,而标准的C数组不能直接分配或复制到另一个数组).您应该看一些标准实现(从您喜欢的IDE跳转到定义或直接打开<array>),它是C++标准库的一部分,非常容易阅读和理解.


Bau*_*gen 18

std::array 被设计为C数组的零开销包装器,为其提供"正常"值,如其他C++容器的语义.

在您仍然享受额外功能的同时,您不应该注意到运行时性能的任何差异.

如果你有C++ 11或者手头的提升,使用std::array而不是int[]样式数组是一个好主意.


小智 11

它的性能更高吗?

它应该完全一样。根据定义,它是一个包含数组作为其唯一成员的简单聚合。

情况似乎更复杂,因为std::array与 C 数组相比,根据特定平台并不总是生成相同的汇编代码。

我在Godbolt上测试了这种特定情况:

#include <array>
void test(double* const C, const double* const A,
          const double* const B, const size_t size) {
  for (size_t i = 0; i < size; i++) {
    //double arr[2] = {0.e0};//
    std::array<double, 2> arr = {0.e0};//different to double arr[2] for some compiler
    for (size_t j = 0; j < size; j++) {
      arr[0] += A[i] * B[j];
      arr[1] += A[j] * B[i];
    }
    C[i] += arr[0];
    C[i] += arr[1];
  }
}
Run Code Online (Sandbox Code Playgroud)

GCCClang为 C 数组版本和std::array版本生成相同的汇编代码。

但是,MSVCICPC为每个数组版本生成不同的汇编代码。(我用-Ofast-Os; MSVC-Ox和测试了ICPC19 -Os

我不知道为什么会这样(我确实希望 std::array 和 c-array 的行为完全相同)。也许采用了不同的优化策略。

补充一点:ICPC 中似乎存在一个错误

#pragma simd 
Run Code Online (Sandbox Code Playgroud)

在某些情况下使用 c-array 时进行矢量化(c-array 代码产生错误的输出;std::array版本工作正常)。

不幸的是,我还没有一个最小的工作示例,因为我在优化一段相当复杂的代码时发现了这个问题。

当我确定我没有误解 C-array/std::array#pragma simd.

  • 真正测试过它的人,接近赞成答案的底部。 (7认同)
  • 可以被视为编译器错误吗? (2认同)

Jan*_*tke 6

std::array解决了 C 风格数组所存在的许多问题。这在很大程度上与性能无关;而是与性能有关。这是关于正确性和便利性。以下是可解决的 C 样式数组问题的列表std::array

1. 函数不能返回数组

这一点特别烦人。特别是对于初始化查找表之类的任务,返回数组非常有用:

constexpr auto lookup = [] {
    std::array<int, 128> result;
    // compute the contents
    return result;
};
Run Code Online (Sandbox Code Playgroud)

2.返回数组的假设语法被诅咒了

int get_array()[10];
auto get_array() -> int[10]; // workaround: trailing return types
Run Code Online (Sandbox Code Playgroud)

根据 C 声明语法,这就是get_array返回 10 个int数组的函数的声明方式。这种语法非常令人惊讶,也是它不太可能在 C 中标准化的原因之一。

3.数组不能赋值

int arr[] = {0, 1, 2, 3};
arr = something_else; // ill-formed
Run Code Online (Sandbox Code Playgroud)

对于小型数组(例如对或三元组)来说,分配数组相当常见。这感觉特别不一致,因为语法=可用于初始化。

4.数组不能初始化为其他数组

int data[] = {0, 1};
int copy[] = data;
Run Code Online (Sandbox Code Playgroud)

这是又一个让人感觉武断的限制。更糟糕的是,复制初始化原则上对数组有效,只是不使用另一个数组。

5. 数组与 的比较==是一个常见的错误

int arr[] = {0, 1, 2, 3};
arr == arr; // true, but doesn't compare contents, but is pointer comparison
Run Code Online (Sandbox Code Playgroud)

这是一个常见的错误,这就是为什么数组比较在 C++23 中已被弃用,并且可能会在 C++26 中被删除。

另请参见为什么数组之间的 == 相等比较不起作用?

6. 数组衰减为指针,赋予它们令人惊讶的语义

char arr[] = "hello ";
if (arr); // always true, even if array contains an empty string
+arr;     // OK, but what does it mean to apply unary plus to an array?!
arr + 1;  // OK, but result is not "hello 1", it is "ello "
Run Code Online (Sandbox Code Playgroud)

这样的例子不胜枚举。数组退化为指针这一事实常常会导致违反直觉的行为。大多数这种行为尚未被弃用。

7. 数组可能是可变长度数组(VLA)

int size = rand();
int vla[size]; // could be OK if the compiler supports VLAs as an extension
std::array<int, size> arr; // error, as expected
Run Code Online (Sandbox Code Playgroud)

如果没有编译器警告(-Wvla对于 GCC/clang),我们可能会无意中创建 VLA。这使得代码不可移植,因为并非每个编译器都支持 VLA。

8.函数参数中的数组类型调整具有误导性

void foo(int arr[4]) {            // equivalent to accepting a parameter of type int*
    sizeof(arr) / sizeof(arr[0]); // = sizeof(void*) / sizeof(int), most likely 2
}

void foo(std::array<int, 4> arr) {
    arr.size(); // 4, correct
}
Run Code Online (Sandbox Code Playgroud)

函数参数中数组类型的参数调整为指针类型。这意味着它sizeof(arr)无法正常工作,并且提供的大小[4]实际上没有意义。

这是非常令人惊讶的,sizeof当需要数组时与指针结合使用是 C 语言初学者最容易犯的错误之一。

9. 无法从函数参数推导出数组大小

template <std::size_t N>
void foo(int arr[N]); // N can't be deduced from the array parameter

template <std::size_t N>
void foo(int (&arr)[N]); // workaround with complicated syntax
Run Code Online (Sandbox Code Playgroud)

此问题是前一点中对指针进行类型调整的结果。参数中没有N可以推断出的数组类型。解决方法是必要的,但是这个解决方法并不漂亮。

10. 数组导致通用代码中的特殊情况

由于上述所有不规则之处,数组需要通用代码中的特殊情况。举一些例子:

  • std::swap数组必须有重载,因为数组是不可移动的
  • 数组不是类,因此需要许多自由函数,例如 、 、 等,它们具有数组的std::size特殊std::begin情况std::empty

任何编写包含range.begin()而不是包含的库的开发人员std::begin(range)都必须担心如果用户使用 C 样式数组,他们的代码会被破坏。当然,使用std::array并不能解决根本问题,但这意味着您永远不会遭受库开发人员做出.begin()始终有效的错误假设的困扰。

而且,这些无数的特殊情况仍然不能涵盖所有情况。您可以用于std::swap对两个数组进行按元素交换,但不能用于按std::exchange元素交换数组(因为不能从函数返回数组)。

11. 数组可能因别名而性能不佳

很容易无意中编写因别名而导致性能损失的代码。考虑以下函数,这些函数旨在将 32 位无符号整数数组序列化为具有小尾数字节顺序的字节数组。

std::array<std::byte, 4> serialize_le(unsigned x) {
    return {
        std::byte(x >>  0),
        std::byte(x >>  8),
        std::byte(x >> 16),
        std::byte(x >> 24)
    };
}

void write_le_n(std::byte* mem, std::array<unsigned, 1024>& numbers) {
    for (unsigned n : numbers) {
        auto bytes = serialize_le(n);
        std::memcpy(mem, &bytes[0], sizeof(bytes));
        mem += 4;
    }
}
Run Code Online (Sandbox Code Playgroud)
void write_le(std::byte mem[], unsigned x) {
    mem[0] = std::byte(x >>  0);
    mem[1] = std::byte(x >>  8);
    mem[2] = std::byte(x >> 16);
    mem[3] = std::byte(x >> 24);
}

void write_le_n(std::byte* mem, unsigned x[1024]) {
    for (unsigned i = 0; i < 1024; ++i) {
        write_le(mem + i * 4, x[i]);
    }
}
Run Code Online (Sandbox Code Playgroud)

请参阅编译器资源管理器中的实时示例

直观上,这两个代码示例似乎在做几乎相同的事情。然而,第二个示例的代码生成要差得多。绝对没有自动矢量化;这是一个极其幼稚的循环。

std::array通常可以向编译器表明没有发生别名,或者如果内存区域之间存在重叠,则它不能是部分的。这是对优化的推动。

结论

std::array解决了无数 C 风格数组的问题。在大多数情况下,这与性能无关,尽管std::array在特定情况下可能会更好。这是关于 C 风格数组的语法混乱、常见陷阱、任意限制和其他问题。

也可以看看


b4h*_*and 5

std::array具有值语义,而原始数组则没有。这意味着您可以将std::array其复制并像原始值一样对待。您可以按值或作为函数参数引用来接收它们,也可以按值返回它们。

如果您从不复制std::array,则性能与原始数组没有任何区别。如果确实需要进行复制,那么std::array将做正确的事情,并且仍应提供相同的性能。