4 ms·
I decided to try writing the same thing in JavaScript and discovered something really strange. My first idea was: numbers.map(function(x){return String.fr
by nene 16y ago
I decided to try writing the same thing in JavaScript and discovered something really strange.
My first idea was:
numbers.map(function(x){return String.fromCharCode(x);}).join("");
This was pretty fast already, but why not eliminate the anonymous function completely and pass String.fromCharCode directly to map():
numbers.map(String.fromCharCode).join("");
I timed it and... ...this was ~100 times slower than the previous version. WTF!
Somehow passing this native function directly to Array.map() is way slower than wrapping it inside another function and passing that to Array.map().
I have tested it so far in Chrome, Firefox and Opera - the results are the same. I also tried forEach(), which behaves similarly.
Does anybody have an idea why this is so?
Update: I tried the same with Math.round and Math.sin, and with these the results were as one would expect: passing the function to Array.map() directly was a little bit faster than using intermediate anonymous function. So it seems the problem is with String.fromCharCode specifically.
- lysium 16y agoJust a guess: your anonymous function cannot be redefined (because there is no name), but String.fromCharCode could potentially be. Thus, a similar reason as mentioned in the article for global vs. local variables. One would think that String.fromCharCode is looked up only once, though.
- nene 16y agoThat's exactly what I was thinking: * In the first version each time the anonymous function is executed interpreter has to lookup String and from it the fromCharCode method => 2n lookups. * In the second version the String and fromCharCode have to be lookud up only once => 2 lookups. Therefore according to slow-lookups-theory the first version should be slower. Except when I measure it, the opposite turns out to be true.
- pornel 16y agoActually it's the opposite. When you pass String.fromCharCode directly, then you're passing reference (not name) to that particular implementation, and it can't change. When you pass anonymous function, then every execution of that function needs to look up `String.fromCharCode` (anonymous functions save scope, not references). I'm surprised by the benchmark as well. I suspect it may be because calls to native functions are handled differently from calls to JS functions, and JS engine is able to optimize call inside anonymous function (create trace/JIT and inline it), but not when calling by reference (and perhaps keeps calling it by some expensive proxy object).
- dish 16y agoThat kind of should be the first train of thought in python too, why using "+" to add strings!? it maybe fixed in py3k but its still not worth it in py2.X <pre> return ''.join(map(chr, list)) </pre> Also the liberal use of the words list and string bothers me.
- dish 16y agoSince Guido invented the words "list" and "string" in python at least, I guess him using them doesn't bother me.
- nene 16y agoFound an answer. The problem is that String.fromCharCode takes multiple arguments and Array.map also passes multiple arguments to the callback, therefore the equivalent of numbers.map(String.fromCharCode) is actually: numbers.map(function(x, y, z){return String.fromCharCode(x, y, z);}) Which of course is slower as the end result will actually be array with longer strings in it.