Faster Math.abs
Math.abs
is a commonly-used utility function for taking the absolute value of a Number
. However, there’s no special-case version for taking the absolute value of an int
. Of course Math.abs
will work for int
values, but we can do it faster. Read on for a couple of ways.
The first way to make Math.abs
faster is to do the operation yourself. It’s a really simple function and it’ll work for int
and Number
:
result = input < 0 ? -input : input;
The second way relies on some bitwise magic:
var mask:int = input >> 31; result = (input + mask) ^ mask;
But the compiler doesn’t do as good a job as it could with constant local variables so let’s try a related way that involves double-computing mask
and inlining it:
result = (input + (input >> 31)) ^ (input >> 31);
Now let’s put them to the test with a little performance-testing app:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class FasterAbs extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function FasterAbs() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); init(); } private function init(): void { var beforeTime:int; var afterTime:int; var i:int; var REPS:int = 100000000; var absInt:int; var absNumber:Number; row("Method", "Time"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { absInt = Math.abs(i); } afterTime = getTimer(); row("int = Math.abs(i)", (afterTime-beforeTime), absInt); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { absNumber = Math.abs(i); } afterTime = getTimer(); row("Number = Math.abs(i)", (afterTime-beforeTime), absNumber); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { absInt = i < 0 ? -i : i; } afterTime = getTimer(); row("int = i < 0 ? -i : i", (afterTime-beforeTime), absInt); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { var mask:int = i >> 31; absInt = (i + mask) ^ mask; } afterTime = getTimer(); row("int = (i + mask) ^ mask", (afterTime-beforeTime), absInt); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { absInt = (i + (i >> 31)) ^ (i >> 31); } afterTime = getTimer(); row("int = (i + (i >> 31)) ^ (i >> 31)", (afterTime-beforeTime), absInt); } } }
I ran this test app in the following environment:
- Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 11.3.300.268
- 2.3 Ghz Intel Core i7
- Mac OS X 10.8.0
And here are the results I got:
Method | Time |
---|---|
int = Math.abs(i) | 502 |
Number = Math.abs(i) | 478 |
int = i < 0 ? -i : i | 242 |
int = (i + mask) ^ mask | 194 |
int = (i + (i >> 31)) ^ (i >> 31) | 194 |
We can draw some conclusions from these results:
Math.abs
is better when its resultNumber
doesn’t need to be converted to anint
. Unfortunately, this is always the case when you are getting the absolute value of anint
.- Inlining
Math.abs
with the simple ternary (? :
) operator is about twice as fast asMath.abs
- The version with the bitwise tricks is the fastest of the three approaches and about 20% faster than the ternary operator version.
- Both bitwise trick versions (mask and no mask) are about the same. It’s a shame that the use of a local variable is so expensive that it equals double-computing the mask.
So if you’re looking for a way to improve the speed of your integer absolute values, now you can do even better than doubling the performance of Math.abs
.
Questions? Suggestions? Spot a bug? Post a comment!
#1 by Nisse Bergman on August 13th, 2012 ·
What about using a static field or a private field for the mask instead of local. Is local always the fastest to access? Using const?
#2 by jackson on August 13th, 2012 ·
Local variables are definitely faster. As for
const
, the performance is identical.#3 by AlexG on August 13th, 2012 ·
Thanks! Native abs() could be slower because of using static methods.
#4 by jackson on August 13th, 2012 ·
That’s definitely part of what’s slowing it down, as is the generalized handling of any
Number
. If you put either of the inlined versions from the article in a static method, you’d incur a huge slowdown too. Unfortunately,Math.abs
is built into the Flash Player and there’s no way to inline it for a “fair” comparison.#5 by ben w on August 13th, 2012 ·
nice work!
I had always been using the ol ternary:
absInt = i < 0 ? -i : i;
you could try the following
(I've not tested the validity but I think they should work)
absInt = i < 0 ? ~i + 1 : i;
or
absInt = i > 31)) ^ (i >> 31);
good work though all the same am sure it will come in handy
#6 by jackson on August 13th, 2012 ·
Thanks. :)
The second one’s a bit broken in the comment, so I’ll skip that for now. The first still incurs the branching instruction (a ternary operator) but replaces the negation (
-i
) with some bitwise tricks (~i+1
). So it would come down to this question: which of those is faster? I haven’t tested, but I’m guessing that two operations (~i
then+ 1
) is more expensive than one negation (-i
).#7 by ben w on August 13th, 2012 ·
Whoopsies yeah made a hash of that, I’ll add it in again tomorrow with the actual test code as well
Will try wrapping it in pre tags :D
#8 by Henke37 on August 13th, 2012 ·
Wouldn’t the compiler just precompute the value of the mask in the later case?
#9 by jackson on August 13th, 2012 ·
Since the mask is dependent on the value you’re trying to get the absolute value of, it’s not known at compile-time and the compiler therefore can’t pre-compute it. However, it is a constant in that expression and, as the first version shows, it can be pre-computed before the result is calculated. The second version leaves this up to the very last moment so the only chance we have at pre-computation is to hope that the JIT recognizes this and generates more optimized code. Unfortunately, this doesn’t seem to be happening since the second version (with the double-compute) is as slow as the first version (with the local variable).
#10 by dan on August 26th, 2012 ·
What about this?
Instead to call the Math.abs in every iteration, store the function in a local variable outside of the for-loop.
And call the function via the local variable inside the loop, this should be faster i think.
#11 by jackson on August 26th, 2012 ·
That’s a very temping change to make and one that does eliminate the static class lookup. Unfortunately, it replaces a static class lookup with a call through a
Function
variable and results in a 3-5x slowdown as of last testing.#12 by dan on August 27th, 2012 ·
Thanks for the link, I didn’t know that.
#13 by VirtualMaestro on September 10th, 2012 ·
Hi Jackson,
Today I was really surprised of my test. I made a few tests with different techniques of calculation abs and ran them with FP 11.4 standalone player and plugin in chrome. I don’t know what Adobe’s guys did but native Math.abs in 3x times faster than every of these techniques !!
Can you please confirm this?
Thank you
#14 by jackson on September 10th, 2012 ·
I just re-tested on the same machine as in the article but with 11.4 and got the same results.
#15 by skyboy on September 25th, 2012 ·
There is one slight hiccup, though.
(i + mask) ^ mask
is patented by Sun (though I bet Oracle owns that patent now). However,(i ^ mask) - mask
is not patented and functions the same.It is very unlikely a flash app will end up in a court room over this patent, but it’s still something to be aware of.
Businesses patent the stupidest things.
#16 by jackson on September 25th, 2012 ·
Wow! Thanks for the heads up.
#17 by skyboy on September 25th, 2012 ·
I got that backwards. They’re too similar (and short). Sigh, I suppose programmers shouldn’t dabble with legalities.
#18 by benjamin guihaire on August 7th, 2013 ·
additional Math.abs versions for AS3 including flash-assembly versions here :
http://guihaire.com/code/?p=543