为什么 gcc -march=znver1 限制 uint64_t 矢量化?

Why does gcc -march=znver1 restrict uint64_t vectorization?

我试图确保 gcc 向量化我的循环。事实证明,通过使用 -march=znver1(或 -march=native),gcc 会跳过一些循环,即使它们可以被矢量化。为什么会这样?

在此代码中,将每个元素乘以标量的第二个循环未矢量化:

#include <stdio.h>
#include <inttypes.h>

int main() {
    const size_t N = 1000;
    uint64_t arr[N];
    for (size_t i = 0; i < N; ++i)
        arr[i] = 1;

    for (size_t i = 0; i < N; ++i)
        arr[i] *= 5;

    for (size_t i = 0; i < N; ++i)
        printf("%lu\n", arr[i]); // use the array so that it is not optimized away
}

gcc -O3 -fopt-info-vec-all -mavx2 main.c:

main.cpp:13:26: missed: couldn't vectorize loop
main.cpp:14:15: missed: statement clobbers memory: printf ("%lu\n", _3);
main.cpp:10:26: optimized: loop vectorized using 32 byte vectors
main.cpp:7:26: optimized: loop vectorized using 32 byte vectors
main.cpp:4:5: note: vectorized 2 loops in function.
main.cpp:14:15: missed: statement clobbers memory: printf ("%lu\n", _3);
main.cpp:15:1: note: ***** Analysis failed with vector mode V4DI
main.cpp:15:1: note: ***** Skipping vector mode V32QI, which would repeat the analysis for V4DI

gcc -O3 -fopt-info-vec-all -march=znver1 main.c:

main.cpp:13:26: missed: couldn't vectorize loop
main.cpp:14:15: missed: statement clobbers memory: printf ("%lu\n", _3);
main.cpp:10:26: missed: couldn't vectorize loop
main.cpp:10:26: missed: not vectorized: unsupported data-type
main.cpp:7:26: optimized: loop vectorized using 16 byte vectors
main.cpp:4:5: note: vectorized 1 loops in function.
main.cpp:14:15: missed: statement clobbers memory: printf ("%lu\n", _3);
main.cpp:15:1: note: ***** Analysis failed with vector mode V2DI
main.cpp:15:1: note: ***** Skipping vector mode V16QI, which would repeat the analysis for V2DI

-march=znver1 包括 -mavx2,所以我认为 gcc 出于某种原因选择不对其进行向量化:

