My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label callee. Show all posts
Showing posts with label callee. Show all posts

Saturday, February 13, 2010

arguments, callee, call, and apply performances

We have dozens of best practices to improve performances. We also have common practices to accomplish daily tasks. This post is about the most used JavaScript ArrayLike Object, aka arguments, and its performances impact over basic tasks.

Why arguments

While it's natural for JavaScripters to use such "magic" variable, as arguments is, in many other languages everybody knows it does not come for free and it is rarely used.

<?php
function myFunc() {
// function call for each execution
// rarely seen in good PHP scripts
$arguments = func_get_args();
}
?>

One clear advantage in PHP, Python, and many others, is the possibility to define a default value for each argument.

<?php

class UserManager extends MyDAL {
public function exists($user='unknown', $pass='') {
return $this->fetch('SELECT 1 FROM table WHERE user=? AND pass=?', $user, $pass);
}
}
?>

This approach may brings automatically developers to code as if arguments does not exist. It's not an important value, everything has a default ... so, why bother?

Why callee

Specially because of black sheep Internet Explorer and its inconsistent/bugged (it does not exist, imho) concept of named function expression, our laziness frequently bring us to use this "shortcut" to refer the current executed function.

// the classic case ..
setTimeout(function
/*
if named, IE will pollute the current scope
with chose name rather than let it "be" only
inside the function body, as is for every other browser
*/
() {
// do stuff
setTimeout(arguments.callee, 1000);
}, 1000);

Even if we are in a closure so that no risk will occur if we name the callback, we are still lazy and we don't even think about something like:

setTimeout(function $_fn1() {
// do stuff
setTimeout($_fn1, 1000);
}, 1000);

return function $_fn2() {
// if stuff $_fn2() again;
};

Of course, how many $_fn1, $_fn2, $_fn3 we can possibly have in the current scope?
Somebody could even think about silly solutions such:

Function.prototype.withCallee = (function ($) {
// WebReflection Silly Ideas - Mit Style
var toString = $.prototype.toString;
return function withCallee() {
return $("var callee=" + toString.call(this) + ";return callee")();
};
})(Function);

// runtime factorial
alert(function(n){
return n * (1 < n ? callee(--n) : 1);
}.withCallee()(5)); // 120

// other example
setTimeout(function () {
// bit slower creation ... but
// much faster execution for each successive call
if (animationStuff) {
setTimeout(callee, 15);
}
}.withCallee(), 15);


OK, agreed that 2 functions rather than one for each function that would like to use callee could require a bit more memory consumption ... but hey, we are talking about performances, right?

Why call and apply

Well, call and apply are one of the best JavaScript part ever. Everything can be injected into another scope, referenced via this, and while Python, as example, has a clear self as first argument, we, as JavaScripters, don't even think about such solution: we've got call and apply, who needs to optimize a this?
Well, somehow this always remind us that we are dealing with an instance, an object, rather than a primitive or whatever value sent as argument.
This means that even where it is possible to avoid it, we feel cooler using such mechanism:

function A(){};
A.prototype = (function () {
// our private closure to have private methods
function _doStuff() {
this.stuff = "done";
}
return {
constructor:A,
doStuff:function () {
_doStuff.call(this);

// it could have been a simple
_doStuff(this);
// if _doStuff was accepting a self argument
}
};
})();

Furthermore, apply is able to combine both worlds, via lazy arguments discovery, and context injection ... how cool it is ...

The Benchmark

Since we have all these approaches to solve our daily tasks, and since these cannot come for free, I have decided to create a truly simple bench, hopefully compatible with a wide range of browsers. There is nothing there, except lots of executions, defined by times parameter in the query string, and a simple table to compare runtime these results.

Interesting Results

The scenario is apparently totally inconsistent across all major browsers, and this is my personal summary, you can deduct your one as well:
  • in IE call and apply are up to 1.5X slower while as soon as arguments is discovered, we have up to 4X performances gap. There is no consistent difference if we discover callee, since it seems to be attached directly into arguments object.
  • in Chrome call is slower than apply only if there are no arguments sent, otherwise call is 4X faster than apply and, apparently, even faster than a direct call. arguments costs generally up to 2.5X while once discovered, callee seems to come for free giving sometimes similar direct call results.
  • in Firefox things are completely different again. Direct call, as call and apply, do not differ that much but as soon as we try to discover arguments.callee, for one of the first browser that got named function expression right, the execution speed is up to 9X slower.
  • Opera seems to be the most linear one. Direct call is faster than call, and call is faster than callee. To discover arguments we slow down up to 2X while callee does not mean much more.
  • In Safari we have again a linear increment, but callee costs more than Opera and others, surely not that much as is for Firefox


