Assembly x86_64检查2加载/存储的电源是否会对2个指针进行页交叉
基本上,我希望在x86_64汇编中尽快实现以下内容。(其中,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
foo
和bar
可能类似于glibc的手写asm strcpy或strcmp,我们希望从宽向量开始,但在不需要时没有页面拆分加载的安全性和/或性能缺点。或者AVX-512掩蔽存储:故障抑制对正确性有效,但如果必须采取行动,则速度较慢(不显示目标中的故障。)
对于一个指针,有很多非常有效的方法可以做到这一点,其中许多方法在中进行了讨论,但对于两个指针,似乎没有一种特别有效的方法不会牺牲准确性
方法
从这里开始,假设ptr1
从rdi
开始,而ptr2
从rsi
开始。负载大小将由常量LSIZE
表示
快速检测假阳性
- 潜伏期:4c
- 吞吐量:测量值约为1.75℃(请注意,Icelake上的tput
lea高于旧CPU)
- 字节:21b
就我个人而言,我正在为使用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)