~ $ gcc -march=znver1 -Q --help=target
The following options are target specific:
  -m128bit-long-double              [enabled]
  -m16                              [disabled]
  -m32                              [disabled]
  -m3dnow                           [disabled]
  -m3dnowa                          [disabled]
  -m64                              [enabled]
  -m80387                           [enabled]
  -m8bit-idiv                       [disabled]
  -m96bit-long-double               [disabled]
  -mabi=                            sysv
  -mabm                             [enabled]
  -maccumulate-outgoing-args        [disabled]
  -maddress-mode=                   long
  -madx                             [enabled]
  -maes                             [enabled]
  -malign-data=                     compat
  -malign-double                    [disabled]
  -malign-functions=                0
  -malign-jumps=                    0
  -malign-loops=                    0
  -malign-stringops                 [enabled]
  -mamx-bf16                        [disabled]
  -mamx-int8                        [disabled]
  -mamx-tile                        [disabled]
  -mandroid                         [disabled]
  -march=                           znver1
  -masm=                            att
  -mavx                             [enabled]
  -mavx2                            [enabled]
  -mavx256-split-unaligned-load     [disabled]
  -mavx256-split-unaligned-store    [enabled]
  -mavx5124fmaps                    [disabled]
  -mavx5124vnniw                    [disabled]
  -mavx512bf16                      [disabled]
  -mavx512bitalg                    [disabled]
  -mavx512bw                        [disabled]
  -mavx512cd                        [disabled]
  -mavx512dq                        [disabled]
  -mavx512er                        [disabled]
  -mavx512f                         [disabled]
  -mavx512ifma                      [disabled]
  -mavx512pf                        [disabled]
  -mavx512vbmi                      [disabled]
  -mavx512vbmi2                     [disabled]
  -mavx512vl                        [disabled]
  -mavx512vnni                      [disabled]
  -mavx512vp2intersect              [disabled]
  -mavx512vpopcntdq                 [disabled]
  -mavxvnni                         [disabled]
  -mbionic                          [disabled]
  -mbmi                             [enabled]
  -mbmi2                            [enabled]
  -mbranch-cost=<0,5>               3
  -mcall-ms2sysv-xlogues            [disabled]
  -mcet-switch                      [disabled]
  -mcld                             [disabled]
  -mcldemote                        [disabled]
  -mclflushopt                      [enabled]
  -mclwb                            [disabled]
  -mclzero                          [enabled]
  -mcmodel=                         [default]
  -mcpu=                            
  -mcrc32                           [disabled]
  -mcx16                            [enabled]
  -mdispatch-scheduler              [disabled]
  -mdump-tune-features              [disabled]
  -menqcmd                          [disabled]
  -mf16c                            [enabled]
  -mfancy-math-387                  [enabled]
  -mfentry                          [disabled]
  -mfentry-name=                    
  -mfentry-section=                 
  -mfma                             [enabled]
  -mfma4                            [disabled]
  -mforce-drap                      [disabled]
  -mforce-indirect-call             [disabled]
  -mfp-ret-in-387                   [enabled]
  -mfpmath=                         sse
  -mfsgsbase                        [enabled]
  -mfunction-return=                keep
  -mfused-madd                      -ffp-contract=fast
  -mfxsr                            [enabled]
  -mgeneral-regs-only               [disabled]
  -mgfni                            [disabled]
  -mglibc                           [enabled]
  -mhard-float                      [enabled]
  -mhle                             [disabled]
  -mhreset                          [disabled]
  -miamcu                           [disabled]
  -mieee-fp                         [enabled]
  -mincoming-stack-boundary=        0
  -mindirect-branch-register        [disabled]
  -mindirect-branch=                keep
  -minline-all-stringops            [disabled]
  -minline-stringops-dynamically    [disabled]
  -minstrument-return=              none
  -mintel-syntax                    -masm=intel
  -mkl                              [disabled]
  -mlarge-data-threshold=<number>   65536
  -mlong-double-128                 [disabled]
  -mlong-double-64                  [disabled]
  -mlong-double-80                  [enabled]
  -mlwp                             [disabled]
  -mlzcnt                           [enabled]
  -mmanual-endbr                    [disabled]
  -mmemcpy-strategy=                
  -mmemset-strategy=                
  -mmitigate-rop                    [disabled]
  -mmmx                             [enabled]
  -mmovbe                           [enabled]
  -mmovdir64b                       [disabled]
  -mmovdiri                         [disabled]
  -mmpx                             [disabled]
  -mms-bitfields                    [disabled]
  -mmusl                            [disabled]
  -mmwaitx                          [enabled]
  -mneeded                          [disabled]
  -mno-align-stringops              [disabled]
  -mno-default                      [disabled]
  -mno-fancy-math-387               [disabled]
  -mno-push-args                    [disabled]
  -mno-red-zone                     [disabled]
  -mno-sse4                         [disabled]
  -mnop-mcount                      [disabled]
  -momit-leaf-frame-pointer         [disabled]
  -mpc32                            [disabled]
  -mpc64                            [disabled]
  -mpc80                            [disabled]
  -mpclmul                          [enabled]
  -mpcommit                         [disabled]
  -mpconfig                         [disabled]
  -mpku                             [disabled]
  -mpopcnt                          [enabled]
  -mprefer-avx128                   -mprefer-vector-width=128
  -mprefer-vector-width=            128
  -mpreferred-stack-boundary=       0
  -mprefetchwt1                     [disabled]
  -mprfchw                          [enabled]
  -mptwrite                         [disabled]
  -mpush-args                       [enabled]
  -mrdpid                           [disabled]
  -mrdrnd                           [enabled]
  -mrdseed                          [enabled]
  -mrecip                           [disabled]
  -mrecip=                          
  -mrecord-mcount                   [disabled]
  -mrecord-return                   [disabled]
  -mred-zone                        [enabled]
  -mregparm=                        6
  -mrtd                             [disabled]
  -mrtm                             [disabled]
  -msahf                            [enabled]
  -mserialize                       [disabled]
  -msgx                             [disabled]
  -msha                             [enabled]
  -mshstk                           [disabled]
  -mskip-rax-setup                  [disabled]
  -msoft-float                      [disabled]
  -msse                             [enabled]
  -msse2                            [enabled]
  -msse2avx                         [disabled]
  -msse3                            [enabled]
  -msse4                            [enabled]
  -msse4.1                          [enabled]
  -msse4.2                          [enabled]
  -msse4a                           [enabled]
  -msse5                            -mavx
  -msseregparm                      [disabled]
  -mssse3                           [enabled]
  -mstack-arg-probe                 [disabled]
  -mstack-protector-guard-offset=   
  -mstack-protector-guard-reg=      
  -mstack-protector-guard-symbol=   
  -mstack-protector-guard=          tls
  -mstackrealign                    [disabled]
  -mstringop-strategy=              [default]
  -mstv                             [enabled]
  -mtbm                             [disabled]
  -mtls-dialect=                    gnu
  -mtls-direct-seg-refs             [enabled]
  -mtsxldtrk                        [disabled]
  -mtune-ctrl=                      
  -mtune=                           znver1
  -muclibc                          [disabled]
  -muintr                           [disabled]
  -mvaes                            [disabled]
  -mveclibabi=                      [default]
  -mvect8-ret-in-mem                [disabled]
  -mvpclmulqdq                      [disabled]
  -mvzeroupper                      [enabled]
  -mwaitpkg                         [disabled]
  -mwbnoinvd                        [disabled]
  -mwidekl                          [disabled]
  -mx32                             [disabled]
  -mxop                             [disabled]
  -mxsave                           [enabled]
  -mxsavec                          [enabled]
  -mxsaveopt                        [enabled]
  -mxsaves                          [enabled]

  Known assembler dialects (for use with the -masm= option):
    att intel

  Known ABIs (for use with the -mabi= option):
    ms sysv

  Known code models (for use with the -mcmodel= option):
    32 kernel large medium small

  Valid arguments to -mfpmath=:
    387 387+sse 387,sse both sse sse+387 sse,387

  Known indirect branch choices (for use with the -mindirect-branch=/-mfunction-return= options):
    keep thunk thunk-extern thunk-inline

  Known choices for return instrumentation with -minstrument-return=:
    call none nop5

  Known data alignment choices (for use with the -malign-data= option):
    abi cacheline compat

  Known vectorization library ABIs (for use with the -mveclibabi= option):
    acml svml

  Known address mode (for use with the -maddress-mode= option):
    long short

  Known preferred register vector length (to use with the -mprefer-vector-width= option):
    128 256 512 none

  Known stack protector guard (for use with the -mstack-protector-guard= option):
    global tls

  Valid arguments to -mstringop-strategy=:
    byte_loop libcall loop rep_4byte rep_8byte rep_byte unrolled_loop vector_loop

  Known TLS dialects (for use with the -mtls-dialect= option):
    gnu gnu2

  Known valid arguments for -march= option:
    i386 i486 i586 pentium lakemont pentium-mmx winchip-c6 winchip2 c3 samuel-2 c3-2 nehemiah c7 esther i686 pentiumpro pentium2 pentium3 pentium3m pentium-m pentium4 pentium4m prescott nocona core2 nehalem corei7 westmere sandybridge corei7-avx ivybridge core-avx-i haswell core-avx2 broadwell skylake skylake-avx512 cannonlake icelake-client rocketlake icelake-server cascadelake tigerlake cooperlake sapphirerapids alderlake bonnell atom silvermont slm goldmont goldmont-plus tremont knl knm intel geode k6 k6-2 k6-3 athlon athlon-tbird athlon-4 athlon-xp athlon-mp x86-64 x86-64-v2 x86-64-v3 x86-64-v4 eden-x2 nano nano-1000 nano-2000 nano-3000 nano-x2 eden-x4 nano-x4 k8 k8-sse3 opteron opteron-sse3 athlon64 athlon64-sse3 athlon-fx amdfam10 barcelona bdver1 bdver2 bdver3 bdver4 znver1 znver2 znver3 btver1 btver2 generic native

  Known valid arguments for -mtune= option:
    generic i386 i486 pentium lakemont pentiumpro pentium4 nocona core2 nehalem sandybridge haswell bonnell silvermont goldmont goldmont-plus tremont knl knm skylake skylake-avx512 cannonlake icelake-client icelake-server cascadelake tigerlake cooperlake sapphirerapids alderlake rocketlake intel geode k6 athlon k8 amdfam10 bdver1 bdver2 bdver3 bdver4 btver1 btver2 znver1 znver2 znver3

