如果 AVX 可用,为什么 copy_user_enhanced_fast_string 不使用它?

St.*_*rio 1 linux assembly x86-64 linux-kernel

在了解我的应用程序(I/O 密集型)的分析结果时,我面临copy_user_enhanced_fast_string成为最热门的区域之一。在用户空间和内核空间之间复制时调用它。x86 上的实现如下所示:

ENTRY(copy_user_enhanced_fast_string)
    ASM_STAC
    cmpl $64,%edx
    jb .L_copy_short_string /* less then 64 bytes, avoid the costly 'rep' */
    movl %edx,%ecx
1:  rep
    movsb
    xorl %eax,%eax
    ASM_CLAC
    ret

    .section .fixup,"ax"
12: movl %ecx,%edx      /* ecx is zerorest also */
    jmp .Lcopy_user_handle_tail
    .previous

    _ASM_EXTABLE_UA(1b, 12b)
ENDPROC(copy_user_enhanced_fast_string)
Run Code Online (Sandbox Code Playgroud)

为什么不vmovaps/vmovups用于那个?不是已经证明 AVX 在复制可用的地方没有性能优势吗?

Pet*_*des 7

内核代码只能在kernel_fpu_begin()/之间安全地使用 FPU / SIMDkernel_fpu_end()来触发xsave(并且xrstor在返回用户空间之前)。或 xsaveopt 或其他什么。

这是一个很大的开销,除了少数罕见的情况(如mdRAID5/RAID6 奇偶校验创建/使用)之外,这是不值得的。

不幸的是,这意味着大多数内核代码只能使用 GP 整数寄存器。 AVX memcpy 循环和rep movsbmemcpy之间的区别不值得在每个系统调用上使用 xsave/xrstor。


上下文切换 vs. 刚进入内核后台:

在用户空间中,内核处理用户空间任务之间上下文切换时的状态保存/恢复。在内核中,当您即将返回相同的用户空间时,您希望避免每次进入内核(例如系统调用)时进行繁重的 FPU 保存/恢复,因此您只需保存 GP-integer regs .


对于已知大小的副本,没有 SSE/AVX 还不错,尤其是在具有 ERMSB 功能的 CPU 上(当使用此复制功能时,因此enhanced_fast_string得名)。对于大中型对齐副本,rep movsb至少在 Intel CPU 上几乎和 AMD 一样快。有关memcpy 的信息,请参阅增强型 REP MOVSB。或者没有 ERMSB,至少有rep movsq+ 清理。

在 64 位内核中,GP 整数 reg 是 XMM reg 大小的一半。对于小副本(低于内核的 64 字节阈值),与一般系统调用的开销相比,8x GP 整数 8 字节加载和 8 字节存储应该非常有效。4x XMM 加载/存储会很好,但这是与保存 FPU 状态的权衡。

没有SIMD是显著恶化的strlen/strcpy在那里pcmpeqb非常好对在同一时间bithack 4或8个字节。而 SSE2 是 x86-64 的基线,所以如果不是为了保存 FPU 状态的问题,x86-64 内核可以依赖它而无需动态调度。

理论上,您可以吃掉 SSE/AVX 转换惩罚,并像一些糟糕的 Windows 驱动程序一样做,只需手动保存/恢复带有传统 SSE 指令的向量 reg 的低 128。(这就是传统 SSE 指令不会将完整 YMM / ZMM 的高位字节归零的原因)。IDK,如果有人对内核模式strcpystrlen, 或memcpy.

  • @St.Antario:在用户空间中,内核处理用户空间任务之间的上下文切换时的状态保存/恢复。在内核中,当您要返回相同的用户空间时,您希望每次进入内核(例如系统调用)时避免大量的 FPU 保存/恢复,因此您只需保存 GP 整数寄存器。它与 SSE/AVX 转换完全无关。 (3认同)
  • @St.Antario:是的,“mmap”可能比“read”快一些。但是你需要memcpy吗?这可能会消耗大部分收益。理想情况下,您可以直接使用映射区域,或者至少在复制过程中做“一些”有用的工作。(例如,计算某些内容、必要时进行字节交换、转置或通常第一个处理步骤是什么,即使它通常是只读步骤。) (2认同)