assembly - x86_64 检查 2 个加载/存储的幂是否会为 2 个指针分页
问题描述
基本上我希望尽快在 x86_64 程序集中实现以下内容。(其中foo
可能bar
是 glibc 的手写 asm strcpy 或 strcmp 之类的东西,我们希望从宽向量开始,但在不需要页面拆分加载时没有安全和/或性能缺点。或者AVX-512 掩码存储:故障抑制用于正确性,但如果它必须实际抑制目标中的故障,则速度很慢。)
#define TYPE __m256i
int has_page_cross(void * ptr1, void * ptr2) {
uint64_t ptr1_u64 = (uint64_t)ptr1;
uint64_t ptr2_u64 = (uint64_t)ptr2;
ptr1_u64 &= 4095;
ptr2_u64 &= 4095;
if((ptr1_u64 + sizeof(TYPE)) > 4096
|| (ptr2_u64 + sizeof(TYPE)) > 4096) {
// There will be a page cross
return foo_handling_page_cross(ptr1, ptr2);
}
return bar_with_no_page_cross(ptr1, ptr2);
}
对于一个指针,有很多非常有效的方法可以做到这一点,其中许多在 x86 和 x64 上的同一页面内读取缓冲区末尾是否安全?但是对于不牺牲准确性的两个指针,似乎没有特别有效的方法。
方法从这里开始,假设ptr1
开始于rdi
并且ptr2
开始于rsi
。负载大小将由常量表示LSIZE
。
快速误报
// cycles, bytes
movl %edi, %eax // 0 , 2 # assuming mov-elimination
orl %esi, %eax // 0 , 5 # which Ice Lake disabled
andl $4095, %eax // 1 , 10
cmpl $(4096 - LSIZE), %eax // 2 , 15
ja L(page_cross)
/* less bytes
movl %edi, %eax // 0 , 2
orl %esi, %eax // 1 , 5
sall $20, %eax // 2 , 8
cmpl $(4096 - LSIZE) << 20, %eax // 3 , 13
ja L(page_cross)
*/
- 延迟:3c
- 吞吐量:~1.08c 实测(两个版本)。
- 字节:13b
这种方法很好,因为它速度快,延迟为 3c(假设movl %edi, %eax
已消除),吞吐量高,并且对于前端来说非常紧凑。
明显的缺点是它会有误报rdi = 4000
,即rsi = 95
. 我认为尽管它的性能应该作为完全正确解决方案的目标。
较慢但正确
这是我能想到的最好的
// 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)
- 延迟:4c
- 吞吐量:约 1.75c 测量值(注意 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 位中的任何内容都相同时才能等于或大于这个数字。我们无法轻松测试所有指定的位是否为 1,因此我们将这些位反转并测试它们是否都为 0 test edi, (4096 - size)
。
请注意,您可以通过使用而不是(将误报转移到a % 4096 == 0
(我认为它更糟,因为它更有可能发生?),因此如果所有低 5 位值都为零,则在反转后它们变为 1 并加一携带它进入测试区域,使其成为 的误报,但隐藏了 ) 的误报。neg
not
-a = ~a + 1
a % 4096 == 0
a % 4096 == 4096 - size
推荐阅读
- python - 熊猫以非固定周期移动行
- eclipse - Eclipse 4.14 的 SVN 插件
- ionic-framework - Ionic/Angular 5.0.1 global.scss 注释掉了,我该如何应用到我的应用程序?
- python - Jinja2 未正确显示在网页中
- powershell - 设置路径参数失败
- javascript - bootstrap-vue:b-form-datepicker 在悬停之前不会更新到 v-model
- c# - 检查触摸区域时触摸很慢
- node.js - 找不到参考错误问题来自哪里
- vue.js - 使用 vue 框架部署 Web 应用程序
- javascript - 从执行ajax请求的js函数返回bool