【问题标题】:Fast Saturate and shift two Halfwords in ARM asmARM asm中的快速饱和和移位两个半字
【发布时间】:2025-12-18 19:50:02
【问题描述】:

我在一个 32 位字中有两个带符号的 16 位值,我需要将它们右移(除)在常数值(它可以是从 1 到 6)并饱和到字节(0..0xFF) .

例如,

  • 0x FFE1 00AAshift=5 必须变为 0x 0000 0005;
  • 0x 2345 1234 必须变为 0x 00FF 0091

我正在尝试同时使值饱和,类似于以下伪代码:

AND RT, R0, 0x80008000; - mask high bits to get negatives
ORR RT, RT, LSR #1
ORR RT, RT, LSR #2
ORR RT, RT, LSR #4
ORR RT, RT, LSR #8; - now its expanded signs in each halfword
MVN RT, RT
AND R0, RT; now negative values are zero
; here something to saturate high overflow and shift after

但我得到的代码非常丑陋和缓慢。 :) 我现在拥有的最好(最快)的东西是每一半的单独饱和度,如下所示:

MOV RT, R0, LSL #16
MOVS RT, RT, ASR #16+5
MOVMI RT, #0
CMP RT, RT, #256
MOVCS RT, #255
MOVS R0, R0, ASR #16+5
MOVMI R0, #0
CMP R0, R0, #256
MOVCS R0, #255
ORR R0, RT, R0, LSL #16

但它是 10 个周期。 :( 可以更快吗?

p.s.:后来我找到了 USAT16 指令,但它只适用于 ARMv6。而且我需要代码才能在 ARMv5TE 和 ARMv4 上工作。


编辑:现在我重写我的第一个代码:

ANDS RT, 0x10000, R0 << 1;      // 0x10000 is in register. Sign (HI) moves to C flag, Sign (LO) is masked
SUBNE RT, RT, 1;            // Mask LO with 0xFFFF if it's negative
SUBCS RT, RT, 0x10000;      // Mask HI with 0xFFFF if it's negative
BIC R0, R0, RT;         // Negatives are 0 now. The mask can be used as XOR too
TST R0, 0xE0000000;         // check HI overflow             
ORRNE R0, R0, 0x1FE00000        // set HI to 0xFF (shifted) if so
TST R0, 0x0000E000          // check LO overflow             
ORRNE R0, R0, 0x00001FE0        // set LO to 0xFF if so          
AND R0, 0x00FF00FF, R0 >> 5;    // 0x00FF00FF is in register     

但它并不漂亮。

【问题讨论】:

  • 你有没有试过用 C 语言编写它,然后看看编译器产生了什么?
  • 史蒂夫,我不知道如何在没有单独处理的情况下用 C 编写它。但是我的大脑产生了一些想法 :) 其中之一是“XOR 掩码”。如果数字没问题,它必须包含 0(分别在每一半中)。如果 number 为负数,它将包含自己。正溢出将包含数字^0xFFFF。所以结果将是源 ^ 掩码。但不知道如何同时进行

标签: optimization assembly arm bit-manipulation simd


【解决方案1】:

您所拥有的与您将针对上述问题所做的一样好。如果您在一个紧密的循环中对大量数据执行此操作,并且可以负担一些寄存器来保存掩码,那么您可能可以节省一两个循环,但这不会有很大的改进。在 v6 架构之前,ARM 上对这种类型的“小向量”饱和操作没有很好的支持。

基本上,除非这是您程序中唯一的瓶颈,否则是时候将其收起并转移到下一个热点。

【讨论】:

  • 是的,你是对的。我的代码在内部循环中。我优化了一些数据处理,使其不使用字节,而是使用字节对。一切都很好,但是饱和现在消耗了大约 35% 的时间在内循环 :) 这个任务对我来说很有趣,所以我想尝试更多地优化它。我喜欢这里的棘手代码 sn-ps:*.com/questions/121240/saturating-addition-in-c/…*.com/questions/347889/…,也许我们可以从这个任务中创建一些漂亮的代码:)
【解决方案2】:

使用一个检查来设置两个操作的标志是个好主意。但是第二部分我做不到。我可以做点别的:)这是一个通用的变体,可用于从 1 到 6 的任何转换:

;prepare:
MOV RMask, ((0xFF00 << shift) & 0xFF00) << 16;  Mask overflow bits
MOV R_0xFF00FF, 0xFF;
ORR R_0xFF00FF, 0xFF000000;
;...
; innerloop:
;....
TST R0, RMask, R0 << 16;            Set flags for LO half
ORRNE R0, R0, 0xFF << shift;        It is overflow. First try positive
BICMI R0, R0, 0xFF << shift;        Fix it if negative. LO half is ready
TST R0, RMask, R0;              Set flags for HI half. Can TST R0, R0, #Mask also
ORRNE R0, R0, 0xFF << (shift+16)
BICNE R0, R0, 0xFF << (shift+16)
AND R0, R_0xFF00FF, R0 >> shift;        Shift and mask

所以现在是 7 个周期。 :)

还能更好吗?


编辑: 看起来溢出很少见,所以添加这样的内容是个好主意:

TST R0, 0xE000E000
BEQ no_saturation_needed
... ; saturation ops here

【讨论】: