性能报告显示此函数“__memset_avx2_unaligned_erms”有开销。这是否意味着内存未对齐?

perf report shows this function "__memset_avx2_unaligned_erms" has overhead. does this mean memory is unaligned?

我正在尝试使用 perf 工具分析我的 C++ 代码。实现包含带有 SSE/AVX/AVX2 指令的代码。除此之外,代码还使用 -O3 -mavx2 -march=native 标志进行编译。我相信 __memset_avx2_unaligned_erms 函数是 memset 的 libc 实现。 perf 表明这个函数有相当大的开销。函数名称表示内存未对齐,但是在代码中我使用 GCC 内置宏 __attribute__((aligned (x))) 显式对齐内存是否明确对齐?

我已将示例报告作为图片附上。

不,不是。 这意味着 glibc 在该硬件上选择的 memset 策略不会尝试完全避免对齐访问,在小尺寸情况下。 (glibc 在动态链接器符号解析时选择一个 memset 实现,因此它在第一次调用后得到运行时调度而没有额外的开销。)

如果您的缓冲区实际上是对齐的并且大小是矢量宽度的倍数,则所有访问都将对齐并且基本上没有开销。 (使用 vmovdqu 和一个恰好在运行时对齐的指针完全等同于 vmovdqa 在所有支持 AVX 的 CPU 上。)

对于大缓冲区,它仍然会在主循环之前对齐指针,以防它未对齐, 代价是一些额外的指令与仅实现适用于 32 字节对齐的指针。 (但看起来它使用了 rep stosb 而没有对齐指针,如果它要指向 rep stosb 的话。)

gcc+glibc 没有只用对齐指针调用的特殊版本的 memset。 (或用于不同对齐保证的多个特殊版本)。 GLIBC 的 AVX2 未对齐实现适用于对齐和未对齐输入。


glibc/sysdeps/x86_64/multiarch/memset-avx2-unaligned-erms.S, which defines a couple macros (like defining the vector size as 32) and then #includes "memset-vec-unaligned-erms.S"中定义。

源码中的注释说:

/* memset is implemented as:
   1. Use overlapping store to avoid branch.
   2. If size is less than VEC, use integer register stores.
   3. If size is from VEC_SIZE to 2 * VEC_SIZE, use 2 VEC stores.
   4. If size is from 2 * VEC_SIZE to 4 * VEC_SIZE, use 4 VEC stores.
   5. If size is more to 4 * VEC_SIZE, align to 4 * VEC_SIZE with
      4 VEC stores and store 4 * VEC at a time until done.  */

主循环之前的实际对齐是在一些 vmovdqu 向量存储之后完成的(如果用于实际对齐的数据则没有惩罚:https://agner.org/optimize/):

L(loop_start):
    leaq        (VEC_SIZE * 4)(%rdi), %rcx   # rcx = input pointer + 4*VEC_SIZE
    VMOVU        %VEC(0), (%rdi)            # store the first vector
    andq        $-(VEC_SIZE * 4), %rcx      # align the pointer
    ...  some more vector stores
    ...  and stuff, including storing the last few vectors I think
    addq        %rdi, %rdx                  # size += start, giving an end-pointer
    andq        $-(VEC_SIZE * 4), %rdx      # align the end-pointer

L(loop):                                       # THE MAIN LOOP
    VMOVA        %VEC(0), (%rcx)               # vmovdqa = alignment required
    VMOVA        %VEC(0), VEC_SIZE(%rcx)
    VMOVA        %VEC(0), (VEC_SIZE * 2)(%rcx)
    VMOVA        %VEC(0), (VEC_SIZE * 3)(%rcx)
    addq        $(VEC_SIZE * 4), %rcx
    cmpq        %rcx, %rdx
    jne        L(loop)

因此 VEC_SIZE = 32,它将指针对齐 128。这太过分了;缓存行是 64 字节,实际上只要与矢量宽度对齐就可以了。

它还有一个使用 rep stos 的阈值,如果启用并且缓冲区大小 > 2kiB,在具有 ERMSB 的 CPU 上。 ().