为什么 glibc memcpy 不选择 avx512 版本?

Chi*_*ing 2 c glibc compiler-optimization avx512

我编译了以下示例代码:

 #cat array_addition.c 
 #define MAX 1000000
 #define S 1024
 #include <string.h>
int a[S], b[S], c[S];

__attribute__((target_clones("avx512f", "avx2","arch=atom","default")))
void foo(int argc){
    int i,x;

for (x=0; x<1024; x++){
    for (i=0; i<S; i++){
        a[i] = b[i] + c[i];
    }
}
    b[0] = argc;
    memcpy(&a[0], &b[0], argc *sizeof(int));
}
int main(int argc, char** argv) {
    foo(argc);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

其中调用 memcpy;

从 objdump 中,我们可以发现它会调用 GLIBC memcpy:

#readelf -r a.out 

Relocation section '.rela.dyn' at offset 0x418 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000403ff8  000200000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

Relocation section '.rela.plt' at offset 0x430 contains 4 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000404018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000404020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000404028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 memcpy@GLIBC_2.14 + 0
000000404030  000000000025 R_X86_64_IRELATIV                    4018f0
Run Code Online (Sandbox Code Playgroud)

然后,我使用 gdb 来跟踪它使用的 glibc 实现;

(gdb) b memcpy@plt    
Breakpoint 1 at 0x401050
(gdb) s
The program is not being run.
(gdb) r
Starting program: /root/a.out 

Breakpoint 1, 0x0000000000401050 in memcpy@plt ()
(gdb) s
Single stepping until exit from function memcpy@plt,
which has no line number information.
0x00007ffff7b623a0 in __memcpy_ssse3_back () from /lib64/libc.so.6
(gdb) info function __memcpy_*
All functions matching regular expression "__memcpy_*":

Non-debugging symbols:
0x00007ffff7aa2840  __memcpy_chk_sse2
0x00007ffff7aa2850  __memcpy_sse2
0x00007ffff7ab1b40  __memcpy_chk_avx512_no_vzeroupper
0x00007ffff7ab1b50  __memcpy_avx512_no_vzeroupper
0x00007ffff7b23360  __memcpy_chk
0x00007ffff7b5a470  __memcpy_chk_ssse3
0x00007ffff7b5a480  __memcpy_ssse3
0x00007ffff7b62390  __memcpy_chk_ssse3_back
0x00007ffff7b623a0  __memcpy_ssse3_back
(gdb) 
Run Code Online (Sandbox Code Playgroud)

有__memcpy_avx512_no_zeroupper,但没有被选中;

我的 CPU 支持它的功能:

标志:FPU VME德PSE TSC MSR PAE MCE CX8 APIC月MTRR PGE MCA CMOV轻拍PSE36 CLFLUSH DTS ACPI MMX FXSR SSE SSE2 SS HT TM PBE系统调用的nx pdpe1gb rdtscp流明constant_tsc技术arch_perfmon PEBS BTS rep_good nopl xtopology nonstop_tsc CPUID aperfmperf PNI pclmulqdq dtes64监视ds_cpl VMX SMX EST TM2 SSSE3 SDBG FMA CX16 xtpr PDCM PCID DCA sse4_1 sse4_2 x2apic movbe POPCNT tsc_deadline_timer AES XSAVE AVX F16C rdrand lahf_lm ABM 3dnowprefetch cpuid_fault EPB cat_l3 cdp_l3 invpcid_single PTI intel_ppin SSBD MBA IBRS ibpb stibp tpr_shadow vnmi FlexPriority可EPT VPID ept_ad fsgsbase tsc_adjust BMI1 HLE AVX2 SMEP bmi2 erms invpcid rtm cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb intel_pt avx512cd avx512bw avx512vl xsaveopt xsavec xgetbqm_pmc_mbtm_pmc_m_p_m_p_m_p_m_p_m_p_m_p_mc_m_p_m_p_m_qlcuptc_mbqm_pllcupdc_mc_pllcupdc_mbqm_pllcpus冲洗_l1d

gcc 版本:

使用内置规范。COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/root/china-gcc-10.2.0/libexec/gcc/x86_64-pc-linux-gnu/10.2.0/lto-wrapper 目标:x86_64-pc-linux-gnu 配置:./configure --prefix=/root/china-gcc-10.2.0 --disable-multilib 线程模型:posix 支持的 LTO 压缩算法:zlib gcc version 10.2.0 (GCC)

Pet*_*des 5

在像 Skylake-X 和 IceLake 这样的“主流”CPU 上,如果您在程序的大部分运行时间中始终如一地使用 512 位向量,而不仅仅是偶尔的 memcpy,那么只值得使用 512 位向量。(而且如果您的程序将运行很长时间,否则您会通过上下文切换和/或超线程减慢共享相同物理核心的其他进程。)有关详细信息,请参阅降低 CPU 频率的 SIMD 指令:您没有希望偶尔调用 memcpy 以将您的 CPU 频率降低到较低的最大涡轮增压。

使用带有 256 位向量 (AVX-512VL) 的 AVX-512 功能在某些方面是值得的,例如,如果屏蔽很好,或者如果您使用 YMM16..31 来避免 VZEROUPPER。

我猜想 glibc 只会__memcpy_avx512_no_vzeroupper在像 Knight's Landing (KNL) Xeon Phi 这样的系统上解析 memcpy ,其中 CPU 是围绕 AVX-512 设计的,并且使用 512 位 ZMM 向量没有缺点。即使在 KNL 上使用 ymm0..15 之后,也不需要 vzeroupper。事实上,vzeroupper 在 KNL 上非常慢,而且绝对是要避免的,因此no_vzeroupper输入了函数名称。

https://code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/memmove-avx512-no-vzeroupper.S.html是该版本的来源。它使用 ZMM 向量,包括 ZMM0..15,所以如果在 Skylake/IceLake CPU 上使用它应该使用 vzeroupper。 这个版本看起来是为 KNL 设计的。


使用 ymm16..31 避免 vzeroupper(加速 32 .. 64 字节副本)的 AVX-512VL 版本会有一些微小的好处,而无需使用 ZMM 寄存器。

并且__memcpy_avx512_no_vzeroupper只使用 ZMM16..31 是有意义的,因此在主流 CPU 上避免 vzeroupper 不是问题;那么这将是已经大量使用 AVX-512 的代码中的一个可用选项(因此已经支付了 CPU 频率成本。)