Arm A64霓虹灯SIMD-256位比较

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

我想比较两个小端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位分支相互比较。对于相等的肢体,这将导致-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

    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")
    
    测量的[时间,秒]平均值:11.558,相对标准偏差:0.065%,值:[11.572626,11.560558,11.549322,11.568718,11.558530,11.550490,11.557086,11.551803,11.557529,11.549782]

  • 标准

    这里的赢家。
    ccmp
    指令在这里真的很出色:-) 不过,很明显,问题出在内存限制上

    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;
    
    测量的[时间,秒]平均值:11.146,相对标准偏差:0.034%,值:[11.149754,11.142854,11.146840,11.149392,11.141254,11.148708,11.142293,11.150491,11.139593,11.145873]

  • 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];
    
    编译为

    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%
    
    测量的[时间,秒]平均值:11.531,相对标准偏差:0.040%,值:[11.525511,11.529820,11.541940,11.531776,11.533287,11.526628,11.531392,11.526037,11.531784,11.533786]



小于(在其他地方,
UInt256
是如何使用的,也就是说,值是否更可能事先存在于SIMD寄存器、通用寄存器或内存中?我可以想象
cmp
和3
ccmp
s的开销可能比一堆SIMD寄存器杂耍要小,但必须溢出一堆GP寄存器并加载值,这很容易导致错误。)p另一种方式是平衡。我认为总体效率问题最好通过基准测试来回答,因为这类问题也会受到代码的其他部分(注册压力、缓存使用情况等)的影响,它们以前在内存中,并且加载了“ld1”.干得好!在
ccmp
变体中,通过首先加载和测试最重要的对,并在比较失败时完全跳过第二组加载,您可能会节省一些宝贵的内存带宽。我想要恒定的运行时间,不需要依赖数据的内存访问。