Alternatives to missing SSAT and USAT instructions in Arm64?
up vote
7
down vote
favorite
We are in the process of porting a major application from Arm32 to Arm64. Our algorithms make frequent use of the SSAT and USAT instructions. They are extremely useful, performing a left or right shift of any size then a signed or unsigned saturate to an arbitrary number of bits. This is extremely useful for image processing algorithms, because we can perform some math that generates a 32-bit integer result, then grab whatever bits we need out of that (saturated to the max/min of the bit depth of the output image) with a single instruction.
These instructions have inexplicably disappeared in Arm64 and the closest alternative we've found are SQSHRN / UQSHRN / SQSHLN / UQSHLN which perform a shift and saturate but are far more limited in the saturation they perform (USAT could saturate to any width, even 7 bits; the new instructions can only saturate to half the width of the input, e.g. 16 bits in case of a 32 bit input, which would require additional processing to achieve the needed result).
Can someone explain why these instructions were dropped, and what's the best way to efficiently port existing code that uses them?
assembly arm arm64
|
show 4 more comments
up vote
7
down vote
favorite
We are in the process of porting a major application from Arm32 to Arm64. Our algorithms make frequent use of the SSAT and USAT instructions. They are extremely useful, performing a left or right shift of any size then a signed or unsigned saturate to an arbitrary number of bits. This is extremely useful for image processing algorithms, because we can perform some math that generates a 32-bit integer result, then grab whatever bits we need out of that (saturated to the max/min of the bit depth of the output image) with a single instruction.
These instructions have inexplicably disappeared in Arm64 and the closest alternative we've found are SQSHRN / UQSHRN / SQSHLN / UQSHLN which perform a shift and saturate but are far more limited in the saturation they perform (USAT could saturate to any width, even 7 bits; the new instructions can only saturate to half the width of the input, e.g. 16 bits in case of a 32 bit input, which would require additional processing to achieve the needed result).
Can someone explain why these instructions were dropped, and what's the best way to efficiently port existing code that uses them?
assembly arm arm64
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12
|
show 4 more comments
up vote
7
down vote
favorite
up vote
7
down vote
favorite
We are in the process of porting a major application from Arm32 to Arm64. Our algorithms make frequent use of the SSAT and USAT instructions. They are extremely useful, performing a left or right shift of any size then a signed or unsigned saturate to an arbitrary number of bits. This is extremely useful for image processing algorithms, because we can perform some math that generates a 32-bit integer result, then grab whatever bits we need out of that (saturated to the max/min of the bit depth of the output image) with a single instruction.
These instructions have inexplicably disappeared in Arm64 and the closest alternative we've found are SQSHRN / UQSHRN / SQSHLN / UQSHLN which perform a shift and saturate but are far more limited in the saturation they perform (USAT could saturate to any width, even 7 bits; the new instructions can only saturate to half the width of the input, e.g. 16 bits in case of a 32 bit input, which would require additional processing to achieve the needed result).
Can someone explain why these instructions were dropped, and what's the best way to efficiently port existing code that uses them?
assembly arm arm64
We are in the process of porting a major application from Arm32 to Arm64. Our algorithms make frequent use of the SSAT and USAT instructions. They are extremely useful, performing a left or right shift of any size then a signed or unsigned saturate to an arbitrary number of bits. This is extremely useful for image processing algorithms, because we can perform some math that generates a 32-bit integer result, then grab whatever bits we need out of that (saturated to the max/min of the bit depth of the output image) with a single instruction.
These instructions have inexplicably disappeared in Arm64 and the closest alternative we've found are SQSHRN / UQSHRN / SQSHLN / UQSHLN which perform a shift and saturate but are far more limited in the saturation they perform (USAT could saturate to any width, even 7 bits; the new instructions can only saturate to half the width of the input, e.g. 16 bits in case of a 32 bit input, which would require additional processing to achieve the needed result).
Can someone explain why these instructions were dropped, and what's the best way to efficiently port existing code that uses them?
assembly arm arm64
assembly arm arm64
asked Nov 13 at 10:46
itaych
11811
11811
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12
|
show 4 more comments
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
1
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12
|
show 4 more comments
1 Answer
1
active
oldest
votes
up vote
1
down vote
--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method
i compared this assembly code:
#define __arm_ssat(src, bits) asm("ssat %[srcr], %[satv], %[srcr]" :[srcr]"+r"(src):[satv]"I"(bits));
with this:
#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);}
when running this --UPDATED TEST-- on 32bit device:
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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
int buf = { 69000, 690, 64000 };
test_ssat(buf, 1000000);
}
I've got those results:
run 1000000 loops, average time reg: 0.0210270
run 1000000 loops, average time assembly: 0.0057960
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method
i compared this assembly code:
#define __arm_ssat(src, bits) asm("ssat %[srcr], %[satv], %[srcr]" :[srcr]"+r"(src):[satv]"I"(bits));
with this:
#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);}
when running this --UPDATED TEST-- on 32bit device:
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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
int buf = { 69000, 690, 64000 };
test_ssat(buf, 1000000);
}
I've got those results:
run 1000000 loops, average time reg: 0.0210270
run 1000000 loops, average time assembly: 0.0057960
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
add a comment |
up vote
1
down vote
--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method
i compared this assembly code:
#define __arm_ssat(src, bits) asm("ssat %[srcr], %[satv], %[srcr]" :[srcr]"+r"(src):[satv]"I"(bits));
with this:
#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);}
when running this --UPDATED TEST-- on 32bit device:
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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
int buf = { 69000, 690, 64000 };
test_ssat(buf, 1000000);
}
I've got those results:
run 1000000 loops, average time reg: 0.0210270
run 1000000 loops, average time assembly: 0.0057960
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
add a comment |
up vote
1
down vote
up vote
1
down vote
--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method
i compared this assembly code:
#define __arm_ssat(src, bits) asm("ssat %[srcr], %[satv], %[srcr]" :[srcr]"+r"(src):[satv]"I"(bits));
with this:
#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);}
when running this --UPDATED TEST-- on 32bit device:
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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
int buf = { 69000, 690, 64000 };
test_ssat(buf, 1000000);
}
I've got those results:
run 1000000 loops, average time reg: 0.0210270
run 1000000 loops, average time assembly: 0.0057960
--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method
i compared this assembly code:
#define __arm_ssat(src, bits) asm("ssat %[srcr], %[satv], %[srcr]" :[srcr]"+r"(src):[satv]"I"(bits));
with this:
#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);}
when running this --UPDATED TEST-- on 32bit device:
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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
int buf = { 69000, 690, 64000 };
test_ssat(buf, 1000000);
}
I've got those results:
run 1000000 loops, average time reg: 0.0210270
run 1000000 loops, average time assembly: 0.0057960
edited Nov 14 at 17:14
answered Nov 14 at 9:19
efrat
114
114
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
add a comment |
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.
– Peter Cordes
Nov 14 at 11:59
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.
– efrat
Nov 14 at 13:53
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53279286%2falternatives-to-missing-ssat-and-usat-instructions-in-arm64%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 at 13:19
@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.
– itaych
Nov 13 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 at 14:57
@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?
– Jester
Nov 13 at 15:12