为啥 C++ 标准库中没有 SIMD 功能?

Posted

技术标签:

【中文标题】为啥 C++ 标准库中没有 SIMD 功能?【英文标题】:Why is there no SIMD functionality in the C++ standard library?为什么 C++ 标准库中没有 SIMD 功能? 【发布时间】:2019-12-17 12:03:39 【问题描述】:

SSE 自 1999 年以来一直存在,它及其后续扩展是提高 C++ 程序性能的最强大工具之一。然而,没有明确使用这一点的标准化容器/算法等(我知道?)。是否有一个原因?有没有没有通过的提案?

【问题讨论】:

你知道std::execution policies吗? <algorithm> 库中的大多数算法都可以使用它们。 不是多线程的 SSE 内在函数是特定于 x86 的,而 C++ 标准在很大程度上是跨平台可移植的。在大多数人的手中,跨平台 SIMD 库可能会比自动矢量化做得更差,而能够有效使用手动 SIMD 的人可能会将其传递给低级 intel 内在函数。 是的,我知道隐式矢量化,但这永远无法保证,并且在您的程序中某处更改 1 行代码可能会阻止编译器对任何内容进行矢量化 嘿,怎么样,看起来他们在烤东西:en.cppreference.com/w/cpp/experimental/simd 【参考方案1】:

(parallelism TS v2) 中对显式短向量 SIMD 类型提供实验性支持,这些类型映射到常见 ISA 的 SIMD 扩展,但截至 2021 年 8 月只有 GCC 实现它。上面链接的 Cppreference 文档不完整,但还有其他Working Draft, Technical Specification for C++ Extensions for Parallelism, Document N4808 中涵盖的详细信息。该提案背后的想法是在博士项目 (2015 thesis here) 期间开发的。 GCC 实现wrote an article 的作者关于将现有的 SSE 字符串处理算法转换为使用其库的 2019 年迭代,实现类似的性能和更高的可读性。下面是一些使用它的简单代码和生成的程序集:

乘加

#include <experimental/simd> // Fails on MSVC 19 and others
using vec4f = std::experimental::fixed_size_simd<float,4>;

void madd(vec4f& out, const vec4f& a, const vec4f& b)

    out += a * b;


使用-march=znver2 -Ofast -ffast-math 编译,我们确实得到了为此生成的硬件融合乘加:

madd(std::experimental::parallelism_v2::simd<float, std::experimental::parallelism_v2::simd_abi::_Fixed<4> >&, std::experimental::parallelism_v2::simd<float, std::experimental::parallelism_v2::simd_abi::_Fixed<4> > const&, std::experimental::parallelism_v2::simd<float, std::experimental::parallelism_v2::simd_abi::_Fixed<4> > const&):
        vmovaps xmm0, XMMWORD PTR [rdx]
        vmovaps xmm1, XMMWORD PTR [rdi]
        vfmadd132ps     xmm0, xmm1, XMMWORD PTR [rsi]
        vmovaps XMMWORD PTR [rdi], xmm0
        ret

点积

一个点/内积可以写得很简洁:

float dot_product(const vec4f a, const vec4f b)

    return reduce(a * b);

-Ofast -ffast-math -march=znver2:

dot_product(std::experimental::parallelism_v2::simd<float, std::experimental::parallelism_v2::simd_abi::_Fixed<4> >, std::experimental::parallelism_v2::simd<float, std::experimental::parallelism_v2::simd_abi::_Fixed<4> >):
        vmovaps xmm1, XMMWORD PTR [rsi]
        vmulps  xmm1, xmm1, XMMWORD PTR [rdi]
        vpermilps       xmm0, xmm1, 27
        vaddps  xmm0, xmm0, xmm1
        vpermilpd       xmm1, xmm0, 3
        vaddps  xmm0, xmm0, xmm1
        ret

(Godbolt link with some more playing around)。

【讨论】:

有趣。 gcc -O2 不包括自动矢量化,因此使用 SIMD 可以证明它是用 __attribute__((vector_size(16))) 定义的。但是,godbolt.org/z/79KddEdba 表明,即使您在 C 中按值传递,它 not 也不会像 x86-64 System V ABI 中的普通 __m128 非类 typedef 那样在 xmm 寄存器中传递, 甚至通过指针返回它 :( 幸运的是,在内联后这并不重要,但您通常不想通过引用传递 SIMD 向量。 @PeterCordes 代码包含注释“以下确保函数参数通过堆栈传递。这对于跨 TU 边界的 ABI 兼容性很重要”:-( 这里更多地提到了 ODR、TU、内联问题(“大多数函数都标记为 [[gnu::always_inline]] 以启用具有不同 -m 标志的 TU 的准 ODR 一致性链接。 "): gcc.1065356.n8.nabble.com/… "> > 请注意,过度使用 always_inline 会导致编译时问题 > >(参见 PR99785 示例)。 > > 啊,我应该验证这是否也是我的 stdx::simd 的原因 >实现编译起来很慢。” gcc.1065356.n8.nabble.com/…

以上是关于为啥 C++ 标准库中没有 SIMD 功能?的主要内容,如果未能解决你的问题,请参考以下文章

为啥C++标准库中没有transform_if?

为啥标准 C++ 库中没有“int pow(int base, int exponent)”?

为啥 Python 的标准库中没有排序容器?

十类C++标准库 十类C++标准库简介

如何在 C++ 标准库中更改堆中的最大元素?

标准库中没有 std::identity 有啥原因吗?