我也尝试了 clang,在这两种情况下,循环都是由 32 字节向量向量化的: remark: vectorized loop (vectorization width: 4, interleaved count: 4)

我正在使用 gcc 11.2.0

编辑: 根据 Peter Cordes 的要求 我意识到我实际上已经用 4 乘以基准测试了一段时间。

生成文件:

all:
    gcc -O3 -mavx2 main.c -o 3
    gcc -O3 -march=znver2 main.c -o 32  
    gcc -O3 -march=znver2 main.c -mprefer-vector-width=128 -o 32128
    gcc -O3 -march=znver1 main.c -o 31
    gcc -O2 -mavx2 main.c -o 2
    gcc -O2 -march=znver2 main.c -o 22
    gcc -O2 -march=znver2 main.c -mprefer-vector-width=128 -o 22128
    gcc -O2 -march=znver1 main.c -o 21
    hyperfine -r5 ./3 ./32 ./32128 ./31 ./2 ./22 ./22128 ./21

clean:
    rm ./3 ./32 ./32128 ./31 ./2 ./22 ./22128 ./21

代码:

#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <time.h>

int main() {
    const size_t N = 500;
    uint64_t arr[N];
    for (size_t i = 0; i < N; ++i)
        arr[i] = 1;

    for (int j = 0; j < 20000000; ++j)
        for (size_t i = 0; i < N; ++i)
            arr[i] *= 4;

    srand(time(0));
    printf("%lu\n", arr[rand() % N]); // use the array so that it is not optimized away
}