Summarized Results

A direct call is faster, cross browser speaking, and specially for those shared functions without arguments, we could avoid usage of call or apply, a self reference as argument is more than enough.
arguments object should be forbidden, if we talk about extreme performances optimizations. This is the only real constant in the whole bench, as soon as it is present, it
slows down every single function call and most of the time
consistently.

HINTS about arguments

To understand if an argument has not been sent, we can always use this check ignoring JSLint warnings about it:

function A(a, b, c) {
if (c == null) {
// c can be ONLY undefined OR null
}
}

If we compare whatever value with == null, rather than === null, we can be sure this value is null or undefined.
Since generally speaking undefined is not an interesting value and null is used instead, also because undefined is a variable and it costs to compare something against it and it could be redefined as well while null cannot, it does not make sense at all to do tedious checks like this:

function A(a, b, c) {
// JSLint way ...
if (c === undefined || c === null) {
// bye bye performances
// bye bye security, undefined can be reassigned
// hello redundant code, == null does exactly the same
// check in a more secure way since it does not matter
// if undefined has been redefined
}
}

Do we agree? That warning in JSLint is one of the most annoying one, at least this is my opinion.
Let's move forward.
If we would like to know arguments length we have different strategies:

function Ajax(method, uri, async, user, pass) {
if (user == null) {
// we know pass won't be there as well
// received probably 3 arguments
// if user is not null, we expect 5 arguments
// and we use all of them
}
if (async == null) {
// this is a sync call
// received 2 arguments
}
}

function count(a, b, c, d) {
// not null, we consider it as a valid value
var argsLength = (a != null) + (b != null) + (c != null) + (d != null);
alert(argsLength);
// rather than alert(arguments.length);
}

count();
count(1);
count(1, 2);
count(1, 2, 3);
count(1, 2, 3, 4);

About latest suggestion please consider that only Chrome is slower, but Chrome is already the fastest browser so far while in IE, as example, arguments.length rather than null checks costs up to 6X the time.
Every other browser will have better performances than arguments.length, then we need to test case after case since a function, as String.fromCharCode could be, cannot obviously use such strategy due to "infinite" accepted arguments.
In these cases, e.g. runtime push or similar methods, we don't have many options ... but these should be exceptions, not the common approach, as is for many other programming languages with some arguments support.

Conclusion

I do not pretend to change developers code style with a single post and things are definitively not that easy to normalize for each browser.
Unfortunately, we cannot even think about features detection when we talk about performances, we don't want 1 second delay to test all performances cases before we can decide which strategy will speed up more, do we?
At least we are now better aware about how much these common JavaScript practices could slow down our code on daily basis and, when performances do matter, we have basis to avoid some micro bottleneck.

Wednesday, June 24, 2009

ES5 arguments and callee, I was wrong!

JavaScript is not JavaScript, I am not crazy, it is just a consideration between the language itself and what is behind it: another programming language with lower level rules and logic ... sounds silly and obvious, but please keep reading to understand what I mean.

No Results Yet, But I've Already Lost My Battle

I spread comments, I wrote post after posts to defend ECMAScript 5 arguments.callee decision with "use strict", but I have to admit I have never investigate the internal behavior of callee, an arguments property which is not what we think is ...

Discovering In Core The Callee Property

What I was thinking was something hilarious for a C or C++ programmer: an inherited property for a mutable instance.

// JavaScript should have a "secret" Arguments class
// and for each function, something like this
function Test(){};

// we have declared the function Test
// internally there should be a secret operation like this:

Test._createArguments = function(args){
for(var i = 0; i < args.length; i++)
this[i] = args[i];
};
Test._createArguments.prototype.callee = Test;


// and for each Test call, there should be
// a secret operation like this one:
Test._injectArguments(
new Test._createArguments(args);
);

// in order to retrieve Test arguments
// variable for each call ... inheriting
// automatically the callee property

