assembly Arm64中缺少SSAT和USAT指令的替代方案?

fcg9iug3  于 2022-11-24  发布在  其他
关注(0)|答案(2)|浏览(274)

我们正在将一个主要应用程序从Arm 32移植到Arm 64。我们的算法经常使用SSATUSAT指令。它们执行任意大小的左移或右移,然后执行任意位数的有符号或无符号饱和。这对图像处理算法非常有用,因为我们可以执行一些数学运算,生成32位整数结果。然后用一条指令从其中获取我们需要的任何位(饱和到输出图像的位深度的最大/最小值)。
这些指令在Arm 64中莫名其妙地消失了,我们找到的最接近的替代指令是SQSHRN/UQSHRN/SQSHLN/UQSHLN,它们执行移位和饱和,但在饱和方面受到更多限制(USAT可以饱和到任何宽度,甚至7位;新指令只能饱和到输入宽度的一半,例如在32位输入的情况下为16位,这将需要附加处理来实现所需结果)。
有人能解释一下为什么这些指令被删除,以及有效地移植使用它们的现有代码的最佳方法是什么?

3j86kqsm

3j86kqsm1#

SIMD解决方案

我们先来看看签名版。
首先,ARM 64具有饱和移位的非窄化版本,其在全字长时饱和:可以通过左移在不同的位计数处饱和,使得期望的最高位变成字的最高位;移位计数也可以被偏移以合并你想要的初始左移位。然后右移位以返回。
例如,ssat r0, #14, r1, lsl #3的等价物可以通过下式得到:

sqshl   v0.4s, v1.4s, #(32 - 14 + 3)
sshr    v0.4s, v0.4s, #(32 - 14)

这是两条指令,而不是一条,但另一方面,你一次可以得到四个结果。在Cortex A-72上,这个序列应该有7个周期的延迟和每2个周期一个的吞吐量。(相比之下,在ARM 32模式下的同一芯片上,单个ssat具有2个周期的延迟和每个周期一个的吞吐量,因此如果我的计算正确,其中四个是5个周期的延迟和每4个周期一个的吞吐量)
在使用内部函数的C语言中,我们会有

int32x4_t ssat4(int32x4_t x) {
    return vshrq_n_s32(vqshlq_n_s32(x, (32 - SATBITS + SHIFT)), 32 - SATBITS);
}

根据输入的大小,这些指令有常见的8x 16、16 x8、32 x4、64 x2版本。如果移位和饱和不是编译时常量,它们还可以在寄存器中而不是立即数中获取移位计数(额外费用:ARM 32 ssat根本无法做到这一点。
对于usat的等价物,使用sqshlu,并将其逻辑右移ushr。这复制了一个有点奇怪的行为,即尽管名称为“unsigned”,但usat实际上将一个 signed 值饱和到0..INT_MAX范围内,所有负输入都Map到0。如果需要真无符号饱和(0..UINT_MAX),请使用uqshl。(尽管实际上在这种情况下,shl / umin可能更快。)
为了饱和到其他任意范围(不一定是2的幂),我们有smax/sminumax/umin,但这样的移位是分开的。

ccgok5k5

ccgok5k52#

  • -UPDATE--使用非汇编代码时,正确测试的时间明显变慢,我将继续寻找不同的方法
    我比较了这个汇编代码:
#define __arm_ssat(src, bits)   asm("ssat %[srcr], %[satv], %[srcr]"    :[srcr]"+r"(src):[satv]"I"(bits));

与此:

#define MAX_SIGNED_NUM(bits) ((1 << (bits -1)) -1)
#define __arm_ssat(src, bits)   {src = ((src > MAX_SIGNED_NUM(bits)) ? MAX_SIGNED_NUM(bits) : src);}

在32位设备上运行此--更新测试--时:

volatile  void assert_ssat_asm(int* buf, size_t loops){
    int64_t num = buf[0];
    int64_t num_a = buf[1];
    int64_t num_b = buf[2];
    int sum = 0;
    struct timeval tmv1; gettimeofday(&tmv1,NULL);
    for (int i = 0; i < loops; ++i){
        __arm_ssat(num, 8);
        sum+=num;
        assert( 127 == num);
        num = buf[0];

        __arm_ssat(num, 16);
        sum+=num;
        assert(32767 == num);

        __arm_ssat(num_a, 8);
        sum+=num;
        assert( 127 == num_a);
        num_a = buf[1];

        __arm_ssat(num_a, 16);
        sum+=num;
        assert( 690 == num_a);

        __arm_ssat(num_b, 8);
        sum+=num;
        assert( 127 == num_b);
        num_b = buf[2];

        __arm_ssat(num_b, 16);
        sum+=num;
        assert( 32767 == num_b);
    }
    struct timeval tmv2; gettimeofday(&tmv2,NULL);
    int tdiff_usec = (tmv2.tv_sec*1000000 + tmv2.tv_usec) - (tmv1.tv_sec*1000000 + tmv1.tv_usec);

    printf("%d\n", sum);
    printf("ran %d times, total time: %d,  average time asm: %.7f\n", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
    int buf[] = { 69000, 690, 64000 };
    test_ssat(buf, 1000000);
}

我得到了这些结果:
运行1000000个循环,平均时间reg:0.0210270
运行1000000个循环,平均组装时间:0.0057960

相关问题