N = 500, arr[i] *= 4:

Benchmark 1: ./3
  Time (mean ± σ):      1.780 s ±  0.011 s    [User: 1.778 s, System: 0.000 s]
  Range (min … max):    1.763 s …  1.791 s    5 runs

Benchmark 2: ./32
  Time (mean ± σ):      1.785 s ±  0.016 s    [User: 1.783 s, System: 0.000 s]
  Range (min … max):    1.773 s …  1.810 s    5 runs

Benchmark 3: ./32128
  Time (mean ± σ):      1.740 s ±  0.026 s    [User: 1.737 s, System: 0.000 s]
  Range (min … max):    1.724 s …  1.785 s    5 runs

Benchmark 4: ./31
  Time (mean ± σ):      1.757 s ±  0.022 s    [User: 1.754 s, System: 0.000 s]
  Range (min … max):    1.727 s …  1.785 s    5 runs

Benchmark 5: ./2
  Time (mean ± σ):      3.467 s ±  0.031 s    [User: 3.462 s, System: 0.000 s]
  Range (min … max):    3.443 s …  3.519 s    5 runs

Benchmark 6: ./22
  Time (mean ± σ):      3.475 s ±  0.028 s    [User: 3.469 s, System: 0.001 s]
  Range (min … max):    3.447 s …  3.512 s    5 runs

