Arm A64霓虹灯SIMD-256位比较
我想比较两个小端256位值与A64霓虹灯指令(asm)的效率Arm A64霓虹灯SIMD-256位比较,arm,comparison,simd,neon,arm64,Arm,Comparison,Simd,Neon,Arm64,我想比较两个小端256位值与A64霓虹灯指令(asm)的效率 相等(=) 为了平等,我已经找到了一个解决方案: bool eq256(const UInt256 *lhs, const UInt256 *rhs) { bool result; 首先,将这两个值加载到SIMD寄存器中 __asm__("ld1.2d { v0, v1 }, %1 \n\t" "ld1.2d { v2, v3 }, %2 \n\t" 将值的每个64
相等(=) 为了平等,我已经找到了一个解决方案:
bool eq256(const UInt256 *lhs, const UInt256 *rhs) {
bool result;
首先,将这两个值加载到SIMD寄存器中
__asm__("ld1.2d { v0, v1 }, %1 \n\t"
"ld1.2d { v2, v3 }, %2 \n\t"
将值的每个64位分支相互比较。对于相等的肢体,这将导致-1(所有位设置),如果位不同,则为0(所有位清除)
"cmeq.2d v0, v0, v2 \n\t"
"cmeq.2d v1, v1, v3 \n\t"
将结果从2个向量减少到1个向量,如果有,只保留包含“0(所有位清除)”的向量
"uminp.16b v0, v0, v1 \n\t"
"uminv.16b b0, v0 \n\t"
将结果从1个向量减少到1个字节,如果有,只保留一个带零的字节
"uminp.16b v0, v0, v1 \n\t"
"uminv.16b b0, v0 \n\t"
移动到ARM寄存器,然后与0xFF进行比较。这就是结果
"umov %w0, v0.b[0] \n\t"
"cmp %w0, 0xFF \n\t"
"cset %w0, eq "
: "=r" (result)
: "m" (*lhs->value), "m" (*rhs->value)
: "v0", "v1", "v2", "v3", "cc");
return result;
}
问题
- 这比使用普通的ARM寄存器进行4次比较更有效吗
- e、 g.是否有来源引用不同操作的时间?我在iphone5s上这么做
- 有没有办法进一步优化这一点?我认为我浪费了很多周期,只是为了把整个向量简化为一个标量布尔
小于比较(使用XCTest measureMetrics和基于Swift的测试运行程序进行基准测试。分配两个256位的整数。然后,在相同的两个整数上重复一个操作1亿次,停止测量,并使用arc4random为两个整数的每个分支分配一个新的随机值。在连接仪器的情况下执行第二次运行,CPU时间d每一条指令的分布都在其旁边以注释的形式标注
相等(=) 对于相等性,当结果从SIMD寄存器传输回ARM寄存器时,SIMD似乎会丢失。SIMD可能只有在结果用于进一步SIMD计算时,或者如果使用的INT长于256位时才值得使用(ld1似乎比ldp快)
- SIMD
测量的[时间,秒]平均值:11.558,相对标准偏差:0.065%,值:[11.572626,11.560558,11.549322,11.568718,11.558530,11.550490,11.557086,11.551803,11.557529,11.549782]bool result; __asm__("ld1.2d { v0, v1 }, %1 \n\t" // 5.1% "ld1.2d { v2, v3 }, %2 \n\t" // 26.4% "cmeq.2d v0, v0, v2 \n\t" "cmeq.2d v1, v1, v3 \n\t" "uminp.16b v0, v0, v1 \n\t" // 4.0% "uminv.16b b0, v0 \n\t" // 26.7% "umov %w0, v0.b[0] \n\t" // 32.9% "cmp %w0, 0xFF \n\t" // 0.0% "cset %w0, eq " : "=r" (result) : "m" (*lhs->value), "m" (*rhs->value) : "v0", "v1", "v2", "v3", "cc"); return result; // 4.9% ("ret")
- 标准
这里的赢家。
指令在这里真的很出色:-) 不过,很明显,问题出在内存限制上ccmp
测量的[时间,秒]平均值:11.146,相对标准偏差:0.034%,值:[11.149754,11.142854,11.146840,11.149392,11.141254,11.148708,11.142293,11.150491,11.139593,11.145873]bool result; __asm__("ldp x8, x9, %1 \n\t" // 33.4% "ldp x10, x11, %2 \n\t" "cmp x8, x10 \n\t" "ccmp x9, x11, 0, eq \n\t" "ldp x8, x9, %1, 16 \n\t" // 34.1% "ldp x10, x11, %2, 16 \n\t" "ccmp x8, x10, 0, eq \n\t" // 32.6% "ccmp x9, x11, 0, eq \n\t" "cset %w0, eq \n\t" : "=r" (result) : "m" (*lhs->value), "m" (*rhs->value) : "x8", "x9", "x10", "x11", "cc"); return result;
- C
LLVM无法检测到“ccmp”是在此处使用的好指令,并且比上面的asm版本慢
编译为return lhs->value[0] == rhs->value[0] & lhs->value[1] == rhs->value[1] & lhs->value[2] == rhs->value[2] & lhs->value[3] == rhs->value[3];
测量的[时间,秒]平均值:11.531,相对标准偏差:0.040%,值:[11.525511,11.529820,11.541940,11.531776,11.533287,11.526628,11.531392,11.526037,11.531784,11.533786]ldp x8, x9, [x0] // 24.1% ldp x10, x11, [x1] // 0.1% cmp x8, x10 // 0.4% cset w8, eq // 1.0% cmp x9, x11 // 23.7% cset w9, eq and w8, w8, w9 // 0.1% ldp x9, x10, [x0, #16] ldp x11, x12, [x1, #16] // 24.8% cmp x9, x11 cset w9, eq // 0.2% and w8, w8, w9 cmp x10, x12 // 0.3% cset w9, eq // 25.2% and w0, w8, w9 ret // 0.1%
小于(在其他地方,
UInt256
是如何使用的,也就是说,值是否更可能事先存在于SIMD寄存器、通用寄存器或内存中?我可以想象cmp
和3ccmp
s的开销可能比一堆SIMD寄存器杂耍要小,但必须溢出一堆GP寄存器并加载值,这很容易导致错误。)p另一种方式是平衡。我认为总体效率问题最好通过基准测试来回答,因为这类问题也会受到代码的其他部分(注册压力、缓存使用情况等)的影响,它们以前在内存中,并且加载了“ld1”.干得好!在ccmp
变体中,通过首先加载和测试最重要的对,并在比较失败时完全跳过第二组加载,您可能会节省一些宝贵的内存带宽。我想要恒定的运行时间,不需要依赖数据的内存访问。