Even Faster isNaN()
I wrote an article last November showing how to make your isNaN()
calls 12x faster. Today, thanks to a tip from the comments on that article, I’ll show you how to make your isNaN()
calls even even faster! (UPDATE: see the definitive article on isNaN for much more!)
The comment I’m referring to goes one step further than my approach last time by recognizing that a Number
that is NaN
is not even equal to itself and, therefore, the isNaN
test can be even further reduced to this:
private function myNewIsNaN(val:Number): Boolean { return val != val; }
The author, skyboy, claims it’s about twice as fast as my version but admits that he hasn’t tested on Flash Player 10.1. Let’s test this claim with Flash Player 10.1 with an extension to the test app from last time:
package { import flash.display.*; import flash.utils.*; import flash.text.*; /** * An app to test improved versions of isNaN() * @author Jackson Dunstan */ public class FastIsNaNTest extends Sprite { public function FastIsNaNTest() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); function log(msg:*): void { logger.appendText(msg + "\n"); } const NUM_ITERATIONS:int = 10000000; var i:int; var beforeTime:int; var local33:Number = 33; var localNaN:Number = NaN; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { isNaN(local33); } log("isNaN(33): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { isNaN(localNaN); } log("isNaN(NaN): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myIsNaN(local33); } log("myIsNaN(33): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myIsNaN(localNaN); } log("myIsNaN(NaN): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { !(local33 <= 0) && !(local33 > 0) } log("inline for 33: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { !(localNaN <= 0) && !(localNaN > 0) } log("inline for NaN: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myNewIsNaN(local33); } log("myNewIsNaN(33): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { myNewIsNaN(localNaN); } log("myNewIsNaN(NaN): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { local33 != local33; } log("new inline for 33: " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) { localNaN != localNaN; } log("new inline for NaN: " + (getTimer()-beforeTime)); } private function myIsNaN(val:Number): Boolean { return !(val <= 0) && !(val > 0); } private function myNewIsNaN(val:Number): Boolean { return val != val; } } }
What’s new in the above is the addition of myNewIsNaN
and the inline version of it. So, let’s get to the performance results so we can compare the two versions:
Environment | val = 33 | val = NaN | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
isNaN | myIsNaN (old) | inline (old) | myIsNaN (new) | inline (new) | isNaN | myIsNaN (old) | inline (old) | myIsNaN (new) | inline (new) | |
3.0 Ghz Intel Core 2 Duo, Windows XP | 251 | 122 | 28 | 91 | 23 | 244 | 122 | 28 | 209 | 23 |
2.0 Ghz Intel Core 2 Duo, Mac OS X | 329 | 136 | 43 | 117 | 35 | 321 | 137 | 44 | 111 | 34 |
As you can see, the new approach supplied by skyboy is indeed faster than the original approach. However, it does not appear to be twice as fast but rather more like 20% faster in inline form. Still, the speedup is real and much appreciated so it should be used in place of the original approach. As an added benefit, it is more compact and perhaps more straightforward to comprehend. I would still recommend a comment:
val != val; // inline isNaN()
Finally, the bug I filed with Adobe regarding this inefficiency has been marked as “resolved”, so we should see some speedup without this inlining in a future version of Flash Player.
#1 by Ramon Fritsch on September 13th, 2010 ·
That’s a good point to take this as a bug then adobe will be able to fix that slow s*. :/
#2 by JabbyPanda on September 13th, 2010 ·
Actually, the bug issue you’ve reported is just marked as “Resolved Externally”, which means “let the folks who are working at Tamarin VM at Mozilla fix it”…. and it is not done yet.
https://bugzilla.mozilla.org/show_bug.cgi?id=416287
#3 by jackson on September 13th, 2010 ·
Ah, I didn’t catch the external resolution part. Thanks for pointing this out.
#4 by Jonathan Toland on September 13th, 2010 ·
Same status for Vector#map() last time I used it :(
#5 by nook on December 8th, 2010 ·
Nice!
Just a short note.
Most often you don’t want to know if a value is NaN, rather if the value is valid, i.e. NOT a NaN. (!isNaN(val))
And since the check for true is somewhat quicker than the check for false a better recommendation for a isNaN substitute would be to use:
if (val == val)
{
// val is set
}
#6 by skyboy on December 19th, 2010 ·
I just found another test that also lets you treat
Infinity
and-Infinity
asNaN
:(n * 0) != 0
My tests show that it’s just as fast, plus or minus 50ms at 1,000,000,000 tests.
#7 by jackson on December 19th, 2010 ·
That could come in really handy and, for a 0.3% slowdown, isn’t much of a hit. Look for tomorrow’s article, yet again on this topic. :)
#8 by skyboy on December 20th, 2010 ·
Although, thinking about it, it would a replacement for isFinite, rather than isNaN; Simply using == instead.
#9 by Glidias on January 24th, 2011 ·
Thank you very much. Somehow, I couldn’t get Haxe’s Math.isNaN() function to work at runtime for SWC library export, so this works really well (and can be inlined too!).