gpt4 book ai didi

c++ - 结合__restrict__和__attribute __((aligned(32)))

转载 作者:行者123 更新时间:2023-11-28 01:25:11 25 4
gpt4 key购买 nike

我想确保gcc知道:

  • 指针引用的内存不重叠块
  • 指针具有32个字节的对齐方式

  • 以下是正确的吗?
    template<typename T, typename T2>
    void f(const T* __restrict__ __attribute__((aligned(32))) x,
    T2* __restrict__ __attribute__((aligned(32))) out) {}

    谢谢。

    更新:

    我尝试使用一次读取和大量写入操作来使cpu端口饱和以进行写入。我希望这将使通过一致的举动带来的性能提升更加显着。

    但是装配体仍然使用未对齐的移动,而不是对齐的移动。

    代码(也位于 godbolt.org)
    int square(const  float* __restrict__ __attribute__((aligned(32))) x,
    const int size,
    float* __restrict__ __attribute__((aligned(32))) out0,
    float* __restrict__ __attribute__((aligned(32))) out1,
    float* __restrict__ __attribute__((aligned(32))) out2,
    float* __restrict__ __attribute__((aligned(32))) out3,
    float* __restrict__ __attribute__((aligned(32))) out4) {
    for (int i = 0; i < size; ++i) {
    out0[i] = x[i];
    out1[i] = x[i] * x[i];
    out2[i] = x[i] * x[i] * x[i];
    out3[i] = x[i] * x[i] * x[i] * x[i];
    out4[i] = x[i] * x[i] * x[i] * x[i] * x[i];
    }
    }

    用gcc 8.2和“-march = haswell -O3”编译的程序集
    它充满了vmovups,它们是未对齐的 Action 。
    .L3:
    vmovups ymm1, YMMWORD PTR [rbx+rax]
    vmulps ymm0, ymm1, ymm1
    vmovups YMMWORD PTR [r14+rax], ymm0
    vmulps ymm0, ymm1, ymm0
    vmovups YMMWORD PTR [r15+rax], ymm0
    vmulps ymm0, ymm1, ymm0
    vmovups YMMWORD PTR [r12+rax], ymm0
    vmulps ymm0, ymm1, ymm0
    vmovups YMMWORD PTR [rbp+0+rax], ymm0
    add rax, 32
    cmp rax, rdx
    jne .L3
    and r13d, -8
    vzeroupper

    即使对于Sandybridge,行为也相同:
    .L3:
    vmovups xmm2, XMMWORD PTR [rbx+rax]
    vinsertf128 ymm1, ymm2, XMMWORD PTR [rbx+16+rax], 0x1
    vmulps ymm0, ymm1, ymm1
    vmovups XMMWORD PTR [r14+rax], xmm0
    vextractf128 XMMWORD PTR [r14+16+rax], ymm0, 0x1
    vmulps ymm0, ymm1, ymm0
    vmovups XMMWORD PTR [r13+0+rax], xmm0
    vextractf128 XMMWORD PTR [r13+16+rax], ymm0, 0x1
    vmulps ymm0, ymm1, ymm0
    vmovups XMMWORD PTR [r12+rax], xmm0
    vextractf128 XMMWORD PTR [r12+16+rax], ymm0, 0x1
    vmulps ymm0, ymm1, ymm0
    vmovups XMMWORD PTR [rbp+0+rax], xmm0
    vextractf128 XMMWORD PTR [rbp+16+rax], ymm0, 0x1
    add rax, 32
    cmp rax, rdx
    jne .L3
    and r15d, -8
    vzeroupper

    使用加法而不是乘法( godbolt)。
    仍未结盟的举动。

    最佳答案

    不,使用float *__attribute__((aligned(32))) x意味着指针本身存储在对齐的内存中,而不是指向对齐的内存。1

    有一种方法可以执行此操作,但是它仅对gcc有用,对clang或ICC无效。

    有关适用于所有GNU C兼容编译器的__builtin_assume_aligned,请参见How to tell GCC that a pointer argument is always double-word-aligned?;有关适用于GCC的__attribute__((aligned(32)))的更多详细信息,请参见How can I apply __attribute__(( aligned(32))) to an int *?

    我使用__restrict而不是__restrict__ ,因为C99 restrict的C++扩展名可移植到所有主流x86 C++编译器(包括MSVC)中。

    typedef float aligned32_float __attribute__((aligned(32)));

    void prod(const aligned32_float * __restrict x,
    const aligned32_float * __restrict y,
    int size,
    aligned32_float* __restrict out0)
    {
    size &= -16ULL;

    #if 0 // this works for clang, ICC, and GCC
    x = (const float*)__builtin_assume_aligned(x, 32); // have to cast the result in C++
    y = (const float*)__builtin_assume_aligned(y, 32);
    out0 = (float*)__builtin_assume_aligned(out0, 32);
    #endif

    for (int i = 0; i < size; ++i) {
    out0[i] = x[i] * y[i]; // auto-vectorized with a memory operand for mulps
    // note clang using two separate movups loads
    // instead of a memory operand for mulps
    }
    }

    ( gcc, clang, and ICC output on the Godbolt compiler explorer)。

    只要有编译时对齐保证,GCC和clang将使用 movaps / vmovaps代替 ups。 (与MSVC和ICC不同,它们从不使用 movaps进行加载/存储,对于在Core2 / K10或更旧版本上运行的任何内容都缺少优化)。正如您所注意到的,它是将 -mavx256-split-unaligned-load / store效果应用于除Haswell( Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd?)。之外的其他调音,这是您的语法无效的另一个线索。

    在对齐的内存上使用 vmovups时不会出现性能问题;当地址在运行时对齐时,它在所有支持AVX的CPU上的性能与 vmovaps相同。因此,在实践中,您的 -march=haswell输出没有真正的问题。只有Nehalem和Bulldozer之前的较旧的CPU始终将 movups解码为多个uops。

    告诉编译器对齐保证的真正好处(这些天)是,编译器有时会为启动/清理循环发出额外的代码,以达到对齐边界。或没有AVX,除非对齐,否则编译器无法将加载折叠到 mulps的内存操作数中。

    一个很好的测试用例是out0[i] = x[i] * y[i],其中加载结果仅需要一次。 out0[i] *= x[i]。知道对齐方式后,就可以启用 movaps / mulps xmm0, [rsi],否则为2x movups + mulps。您甚至可以在ICC或MSVC之类的编译器上检查此优化,即使它们知道有对齐保证也使用 movups,但是当他们可以将负载折叠到ALU操作中时,仍会生成需要对齐的代码。

    似乎__builtin_assume_aligned是执行的唯一真正可移植的方式(对于GNU C编译器)。您可以像将指针传递给 struct aligned_floats { alignas(32) float f[8]; };一样进行破解,但这使用起来很麻烦,并且除非您实际上通过该类型的对象访问内存,否则编译器将无法假定对齐。 (例如,将指向该指针的指针转换回 float *

    I try to use one read and lots of write to saturate the cpu ports for writing.



    使用4个以上的输出流可能会导致高速缓存中出现更多冲突未命中的情况而受到伤害。例如,Skylake的L2缓存只有4路。但是L1d是8路的,因此对于小型缓冲区您可能还可以。

    如果要使存储端口uop吞吐量达到饱和,请使用较窄的存储(例如标量),而不要使用每个uop需要更多带宽的宽SIMD存储。在提交给L1d之前,可以将到同一缓存行的背对背存储合并到存储缓冲区中,因此这取决于您要测试的内容。

    半相关:在Intel Sandybridge系列CPU上,像 c[i] = a[i]+b[i]或STREAM triad这样的2倍负载+ 1倍存储内存访问模式将最接近 maxing out total L1d cache load+store bandwidth。在SnB / IvB上,每个加载/存储256位 vector 需要2个周期,因此在加载的第二个周期内,存储地址oups需要时间在端口2或3上使用AGU。在Haswell及更高版本(256位宽的加载/存储端口)上,存储需要使用非索引寻址模式,以便它们可以在端口7上使用简单寻址模式存储AGU。

    但是,AMD CPU的每个时钟最多可以执行2个内存操作,最多只能有一个存储,因此它们将通过复制和操作存储=加载模式最大化。

    顺便说一句,英特尔最近宣布了Sunny Cove(Ice Lake的前身),该时钟每时钟将有 2x load + 2x store throughput,第二个 vector 洗牌ALU和5个问题/更名。太有趣了!编译器需要将循环展开至少2步,以使每个时钟1个循环分支上不会出现瓶颈。

    脚注1 :这就是为什么(如果不使用AVX进行编译),则会收到警告,并且gcc会忽略 and rsp,-32,因为它假定RSP已对齐。 (它实际上并没有溢出任何YMM规则,因此无论如何它都应该对此进行了优化,但是gcc在使用局部对齐或自动矢量化创建的对象时已经存在此错过优化的错误一段时间了。)
    <source>:4:6: note: The ABI for passing parameters with 32-byte alignment has changed in GCC 4.6

    关于c++ - 结合__restrict__和__attribute __((aligned(32))),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54189780/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com