Apparently, except for Internet Explorer, I was so wrong.
arguments.callee is not a pointer or a static variable retrieved instantly as {callee:"it's a me!"}.callee could be, arguments, plus callee is a property with a "discover latency" able to make each call execution up to 100 times slower than a regular function!

We Want Perfomance? Get Rid Of Arguments And Callee Then!

Even with most advanced JavaScript Engines, like the V8 one used by Google Chrome Browser, arguments and arguments.callee are a big bottleneck. Test by yourself!

(function(){
// Array to store execution time
var execution = [];

// named function
function factorial(i){
return i < 2 ? i : i * factorial(i - 1);
};
for(var i = 0, t = new Date; i < 1000; ++i)
factorial(100);
t = new Date - t;
execution.push(
"Native named function: " + t
);

// named function with arguments
function fact(i){
var i = arguments[0];
return i < 2 ? i : i * fact(i - 1);
};
for(var i = 0, t = new Date; i < 1000; ++i)
fact(100);
t = new Date - t;
execution.push(
"Native named with arguments: " + t
);

// arguments.callee
fact1 = function(i){
return i < 2 ? i : i * arguments.callee(i - 1);
};
for(var i = 0, t = new Date; i < 1000; ++i)
fact1(100);
t = new Date - t;
execution.push(
"arguments.callee: " + t
);

alert(execution.join("\n"));
})();

Impressive is the responsiveness of Internet Explorer, the browser which is "die hard" and the only one I was trying to defend about this decision, due to named functions misbehavior. IE is about 100 times slower when arguments variable is used, while Firefox 3 for example is about 50 time slower when arguments.callee is discovered. Chrome is about 4 times slower with both arguments, and callee, and the same is for Safari and Opera, impressive results. A little note about Chrome, apparently the main problem is the scope resolution, rather than arguments or callee.

We Still Love Scripting: A Callee Proposal

During these tests, I can proudly say I found a way to understand in which era we are, thanks to this simple snippet:

// WebReflection knows if we are in ES5 era!
navigator.ES5 = (function(){"use strict";try{return !arguments.callee}catch(e){return true}})();

Avoiding conflicts via global navigator object, ES5 will tell us if the browser is compatible with ECMAScript 5 "use strict"; rule or not, allowing our code to behave differently when necessary.
It does not really matter in any case, to use my callee proposal we need to inject a callee property, as I suggested in the latest post, and for the sake of good gode, via eval:

// Another WebReflection Silly Idea
function F/*or whatever name you prefer*/(callee){
return eval("(callee="+callee+")");
};

Above function is able to take a user defined function and to inject a callee property creating another defined function, a sort of operation usually performed once and never again. In few words, inside our function body we will find another variable with local scope: callee

(function(){
"use strict"; // or not

var execution = [];

// create the function via F
factorial = F(function(i){
// we got arguments, and callee
return i < 2 ? i : i * callee(i - 1);
});
for(var i = 0, t = new Date; i < 1000; ++i)
factorial(100);
t = new Date - t;
execution.push(
"WebReflection F: " + t
);

alert(execution.join("\n"));
})();

This strategy is particularly useful for these cases:

  • classic configuration object, new Ext.Panel({listeners:render:F(function(Panel){
    Panel.removeListener("render", callee);
    })})

  • classic pre-compiled function, var circle = F(Function("r,i","return !i ? r*callee(r," + Math.PI + ") : r*i"));

  • every time we would like to retrieve arguments.callee


Last point is the one that should let us think about what we have done so far.
If we try to execute latest benchmark, we will realize that specially with Internet Explorer, the evaluation trap works faster than arguments.callee so every time we need callee, but we do not need arguments, we should go for it.

Deeper Investigation

I'll keep digging inside this stuff but right now the only thing I can say is that I will try to avoid the usage of arguments.callee every single time I can because specially for the already JS speaking slow browser Internet Explorer, it is a performances killer I could not even imagine.

I hope I gave you more knowledge about this problem and why ECMAScript group decided to get rid of callee, and in a possible future get rid of arguments as well. One side is pro scriptish stuff, and we all love it, bot the other side is about how much this stuff could slow down Web possibilities. Stay tuned!

P.S. amazing, in FF and Chrome you can set whatever property into the navigator object and rather than transform it into a string a la window.name, you can safe whatever amount of data without loosing it until you do not close that tab!!! Stay tuned for this as well, it is a new security or hack problem we could use for better purposes ;)