Benchmark 7: ./22128
  Time (mean ± σ):      3.464 s ±  0.034 s    [User: 3.459 s, System: 0.001 s]
  Range (min … max):    3.431 s …  3.509 s    5 runs

Benchmark 8: ./21
  Time (mean ± σ):      3.465 s ±  0.013 s    [User: 3.460 s, System: 0.001 s]
  Range (min … max):    3.443 s …  3.475 s    5 runs

N = 500, arr[i] *= 5:

Benchmark 1: ./3
  Time (mean ± σ):      1.789 s ±  0.004 s    [User: 1.786 s, System: 0.001 s]
  Range (min … max):    1.783 s …  1.793 s    5 runs

Benchmark 2: ./32
  Time (mean ± σ):      1.772 s ±  0.017 s    [User: 1.769 s, System: 0.000 s]
  Range (min … max):    1.755 s …  1.800 s    5 runs

Benchmark 3: ./32128
  Time (mean ± σ):      2.911 s ±  0.023 s    [User: 2.907 s, System: 0.001 s]
  Range (min … max):    2.880 s …  2.943 s    5 runs

Benchmark 4: ./31
  Time (mean ± σ):      2.924 s ±  0.013 s    [User: 2.921 s, System: 0.000 s]
  Range (min … max):    2.906 s …  2.934 s    5 runs

Benchmark 5: ./2
  Time (mean ± σ):      3.850 s ±  0.029 s    [User: 3.846 s, System: 0.000 s]
  Range (min … max):    3.823 s …  3.896 s    5 runs

Benchmark 6: ./22
  Time (mean ± σ):      3.816 s ±  0.036 s    [User: 3.812 s, System: 0.000 s]
  Range (min … max):    3.777 s …  3.855 s    5 runs

Benchmark 7: ./22128
  Time (mean ± σ):      3.813 s ±  0.026 s    [User: 3.809 s, System: 0.000 s]
  Range (min … max):    3.780 s …  3.834 s    5 runs

Benchmark 8: ./21
  Time (mean ± σ):      3.783 s ±  0.010 s    [User: 3.779 s, System: 0.000 s]
  Range (min … max):    3.773 s …  3.798 s    5 runs

N = 512, arr[i] *= 4

Benchmark 1: ./3
  Time (mean ± σ):      1.849 s ±  0.015 s    [User: 1.847 s, System: 0.000 s]
  Range (min … max):    1.831 s …  1.873 s    5 runs

Benchmark 2: ./32
  Time (mean ± σ):      1.846 s ±  0.013 s    [User: 1.844 s, System: 0.001 s]
  Range (min … max):    1.832 s …  1.860 s    5 runs

Benchmark 3: ./32128
  Time (mean ± σ):      1.756 s ±  0.012 s    [User: 1.754 s, System: 0.000 s]
  Range (min … max):    1.744 s …  1.771 s    5 runs

Benchmark 4: ./31
  Time (mean ± σ):      1.788 s ±  0.012 s    [User: 1.785 s, System: 0.001 s]
  Range (min … max):    1.774 s …  1.801 s    5 runs

Benchmark 5: ./2
  Time (mean ± σ):      3.476 s ±  0.015 s    [User: 3.472 s, System: 0.001 s]
  Range (min … max):    3.458 s …  3.494 s    5 runs

Benchmark 6: ./22
  Time (mean ± σ):      3.449 s ±  0.002 s    [User: 3.446 s, System: 0.000 s]
  Range (min … max):    3.446 s …  3.452 s    5 runs

Benchmark 7: ./22128
  Time (mean ± σ):      3.456 s ±  0.007 s    [User: 3.453 s, System: 0.000 s]
  Range (min … max):    3.446 s …  3.462 s    5 runs

Benchmark 8: ./21
  Time (mean ± σ):      3.547 s ±  0.044 s    [User: 3.542 s, System: 0.001 s]
  Range (min … max):    3.482 s …  3.600 s    5 runs

N = 512, arr[i] *= 5

