ARM asm での 2 つのハーフワードの高速飽和とシフト
-
18-09-2019 - |
質問
32 ビット ワード内に 2 つの符号付き 16 ビット値があり、それらを定数値 (1 から 6 まで) で右にシフト (除算) し、バイト (0..0xFF) に飽和させる必要があります。
例えば、
- 0x FFE1 00AA と シフト=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サイクルです。:( もっと速くできるでしょうか?
追記:後でこれ用の 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
しかしそれは美しくありません。
解決 2
1 つのチェックを使用して 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
他のヒント
あなたが持っているものは、述べられているように、問題に対してやろうとしていることとほぼ同じくらい良いです。タイトなループで大量のデータに対してこれを実行しており、マスクを保持するためのいくつかのレジスタに余裕がある場合は、1 ~ 2 サイクルを節約できるかもしれませんが、大きな改善にはなりません。v6 アーキテクチャ以前の ARM では、このタイプの「小さいベクトル」飽和操作はあまりサポートされていません。
基本的に、これがプログラムの唯一のボトルネックでない限り、これを片付けて次のホットスポットに移ります。