在 x64 系统上递增一串长字的最快汇编代码

Fastest assembly code to increment a string of longwords on x64 system

很久以前,我对 Z80 和 68000 汇编程序进行了一些业余爱好编程,所以我了解基础知识,但我是 x86/x64 汇编程序的新手。我正在尝试找到 ** 最快 ** 代码来执行以下操作(需要最佳速度的关键部分仅是步骤 2-4)。

我根本不需要步骤 1 或 5 的帮助,它们仅供参考。 我不是在寻找完整的代码编写,但希望得到任何有关此平台的最佳指令和算法的提示。有很多方法可以编写这样的例程,但通常最明显的方法是不是最优的。如果有人说 "try using the XYZ instruction" 之类的话,我会很好。此外,正如我在下面提到的,在汇编程序中使用数组可能不是最快的方法,因此关于如何优化数据结构以提高速度的任何建议也是我正在寻找的答案的一部分。 (x64 汇编程序甚至可以处理带索引的 4GB 数组吗?

该文件通常很小(小于 100K),但 有时 可能有 1GB 的元素那么长。 不需要将所有元素同时读入内存,只要单个元素直接accessed/modified即可。本能地,这听起来像使用 4GB 数组是最快的,但我是 x64 汇编程序的新手,不确定数组的 overhead 是否会帮助或损害速度。

子程序所花费的时间不在本题范围之内,暂时可以直接使用return指令。 然而,子例程需要知道元素的索引。

还有两个相关问题:



对 "TOO BROAD" 关闭标志的响应:

这个问题 "Too Broad" 如何 准确地 符合 SO Help Page 社区指南顶部的所有四个主题描述:

请参阅 标签 wiki,了解有关很多内容的信息。

为什么要在回信之前完成所有 I/O?您未指定的子例程是否需要随机访问任意元素?如果是这样,mmap(2) 你的整个文件。 (假设 POSIX 系统调用)。

如果不是,read(2) 放入一个大约 128kB 的缓冲区。 (小于二级缓存)。对其进行处理,然后 pwrite(2) 将其返回到您读取它的位置。 (或 lseek(2)/write(2))。

您的子例程是否可能中止整个过程,导致文件没有修改?


您可以使用 SSE2 进行递增:使用 PADDD / MOVMSKPS 并行执行四个 32 位(双字)加法,然后提取符号位。在掩码结果上使用 test 查看是否有任何元素设置了符号位。如果是这样,请为这些元素调用子例程。

bsf 将找到第一个设置位。有 BMI1 或 BMI2 指令清除最低设置位 IIRC。您可以使用它来遍历掩码中的设置位。

或者,如果您发现向量中的任何元素都设置了符号位,则可以跳过向量存储回到数组中,而是使用标量代码再次遍历这些元素。这样做的好处是在调用子程序时数组中的相邻元素处于 "correct" 状态。

例如

    ;; set up constants
    pcmpeqw   xmm1, xmm1
    psrld     xmm1, 31     ; xmm1 = [ 1 1 1 1]

    ; rsi = start,  rdi = one-past-the-end
    ; or maybe prefer keeping these in regs the subroutine won't clobber
.vectorloop:
    movdqa    xmm0, [rsi]
    paddd     xmm0, xmm1
    movmskps  eax, xmm1       ; pmovmskb would give us the high bit of every byte.  This is just every dword element
    test      eax, eax
    jnz     .at_least_one_sign_bit_set
    movdqa    [rsi], xmm0     ; vector store back, since no elements had sign bits set
.resume_vectorloop:    ; scalar code jumps back here when done
    add       rsi, 16
    cmp       rsi, rdi
    jb       .vectorloop

    jmp     all_done

.at_least_one_sign_bit_set:

    ; Array isn't modified at this point.
    inc     dword [rsi]                ;; or better, load / inc / jns, passing the pointer and index to the subroutine, so it doesn't have to load again after the read-modify-write inc.
    jns ...
        ;; maybe add rsi, 4  here, depending on how we want want to call the subroutine.
    inc     dword [rsi+4]
    jns ...
    ...
    jmp      .resume_vectorloop    ;; or duplicate the tail and cmp/jb to .vectorloop

这假设您的缓冲区是对齐的并且大小是矢量宽度的倍数,因此您不必关心未对齐或标量清理。您控制缓冲区,所以这应该很容易。 (除了mmap的长度部分,可能。但这不是一个很难解决的问题。)