Benchmark 1: ./3
  Time (mean ± σ):      1.847 s ±  0.013 s    [User: 1.845 s, System: 0.000 s]
  Range (min … max):    1.836 s …  1.863 s    5 runs

Benchmark 2: ./32
  Time (mean ± σ):      1.830 s ±  0.007 s    [User: 1.827 s, System: 0.001 s]
  Range (min … max):    1.820 s …  1.837 s    5 runs

Benchmark 3: ./32128
  Time (mean ± σ):      2.983 s ±  0.017 s    [User: 2.980 s, System: 0.000 s]
  Range (min … max):    2.966 s …  3.012 s    5 runs

Benchmark 4: ./31
  Time (mean ± σ):      3.026 s ±  0.039 s    [User: 3.021 s, System: 0.001 s]
  Range (min … max):    2.989 s …  3.089 s    5 runs

Benchmark 5: ./2
  Time (mean ± σ):      4.000 s ±  0.021 s    [User: 3.994 s, System: 0.001 s]
  Range (min … max):    3.982 s …  4.035 s    5 runs

Benchmark 6: ./22
  Time (mean ± σ):      3.940 s ±  0.041 s    [User: 3.934 s, System: 0.001 s]
  Range (min … max):    3.890 s …  3.981 s    5 runs

Benchmark 7: ./22128
  Time (mean ± σ):      3.928 s ±  0.032 s    [User: 3.922 s, System: 0.001 s]
  Range (min … max):    3.898 s …  3.979 s    5 runs

Benchmark 8: ./21
  Time (mean ± σ):      3.908 s ±  0.029 s    [User: 3.904 s, System: 0.000 s]
  Range (min … max):    3.879 s …  3.954 s    5 runs

我认为 运行 其中 -O2 -march=znver1-O3 -march=znver1 的速度相同是我在文件命名方面的错误,当时我还没有创建 makefile然而,我正在使用我的 shell 的历史记录。

默认 -mtune=generic-mprefer-vector-width=256,并且 -mavx2 不会更改。

znver1 意味着 -mprefer-vector-width=128,因为这是 HW 的所有原始宽度。使用 32 字节 YMM 向量的指令解码为至少 2 微指令,如果它是 lane-crossing 随机播放则更多。对于像这样的简单垂直 SIMD,32 字节向量就可以了;流水线有效地处理 2-uop 指令。 (而且我认为宽度为 6 微指令但只有 5 条指令,因此仅使用 1 微指令无法获得最大 front-end 吞吐量)。但是当矢量化需要改组时,例如对于不同元素宽度的数组,GCC code-gen 可以在 256 位或更宽的情况下变得更加混乱。

vmovdqa ymm0, ymm1 mov-elimination 仅适用于 Zen1 上的低 128 位一半。此外,通常使用 256 位向量意味着之后应该使用 vzeroupper,以避免在其他 CPUs(但不是 Zen1)上出现性能问题。

我不知道 Zen1 如何处理未对齐的 32 字节 loads/stores,其中每个 16 字节的一半对齐但在单独的缓存行中。如果表现良好,GCC 可能会考虑将 znver1 -mprefer-vector-width 增加到 256。但如果不知道大小是矢量宽度的倍数,更宽的矢量意味着更多的清理代码。

理想情况下,GCC 能够检测到 easy 这种情况,并在那里使用 256 位向量。 (纯垂直,不混合元素宽度,恒定大小是 32 字节的倍数。)至少在 CPUs 上是好的:znver1,但不是 bdver2,例如 256 位存储总是很慢,因为CPU 设计错误。

您可以通过矢量化第一个循环的方式看到此选择的结果,memset-like 循环,带有 vmovdqu [rdx], xmm0https://godbolt.org/z/E5Tq7Gfzc


鉴于 GCC 决定只使用 128 位向量,它只能容纳两个 uint64_t 元素,它(正确或错误)决定不值得使用 vpsllq / vpaddd 将 qword *5 实现为 (v<<2) + v,而不是在一个 LEA 指令中使用整数。

