Alternatives to missing SSAT and USAT instructions in 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
|
show 4 more comments
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 '18 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 '18 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 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 '18 at 15:12
|
show 4 more comments
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 '18 at 10:46
itaychitaych
143111
143111
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 '18 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 '18 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 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 '18 at 15:12
|
show 4 more comments
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 '18 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 '18 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 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 '18 at 15:12
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 '18 at 13:19
Write their equivalent in C and let the compiler translate that code?
– Ira Baxter
Nov 13 '18 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 '18 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 '18 at 13:53
1
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 at 14:57
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 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 '18 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 '18 at 15:12
|
show 4 more comments
1 Answer
1
active
oldest
votes
--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 '18 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 '18 at 13:53
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
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
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
--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 '18 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 '18 at 13:53
add a comment |
--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 '18 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 '18 at 13:53
add a comment |
--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 '18 at 17:14
answered Nov 14 '18 at 9:19
efratefrat
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 '18 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 '18 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 '18 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 '18 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 '18 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 '18 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 '18 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 '18 at 13:53
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
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 '18 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 '18 at 13:53
1
See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792
– Ira Baxter
Nov 13 '18 at 14:31
@itaych - why can't you(r compiler) use conditional instructions instead of branching?
– Toby Speight
Nov 13 '18 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 '18 at 15:12