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

why glibc memcpy not choose avx512 version?

我编译了一个示例代码如下:

 #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;
}

调用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

然后,我使用 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) 

有__memcpy_avx512_no_zeroupper,但没有被选中;

我的 cpu 支持它的功能:

Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor 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 xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts pku ospke flush_l1d

gcc 版本:

Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/root/china-gcc-10.2.0/libexec/gcc/x86_64-pc-linux-gnu/10.2.0/lto-wrapper Target: x86_64-pc-linux-gnu Configured with: ./configure --prefix=/root/china-gcc-10.2.0 --disable-multilib Thread model: posix Supported LTO compression algorithms: zlib gcc version 10.2.0 (GCC)

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

将 AVX-512 特征与 256 位向量 (AVX-512VL) 结合使用对于某些事情来说是值得的,例如如果屏蔽很好,或者如果您使用 YMM16..31 来避免 VZEROUPPER。

我猜 glibc 只会在像 Knight's Landing (KNL) Xeon Phi 这样的系统上将 memcpy 解析为 __memcpy_avx512_no_vzeroupper,其中 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 的 AVX-512VL 版本来避免 vzeroupper(以加速 32 .. 64 字节拷贝),而无需使用 ZMM 寄存器会有一些微小的好处。

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