在这种情况下几乎肯定是错误的,因为它仍然需要为每个元素或元素对单独加载和存储。 (并且循环开销因为 GCC 的默认设置是不展开,除了 PGO,-fprofile-use。SIMD 就像循环展开,特别是在 CPU 上将 256 位向量作为 2 个单独的 uops 处理。)

我不确定 GCC 所说的“未矢量化:不受支持 data-type”到底是什么意思。 x86 在 AVX-512 之前没有 SIMD uint64_t 乘法指令,因此 GCC 可能会根据 必须用多个 32x32 => 64 位 pmuludq 来模拟它来分配成本] 指令和一堆洗牌。只有在它克服了这个困难之后,它才意识到对于像 5 这样只有 2 个设置位的常量来说,它实际上非常便宜?

这可以解释 GCC 的 decision-making 过程,但我不确定这是否是正确的解释。尽管如此,这些因素仍会发生在像编译器这样的复杂机器中。熟练的人可以轻松做出更明智的选择,但编译器只是执行一系列优化过程,并不总是同时考虑全局和所有细节。


-mprefer-vector-width=256 没有帮助:

未向量化uint64_t *= 5 似乎是 GCC9 回归

(问题中的基准确认实际的 Zen1 CPU 获得了近 2 倍的加速,正如预期的那样,在 6 微指令中执行 2x uint64 而在 5 微指令中执行 1x 标量。或 4x uint64_t 在 10 微指令中使用 256 位向量,包括两个 128 位存储,这将是吞吐量瓶颈以及 front-end。)

即使使用 -march=znver1 -O3 -mprefer-vector-width=256,我们也不会使用 GCC9、10 或 11 或当前主干对 *= 5 循环进行矢量化。正如您所说,我们使用 -march=znver2https://godbolt.org/z/dMTh7Wxcq

我们确实使用 uint32_t 的这些选项进行了矢量化(甚至将矢量宽度保留为 128 位)。无论 Zen1 上是 128 位还是 256 位向量化,标量每个向量 uop(不是指令)将花费 4 次操作,所以这并没有告诉我们 *= 是否是 cost-model 决定不向量化的原因,或者只是每个 128 位内部 uop 的 2 对 4 个元素。


使用 uint64_t,更改为 arr[i] += arr[i]<<2; 仍然不会向量化,但 arr[i] <<= 1; 会。 (https://godbolt.org/z/6PMn93Y5G)。即使 arr[i] <<= 2;arr[i] += 123 在同一个循环中向量化,GCC 认为不值得向量化 *= 5 的相同指令,只是不同的操作数,常量而不是原始向量。 (标量仍然可以使用一个 LEA)。很明显 cost-model 并没有达到最终的 x86 asm 机器指令,但我不知道为什么 arr[i] += arr[i] 会被认为比 arr[i] <<= 1; 更昂贵,这是完全相同的事情.

GCC8 会矢量化您的循环,即使是 128 位矢量宽度: https://godbolt.org/z/5o6qjc7f6

# GCC8.5 -march=znver1 -O3  (-mprefer-vector-width=128)
.L12:                                            # do{
        vmovups xmm1, XMMWORD PTR [rsi]            # 16-byte load
        add     rsi, 16                             # ptr += 2 elements
        vpsllq  xmm0, xmm1, 2                      # v << 2
        vpaddq  xmm0, xmm0, xmm1                   # tmp += v
        vmovups XMMWORD PTR [rsi-16], xmm0         # store
        cmp     rax, rsi
        jne     .L12                             # } while(p != endp)

使用 -march=znver1 -mprefer-vector-width=256,使用 vmovups xmm / vextracti128 将存储作为两个 16 字节的一半是 znver1 意味着 -mavx256-split-unaligned-store(影响每个当 GCC 不确定 它已对齐时存储。因此即使数据确实对齐,它也会花费额外的指令ppen 对齐)。

znver1 并不意味着 -mavx256-split-unaligned-load,因此 GCC 愿意将负载作为内存源操作数折叠到有用的代码中的 ALU 操作中。