Assembly x86_64检查2加载/存储的电源是否会对2个指针进行页交叉

Assembly x86_64检查2加载/存储的电源是否会对2个指针进行页交叉,assembly,x86-64,micro-optimization,Assembly,X86 64,Micro Optimization,基本上,我希望在x86_64汇编中尽快实现以下内容。(其中,foo和bar可能类似于glibc的手写asm strcpy或strcmp,我们希望从宽向量开始,但在不需要时没有页面拆分加载的安全性和/或性能缺点。或者AVX-512掩蔽存储:故障抑制对正确性有效,但如果必须采取行动,则速度较慢(不显示目标中的故障。) 对于一个指针,有很多非常有效的方法可以做到这一点,其中许多方法在中进行了讨论,但对于两个指针,似乎没有一种特别有效的方法不会牺牲准确性 方法 从这里开始,假设ptr1从rdi开始,而p

基本上,我希望在x86_64汇编中尽快实现以下内容。(其中,
foo
bar
可能类似于glibc的手写asm strcpy或strcmp,我们希望从宽向量开始,但在不需要时没有页面拆分加载的安全性和/或性能缺点。或者AVX-512掩蔽存储:故障抑制对正确性有效,但如果必须采取行动,则速度较慢(不显示目标中的故障。)

对于一个指针,有很多非常有效的方法可以做到这一点,其中许多方法在中进行了讨论,但对于两个指针,似乎没有一种特别有效的方法不会牺牲准确性

方法 从这里开始,假设
ptr1
rdi
开始,而
ptr2
rsi
开始。负载大小将由常量
LSIZE
表示

快速检测假阳性
  • 潜伏期:4c
  • 吞吐量:测量值约为1.75℃(请注意,Icelake上的tput
    lea高于旧CPU)
  • 字节:21b
它有一个4c的延迟,这不是很糟糕,但是它的吞吐量更差,并且它有一个更大的代码占用空间

问题:
  • 在延迟、吞吐量或字节方面,这两种方法中的任何一种都能得到改进吗?通常我最感兴趣的是延迟>吞吐量>字节
  • 我的总体目标是尽快得到正确的病例和假阳性

    编辑: 修正了正确版本中的错误

    CPU:
    就我个人而言,我正在为使用AVX512的CPU进行调整,以便使用Skylake Server、Icelake和Tigerlake,但这个问题针对的是整个Sandybridge系列。

    如果在
    a%4096==4096-size
    处出现一个误报,您可以使用以下方法:

    ~a & (4096 - size) == 0
    
    转换为汇编:

      not edi
      not esi
      test edi, (4096 - size)
      jz crosses-page-boundary
      test esi, (4096 - size)
      jz crosses-page-boundary
      (2 cycle latency)
    
    说明:对于size=32,我们希望地址的最后12位大于4096-32=4064=
    0b1111'1110'0000
    。我们知道,只有当一个数的前导1位和低位5位中的任何一个都相同时,它才能等于或大于这个数。我们不能很容易地测试所有指定的位是否都是一,因此我们使用
    testedi,(4096-size)
    反转这些位并测试它们是否都为零



    注意:您可以通过使用
    neg
    而不是
    not
    将假阳性转移到
    a%4096==0
    (我认为这更糟,因为它更可能发生?)(
    -a=~a+1
    ,因此,如果所有低5位值均为零,则在反转后它们变为1,加上1将其带入测试区域,这使得
    a%4096==0
    为假阳性,但隐藏了
    a%4096==4096-大小
    的假阳性).

    如果您正在为冰湖进行调整,请注意,微码更新禁用了整数寄存器的mov消除,以围绕ICL065勘误表工作。因此,依赖或不可能在与mov相同的周期内运行。请注意,误报不是正确性问题,因此这是一种权衡,取决于处理页面交叉的额外成本e> 我想对于strcpy的AVX-512屏蔽存储的错误抑制,对于小拷贝来说非常重要,而且关键是页面中没有错误,因此对于相同缓冲区中的拷贝,它可以重复发生。(即使它在页表中是写时复制或只读的,但仍然由malloc在逻辑上分配。)我认为正确的解决方案需要使用
    LSIZE-1
    进行添加(lea)?@PeterCordes Icelake只是基准CPU,没有专门针对它进行调整。添加了编辑。仅使用一个分支可以有多高的效率?我觉得它必须比ICC在两个布尔()上使用
    |
    时的效率更高(gcc和clang只进行了两次分支),可能在某个地方使用
    指令,或者以某种方式使用
    andn
    ?我想只需对两个指针进行O型运算并检查结果偏移量是否接近页面末尾就可以了,但这会带来很大的误报可能性。@PeterCordes也许它们可以与移位相结合?即沿着(但效率高于)。事实上,than shift将零低位用于
    和n
    似乎已经成熟,但似乎无法使其工作。@Stephan nice!这比一页交叉中的内容快得多,速度更快/代码更少。不过,有点担心两个分支。我想再多考虑一下这个问题,这个解决方案将1个假阳性替换为任何一个1b代码或者,与
    not
    ->
    和l
    /
    sall
    相比,为第一条指令增加两个端口。猜测取决于数据是否成功。@Noah对这两条指令的测试(在一次转换后)使其“a跨越页面边界”和“b跨越页面边界”。这似乎是一个分支的罪魁祸首:p
                                            // cycles, bytes
        leal    (LSIZE - 1)(%rdi), %eax     // 0     , 4
        leal    (LSIZE - 1)(%rsi), %edx     // 0     , 8
        xorl    %edi, %eax                  // 1     , 11
        xorl    %esi, %edx                  // 1     , 14
        orl     %edx, %eax                  // 2     , 17
        testl   $4096, %eax                 // 3     , 22
        jnz     L(page_cross)
    
    ~a & (4096 - size) == 0
    
      not edi
      not esi
      test edi, (4096 - size)
      jz crosses-page-boundary
      test esi, (4096 - size)
      jz crosses-page-boundary
      (2 cycle latency)