Saturday, May 23, 2009

[ECMAScript 5] Do Not Remove arguments.callee !

Being subscribed in dunno how many developers mailing list, I completely forgot to follow up the arguments and arguments.callee discussion.
Accordingly to this John post, is with extreme surprise that I am discovering how critical is the gap between programming language developers and programming languages active users.

Even if I read more than once piece of SpiderMonkey or Google Chrome, I am part of programming languages users and I can tell you for sure that the decision to remove arguments.callee from the language will be a complete mess.

The Main Aim Of ECMAScript 5: Do Not Break What We Have So Far

Unfortunately, Internet Explorer will be broken, because named function persists in the scope, causing naming conflicts everywhere!


setTimeout(function f(){
alert(window.f);
}, 1000);

Above code will alert the function f in every Internet Explorer. Moreover, as you can read in one comme of John's entry, Internet Explorer has function interpretation "problems", declaring or overriding already declared function even if these are in an else case, while other browsers do not even consider that part of code if it will never be executed.

Another Attemp To Slow Down The Already Slow Browser


Reading this long thread about the discussion, some clever guy suggested a simple solution to avoid naming conflicts ... a closure

(function(){
setTimeout(function f(){
alert(window.f);
}, 1000);
})();

This simply means that our code size will drastically increase without a valid reason and number of created scopes and functions will be twice as was before (already too big). They think it will not slow down performances but they are forgetting old browsers that will be still used when all this wonderful shiny JavaScript will be released: Internet Explorer 7, and Internet Explorer 8 (assuming Internet Explorer 6 does not exist anymore ... would be nice to start to fight against the version 7 and then 8 ...).

Introspection and BackTrace? They Decided It Is A Bad Thing!


Without arguments.callee we are loosing arguments.callee.caller as well, often the only way we have to truly debug our code. At the same time the dynamic nature of JavaScript where "you can inject whatever wherever" will loose a big portion of its malleability.

Everything For Performances Reason ???


Apparently what this "new" language would like to get rid off is arguments variable, probably the most used variable ever, the inspiration of every ArrayLike object or library. This would be for performances, where we need as first step to call 2 functions rather than one for each runtime function assignment (setTimeout example and every assigned inline lambda) and where everybody will try to overload the language itself providing other methods to emulate the good old stuff!

Let's Start To Think About Re-Implementation Of Callee

As soon as I realized the news, I decided to think how to put again, and against performances, the callee behavior.

function F(original){
return function(){
var f = window.callee,
r = (callee = original).apply(this, arguments)
;
window.callee = f;
return r;
};
};

Above snippet execution time will be almost the same of a closure with mainly two advantages

  • less code to write

  • more performances when needed


Here an example:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(callee /* please read more */, 1000);
}), 1000);

Since JavaScript is single thread, the instant that lambda will be called the global variable callee will refer to the lambda itself. Cool, but it is not enough.
To be sure that we do not loose the callee because of some other function assignment, we should trap again the callee inside that execution:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(F(callee), 1000);
}), 1000);

There we are, a quick and dirty solution to the problem ... but, in this case for each timeout we are creating another function within its scope. This does not happen if we use the closure strategy so, again, we are trapped and we need to add more layers in the middle to increase responsiveness for something designed to improve performances ... does it make any sense?

function F(original){
function callee(){
// save the current window.callee
var f = window.callee, r;
// assign this callee function
window.callee = callee;
// retrieve the result calling original
// in the original scope, callee will be
// this whole function
r = original.apply(this, arguments);
// assign back the window callee
window.callee = f;
// return the result
return r;
};
// callee.callee will be the lambda
// rather than its wrap (callee itself)
callee.callee = original;
// here we are
return callee;
};

With above "monster" we have a slightly slower function that will always reassign the correct callee whatever will happen in the original function scope. This meanse simply that we can now use directly callee:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(callee, 1000);
// callee.callee for direct access to the lambda itself
}), 1000);


I Bloody Hate This Idea

... but if they do not change their mind, it will be one of few other possibilities we have to avoid naming pollution everywhere causing conflicts nightmare with every Internet Explorer version less than 9.

Have fun with ECMAScript 5 and its brilliant strict mode.