我们是不是需要 C++ 中的向量化或 for 循环已经足够快?

Posted

技术标签:

【中文标题】我们是不是需要 C++ 中的向量化或 for 循环已经足够快?【英文标题】:Do we need vectorization in C++ or are for loops already fast enough?我们是否需要 C++ 中的向量化或 for 循环已经足够快? 【发布时间】:2021-05-11 23:36:28 【问题描述】:

在 Matlab 中,我们使用vectorization 来加速代码。例如,这里有两种执行相同计算的方法:

% Loop
tic
i = 0;
for t = 0:.01:1e5
    i = i + 1;
    y(i) = sin(t);
end
toc

% Vectorization
tic
t = 0:.01:1e5;
y = sin(t);
toc

结果是:

Elapsed time is 1.278207 seconds. % For loop
Elapsed time is 0.099234 seconds. % Vectorization

因此矢量化代码的速度几乎快了 13 倍。实际上,如果我们再次运行它,我们会得到:

Elapsed time is 0.200800 seconds. % For loop
Elapsed time is 0.103183 seconds. % Vectorization

矢量化代码现在只有 2 倍而不是 13 倍。所以看起来我们在第一次运行代码时获得了巨大的加速,但在以后的运行中,加速并没有那么大,因为 Matlab 似乎知道 for 循环没有改变并且正在对其进行优化。在任何情况下,矢量化代码的速度仍然是 for 循环代码的两倍。

现在我已经开始使用 C++,我想知道这种语言的向量化。我们是否需要在 C++ 中对循环进行矢量化,或者它们已经足够快了?也许编译器会自动对它们进行矢量化?实际上,我不知道 Matlab 类型向量化是否甚至是 C++ 中的一个概念,也许它只是 Matlab 需要的,因为这是一种解释语言?您将如何用 C++ 编写上述函数以使其尽可能高效?

【问题讨论】:

您可能希望使用std::valarray检查和测量简单循环与实现的结果。 根据硬件和用例矢量化为您提供 1 倍到 16 倍的加速。这不是微不足道的。但是,如果您不能回退到矢量化库,则必须在实施中投入大量精力。因此,在 C++ 中是否需要矢量化的答案是“它取决于...”。 根据 Mathworks 的说法,matlab 解释器本身的大部分核心代码都是用 C 编写的,而大多数进行并行化的代码都是用 C++ 编写的。 (并且现代版本的 Matlab 中的用户界面是用 Java 编写的)。因此,从这个意义上说,Matlab 在矩阵上的性能展示了使用 C++ 可以实现的目标——只要开发人员付出足够的努力。在 Matlab 中,手写循环往往较慢,因为更多的工作是在解释器中完成的,而不是在 C++(或其他语言)中为矩​​阵性能而手工制作的代码中。 @πάνταῥεῖ 见Why is valarray so slow?。没人关心优化std::valarray @phuclv 没用过,只记得功能的“对等”。 【参考方案1】:

我们是否需要在 C++ 中进行向量化

不一定总是需要矢量化,但它可以使某些程序更快。

C++ 编译器支持自动矢量化,但如果您需要矢量化,那么您可能无法依赖这种优化,因为并非每个循环都可以自动矢量化。

[循环]已经足够快了吗?

取决于循环、目标 CPU、编译器及其选项,关键是:需要多快。


在标准 C++ 中你可以做一些事情来潜在地实现矢量化:

启用执行自动矢量化的编译器优化。 (请参阅编译器手册) 指定一个在其指令集中具有向量操作的目标 CPU。 (请参阅编译器手册) 通过std::parallel_unsequenced_policystd::unsequenced_policy 使用标准算法。 确保正在操作的数据与 SIMD 指令充分对齐。您可以使用alignas。请参阅目标 CPU 的手册了解您需要的对齐方式。 通过使用链接时间优化,确保优化器尽可能多地了解。 部分展开循环。这样做的限制是您对并行化的数量进行硬编码:
for (int i = 0; i < count; i += 4) 
    operation(i + 0);
    operation(i + 1);
    operation(i + 2);
    operation(i + 3);

在标准的、可移植的 C++ 之外,还有一些特定的实现方式:

一些compilers 提供语言扩展来编写显式矢量化程序。这可以跨不同的 CPU 移植,但不能移植到未实现该扩展的编译器。
using v4si = int __attribute__ ((vector_size (16)));
v4si a, b, c;
a = b + 1;    /* a = b + 1,1,1,1; */
a = 2 * b;    /* a = 2,2,2,2 * b; */
一些compilers 提供“内置”函数来调用可用于调用SIMD 矢量指令的特定CPU 指令。使用这些不能跨不兼容的 CPU 移植。 一些compilers 支持OpenMP API,其中有#pragma omp simd

【讨论】:

这种循环展开建议在 2021 年仍然有效吗?编译器在这方面做得非常好。他们知道每次迭代中有多少代码,并且更善于选择合适的代码展开。 @MSalters 编译器只能优化他们所知道的。如果count是在运行时确定的,那么编译器怎么知道它可以被4整除呢?也许它知道,也许它不知道。如果不是,那么展开是让编译器知道它是可整除的方法。

以上是关于我们是不是需要 C++ 中的向量化或 for 循环已经足够快?的主要内容,如果未能解决你的问题,请参考以下文章

SPARK的计算向量化-已有的向量化项目

模乘的向量化

C++ 性能,for 与 while

吴恩达机器学习_43矢量

MATLAB中结构多级索引的向量化

向量化