addEvent - The late entry :)

J

Jorge

At
20 arguments Windows Safari 3 executes that expression in 48% of the
time, and at zero arguments 47%. Mac Safari 2 was better, ranging from
16% with 20 arguments down to 34% with zero.

Opera 9.2 showed the next greatest performance change. 23% at 20
arguments and 57% at zero.
IE 7 came next with 62% at 20 arguments and 81% at zero.
IE 6: 65% to 89%.

Firefox 2.0.4 was the worst I have tried to date. At 20 arguments it
only manages 79% and was down to 85% by zero arguments, but the actual
execution time for the expression (and all of the alternatives) was the
longest of the group tested on the same hardware (by at least a factor
of 2) so the actual gain in time saved was still greater than for some
of the better performing JS engines.

Now let's see who's got the balls to argue with Richard for having
posted a benchmark... a *benchmark* !

Duh. And Safari wins one more time, again, as ever. LOL.

Benchmark (computing): the result of running a computer program, or a
set of programs, in order to assess the relative performance of an
object by running a number of standard tests and trials against it.

--Jorge.
 
K

kangax

kangax said:
On Jul 21, 5:37 pm, Richard Cornford wrote:
[snip]
And finally, using the - concat - method to append an array created
from
the arguments object is very convoluted and relatively inefficient
when
the arguments object can be used as the second argument to the -
apply -
method and the - apply - method could be called on - push -, which
will
take any number of arguments and append them to an array. That is:-
__method.apply(object, args.concat($A(arguments))); - can be replaced
with - __method.apply(obj, args.push.apply(args, arguments)); - and
should result in superior performance.
I didn't know about Array.prototype.push being faster than
Array.prototype.concat in this case. Thanks for the tip.

Faster, but not usefully so.

However, it seems that the fastest method of turning an arguments object
into an array is:-

((arguments.length == 1)?[arguments[0]]:Array.apply(this, arguments))

- where - this - is the global object in my tests, but should not be
altered by the process so could be any object. I did try null as first
argument, which is fine on everything but Firefox, where it makes the
process considerably slower than using an object reference.

Unfortunately when the Array constructor, called as a function (so not
with the - new - keyword), is only given one argument and that argument
turns out to be a numeric value that is a positive integer smaller than
2 to the power of 32 then you get different behaviour. So the expression
has to include that special handling for (arguments.length == 1). My
test still show that whole expression outperforming all of the
alternatives that I could think of.

The precise benefit depends on the number of arguments. With zero
arguments the different can be very small on some browsers (especially
firefox and IE). I did my comparisons against Prototype.js's - $A -
function , which is considered to be 100% in the following numbers. At
20 arguments Windows Safari 3 executes that expression in 48% of the
time, and at zero arguments 47%. Mac Safari 2 was better, ranging from
16% with 20 arguments down to 34% with zero.

Opera 9.2 showed the next greatest performance change. 23% at 20
arguments and 57% at zero.
IE 7 came next with 62% at 20 arguments and 81% at zero.
IE 6: 65% to 89%.

Firefox 2.0.4 was the worst I have tried to date. At 20 arguments it
only manages 79% and was down to 85% by zero arguments, but the actual
execution time for the expression (and all of the alternatives) was the
longest of the group tested on the same hardware (by at least a factor
of 2) so the actual gain in time saved was still greater than for some
of the better performing JS engines.

Array.prototype.slice.call(arguments, X); - has still got to be the
fastest method if not all of the arguments are wanted, and (strangely) -
Array.prototype.splice.call(arguments, 0, arguments.length); - is
another alternative, but there, with Windows Safari 3, the benefit
dropped off with more arguments and at 8 arguments it was worse than -
$A -.

These were tests on a mixture of Pentium 4, Core 2 Duo and Core 2 Quad
processors and Windows and Mac OSs so the results may not yet be
sufficiently reprehensive for the comparisons to hold in general. I will
probably post the test code for these tomorrow (or soonish) in case
anyone wants to see if other hardware permutations contradict those
results (or try it with other browsers/OSs).

Richard.

Thanks for an exhaustive comparison.

Unfortunately, prototype.js's $A is a general-purpose function. That's
one of the reasons why it's slower than other alternatives. Besides
converting arguments object into an array, it's often used to convert
array-like objects (namely NodeList's) into actual arrays (by
exploiting length property that NodeList's expose).

$A is also used for delegating behavior to a passed object's toArray
method if an object has one. Delegating allows to decouple concerns,
as we know, so this pattern allows other data structures to define
"toArray" containing its own logic.

As an example, String.prototype.toArray is defined as:

....
function() {
return this.split('');
}
....

It would probably be wiser to use a separate function when converting
arguments to an array, rather than relying on a "heavier" $A. We might
consider this in later revisions.


Best,
 
D

dhtml

Could you elaborate on that, please?

In Webkit and Firefox (I meant to say), events fire on bubble when
useCapture has been set to true. This is a bug and in direct violation
of the Events spec, which states:

| useCapture of type boolean
| If true, useCapture indicates that the user wishes to
| initiate capture. After initiating capture, all events of the
| specified type will be dispatched to the registered
| EventListener before being dispatched to any EventTargets
| beneath them in the tree. Events which are bubbling upward
| through the tree will not trigger an EventListener designated
| to use capture.

"Events which are bubbling upward through the tree will not trigger an
EventListener designated to use capture."

https://bugzilla.mozilla.org/show_bug.cgi?id=235441
https://bugs.webkit.org/show_bug.cgi?id=9127

The spec statment was strengthened in D3E:
http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTarget-addEventListener

| useCapture of type boolean
| If true, useCapture indicates that the user wishes to add
| the event listener for the capture phase only, i.e. this
| event listener will not be triggered during the target and
| bubbling phases. If false, the event listener will only be
| triggered during the target and bubbling phases.

Could you provide a test case that demonstrates the issue, please?

I was wrong about the legacy events bubbling differently, but not
about IE DOM Events. They don't bubble the same.

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-htmlevents

| change
| The change event occurs when a control loses the input focus and its
| value has been modified since gaining focus. This event is valid for
| INPUT, SELECT, and TEXTAREA. element.
| Bubbles: Yes
| Cancelable: No
| Context Info: None
|
| submit
| The submit event occurs when a form is submitted. This event only
| applies to the FORM element.
| Bubbles: Yes
| Cancelable: Yes
| Context Info: None

Both of these events should bubble.
Moz and Safari: these events bubble when registered with
addEventListener.

IE: these events don't bubble

MSDN docs
onchange: http://msdn.microsoft.com/en-us/library/ms536912(VS.85).aspx
onsubmit: http://msdn.microsoft.com/en-us/library/ms536972.aspx

I'll post up an example tomorrow.

and

* event listeners *added* with addEventListener() are executed
  *in order of addition*;

  event listeners *attached* with attachEvent() are executed
  *in arbitrary order*

Good to know, but I can't think of a good reason for relying on the
order.
 
D

dhtml

This copies the arguments ...


... and this push call changes the copy. I.e., every time
the function is called, the args array is made larger.
Also, the push function doesn't return the updated array.


Good point. push() will return a number. Passing a number as the
second argument to Function.prototype.apply would result in a
TypeError -- Definitely not good for performance.

concat returns a new Array. So do slice and splice.

In this case, the concat function would probably be better, i.e.:

The change could be rolled back, e.g.:
      return function() {
        return fnc.apply(obj, args.concat(arguments));
      }

But that would not work in the way that you want it o with an
arguments object because arguments is not an array, so it would be
added next in the list. Now if arguments were an array, it would work
as desired, adding up all the items to the array. So it would be
necessary to slice() it.

Garrett
 
D

dhtml

I've added a test case below that adds callbacks for change, submit,
and select events. The callbacks are added to an ancestor node. The
example shows that the events DO NOT bubble in IE, just as the MSDN
spec states.
We can see that the events don't bubble in IE.
I was wrong about the legacy events bubbling differently, but not
about IE DOM Events. They don't bubble the same.

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroup...

| change
| The change event occurs when a control loses the input focus and its
| value has been modified since gaining focus. This event is valid for
| INPUT, SELECT, and TEXTAREA. element.
| Bubbles: Yes
| Cancelable: No
| Context Info: None
|
| submit
| The submit event occurs when a form is submitted. This event only
| applies to the FORM element.
| Bubbles: Yes
| Cancelable: Yes
| Context Info: None

Both of these events should bubble.
Moz and Safari: these events bubble when registered with
addEventListener.

IE: these events don't bubble

MSDN docs
 onchange:http://msdn.microsoft.com/en-us/library/ms536912(VS.85).aspx
 onsubmit:http://msdn.microsoft.com/en-us/library/ms536972.aspx

I'll post up an example tomorrow.

Example:
==================================================================

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> Change </title>
<style type="text/css">
label { cursor: default; }
</style>
</head>

<body>

<h1>Form Dirty?</h1>

<form action="">

<fieldset id="ff"><legend>legend</legend>
<select name='big'>
<option disabled="disabled">disabled option</option>
<option>one</option>
<option>two</option>
</select>

<textarea name="a" cols="12" rows="1">foo</textarea>
<label>
<input type="radio" name="df" value="R1"/>radio
</label>
<label>
<input type="radio" name="df" value="R2"/>radio
</label>
<input type="checkbox"/>
<input type='text'/>
<input type="password"/>
<input type="submit" value="go"/>
</fieldset>

</form>

<strong>attachEvent/addEventListener</strong>
<pre id='monitor'>



</pre>
<strong>legacy event</strong>
<pre id='monitor2'>



</pre>
<script type="text/javascript">(function(){
var ff = document.getElementById('ff'),
monitor = document.getElementById('monitor'),
monitor2 = document.getElementById('monitor2');

if(ff.addEventListener) {
ff.addEventListener("change", getTimeStamp, false);
document.addEventListener("submit", getTimeStamp, false);
document.addEventListener("select", getTimeStamp, false);
} else if(ff.attachEvent) {
ff.attachEvent("onchange", getTimeStamp);
document.attachEvent("onsubmit", getTimeStamp);
document.attachEvent("onselect", getTimeStamp);
}

ff.onchange = document.onsubmit = document.onselect = legacy;

function getTimeStamp(e){
e = e || event;
var tag = (e.target || e.srcElement);
monitor.innerHTML = "this = " + this.nodeName
+ "\ntarget: " + (tag.type || tag.nodeName)
+ "\ntype: " +(e.type + "\ntimestamp: " + e.timeStamp);
}
function legacy(e) {
e = e || event;
var tag = (e.target || e.srcElement);
monitor2.innerHTML = "this = " + this.nodeName + "\n"
+ "target: " + (tag.type || tag.nodeName)
+ "\ntype: legacy on" + (e.type + "\ntimestamp: " + e.timeStamp);
return false;
}
})();</script>
</body>
</html>

==================================================================

By interacting with the example, it can be seen that the registered
events do fire callbacks in MSIE. This is because these events don't
bubble, and this is clearly stated on MSDN documentation links
(provided).

It can also be observed that the events will fire callbacks
registered
with useCapture=true, by changing the code to:

ff.addEventListener("change", getTimeStamp, true);
document.addEventListener("submit", getTimeStamp, true);
document.addEventListener("select", getTimeStamp, true);

Callbacks fire on bubbled event in: Opera9, Safari3, Firefox3, a bug
in all 3 browsers.

Garrett
 
R

Richard Cornford

On Jul 23, 8:44 pm, "Richard Cornford wrote:
Thanks for an exhaustive comparison.

Didn't I make the point that they were not exhaustive clearly enough?
Unfortunately, prototype.js's $A is a general-purpose function.
That's one of the reasons why it's slower than other alternatives.
Besides converting arguments object into an array, it's often
used to convert array-like objects (namely NodeList's) into
actual arrays (by exploiting length property that NodeList's
expose).

What - $A - may or may not be is irrelevant to the question. The
approach used in converting an - arguments - object into an array can be
chosen at the point of needing to do it. There is no necessity to choose
to use a general function to do it, and no reason for the changing of
the approach used for converting - arguments - objects to arrays to
impact on the - $A - function at all.
$A is also used for delegating behavior to a passed object's
toArray method if an object has one. Delegating allows to
decouple concerns, as we know, so this pattern allows other
data structures to define "toArray" containing its own logic.

As an example, String.prototype.toArray is defined as:

...
function() {
return this.split('');}

...

But we can be certain that - arguments - objects will not have -
toArray - methods (unless they are explicitly assigned them).
It would probably be wiser to use a separate function when
converting arguments to an array, rather than relying on a
"heavier" $A. We might consider this in later revisions.

I did include a dedicated function in my tests (one that did little more
than create a new array and loop over the 'array index' properties of
its argument object copying values to corresponding properties of the
new array). Inevitably it was a little faster than - $A - by virtue of
doing less, but it was such a minimal difference that I would not regard
it as worth consideration in comparison to the differences that some of
the alternatives offer.

At this point it is probably worth posting my test code. The following
is a simple scripted HTML page. At the top there is a filed for entering
the number of iterations to perform (which may need adjusting for
particular browser/OS/hardware combinations). Generally, the number of
iterations should be the most that any particular system will allow
without putting up the 'A script on this page ... " dialog, and
certainly large enough to make the total duration of _all_ the results
greater than 300 milliseconds at (absolute) minimum (as the 10
millisecond resolution of most browser timers would make shorter
durations close to meaningless).

There is a table at the bottom of the page into which the results are
written, and a number of buttons ladled 'Test' to start to test process
(which are disabled while the test runs). On of these buttons is under
the output table at the bottom of the page. Individual loop runs are
separated with - setTimeout - calls with long-ish intervals (to
guarantee that the browser has time to catch up with any work it needs
to do (including re-laying out the results table) and reduce the
likelihood of seeing the "A Script on this page ..." dialog.

The individual test functions are stored in an array of objects with the
global identifier - fncts -. These object have - description - and -
testFnc - properties, where - testFnc - is the function that executes
the expression being tested. With the exception of the first item in
this array (which is used to gauge the overheads involved in the test
loop) items in this array may be removed, or alternatives added. Layout
and overall test looping is driven from the length of this array. The
last item in the array is the one against which comparisons are made and
should be the one expected to be slowest.

And array of argument sets with the global identifier - args - is used
to demonstrate the impact of changing numbers of arguments on the
process. The number of entries in this array can also be changed as the
array length is used to drive that aspect of the test process. Also,
currently all of the arguments are numeric literals. Other types of
arguments may be tried to see whether that has an impact on performance
(unlikely but not impossible).

<html>
<head>
<title>arguemnts to Array tests</title>
<style type="text/css">
TD.alRight {
text-align:right;
}
</style>
<script type="text/javascript">


function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
function makeArray(iterable) {
var c, ar = [];
if((c = iterable.length)){
do{
ar[--c] = iterable[c];
}while(c)
}
return ar;
}

var args = [
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9],
[1,2,3,4,5,6,7,8],
[1,2,3,4,5,6,7],
[1,2,3,4,5,6],
[1,2,3,4,5],
[1,2,3,4],
[1,2,3],
[1,2],
[1],
[]
];

var argsIndex = 0;

var frm = null;
var fncts = [
/* Timing of an "empty" loop to estimate the overheads of the
test itself. */
{
testFnc:function(){
return arguments;
},
description:'Only overheads.'
},
{
testFnc:function(){
var ar = [];
ar.push.apply(ar, arguments);
return ar;
},
description:'push.apply'
},
{
testFnc:function(){
var ar = [];
ar.unshift.apply(ar, arguments);
return ar;
},
description:'unshift.apply'
},
{
testFnc:function(){
return (
arguments.length == 1)?
[arguments[0]]:
Array.apply(null, arguments
);
},
description:'Array.apply(null'
},
{
testFnc:function(){
return (
arguments.length == 1)?
[arguments[0]]:
Array.apply(this, arguments
);
},
description:'Array.apply(this'
},
{
testFnc:function(){
return Array.prototype.slice.call(arguments, 0);
},
description:'slice.call'
},
{
testFnc:function(){
return Array.prototype.splice.call(
arguments, 0, arguments.length
);
},
description:'splice.call'
},
{
testFnc:function(){
switch(arguments.length){
case 0:
return [];
case 1:
return [arguments[0]];
default:
return Array.apply(this, arguments);
}
},
description:'switch'
},
{
testFnc:function(){
return makeArray(arguments);
},
description:'makeArray'
},
{
testFnc:function(){
return $A(arguments);
},
description:'$A'
}
];

function runTest(p){
var lim = +frm['loopLimit'].value;
var N, totTime, stTime;
var obj = {};
stTime = new Date().getTime();
for(var c = 0;c < lim;c++){
N = fncts[p].testFnc.apply(this, args[argsIndex]);
}
totTime = (new Date().getTime() - stTime);
frm["Dur"+p].value = totTime;
frm["Avr"+p].value = (totTime/lim);
frm["Res"+p].value = N;
act(p+1);
}

var f;
var running = false;
function setButtons(bl){
frm['loopLimit'].disabled = bl;
var sw = frm['bt'];
if(typeof sw.length == 'undefined'){
sw = [sw];
}
for(var c = 0;c < sw.length;c++){
sw[c].disabled = bl;
}
}
function startTests(){
if(!running){
frm = document.forms['f'].elements;
setButtons(true);
frm["Dur0"].value = '';frm["Avr0"].value = '';
for(var c = 1;c < fncts.length;c++){
frm["Dur"+c].value = '';
frm["Avr"+c].value = '';
frm["Res"+c].value = '';
}
running = true;
act(0);
}
}
function act(p){
/* setTimeout is used to minimise the occurrences
of 'a script on this page is running slow' dialogs. */
if(p >= fncts.length){
++argsIndex;
if(argsIndex < args.length){
setTimeout('report();startTests()',1000);
}else{
setTimeout('report();argsIndex = 0;',1000);
}
}else{
setTimeout(('(f = runTest('+p+'));'),2000);
}
}
function report(){
var co = ((argsIndex - 1)<<1)+1
var emDur, unaC, diff1, diff2, c, evaC;
var lim = +frm['loopLimit'].value;
var row, tBody = document.getElementById('outBody');
emDur = +frm["Dur0"].value;
c = (fncts.length-1);
row = tBody.rows[1+c];
diff1 = (frm["Dur"+c].value - emDur); //if this is negative then
//the whole test is invalid.
if(diff1 < 0){
// "Empty" loop longer than base test loop
row.cells[co].innerHTML = '+++++++'
return;
}
unaC = diff1 / lim;
if(!unaC){
row.cells[co].innerHTML = 'Loop too short';
return;
}
row.cells[co].innerHTML = formatVal(100);
row.cells[co+1].innerHTML = formatVal(unaC);
for(c = 1;c < (fncts.length-1);c++){
row = tBody.rows[1+c];
diff2 = (frm["Dur"+c].value - emDur);
if(diff2 < 0){
//"Empty" loop longer than this test';
row.cells[co].innerHTML = '*******'
}else{
evaC = diff2 / lim;
row.cells[co].innerHTML = formatVal(((evaC/unaC)*100));
row.cells[co+1].innerHTML = formatVal(evaC);
}
}
tBody.rows[0].cells[0].innerHTML = (navigator.userAgent);
tBody.rows[1].cells[0].innerHTML = ('Iterations = '+lim);
setButtons(false);
running = false;
}
function roundToR(X, R) { return Math.round(X*R)/R }
function formatVal(n){
if(isNaN(n)){
return '------'
}
var s = String(roundToR(n, 10000));
var ind;
if((ind = s.indexOf('.')) < 0){
s += '.';
ind = s.length - 1;
}
s += '000'
return s.substring(0, (ind+4));
}
</script>
</head>
<body>
<div>
<form name="f" action="#">
Loop Length = <input type="text" value="80000"
name="loopLimit"><br><br>


<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>
Empty Loop Duration (milliseconds) = <input type="text" value="X"
name="Dur0"><br>
Empty Loop Average (milliseconds) = <input type="text" value="X"
name="Avr0" size="22"><br>
(result = <input type="text" value="X" name="Res0" size="42">)<br>
<br>

<script type="text/javascript">
var c, len = fncts.length;
var st = ''
for(c = 1;c < len;++c){
st += '<br><code>'+fncts[c].description;
st += '</code> Duration (milliseconds) = ';
st += '<input type="text" value="X" name="Dur'+c+'"><br>';
st += '<code>'+fncts[c].description;
st += '</code> Average (milliseconds) = ';
st += '<input type="text" value="X" name="Avr'+c;
st += '" size="22"><br>';
st += '(result = <input type="text" value="X" name="Res'+c;
st += '" size="42">)<br>';
}
document.write(st);
</script>

<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>

Average: (duration of test - duration of &quot;empty&quot; loop)
/ loop length (Milliseconds)<br>

<script type="text/javascript">
var tdsOut = [''];
tdsOut.toString = function(){
return ('<td>'+this.join('<\/td><td class="alRight">')+'<\/td>');
}
var rowsOut = [];
rowsOut.toString = function(){
return ('<tr>'+this.join('<\/tr><tr>')+'<\/tr>');
}
var tableOut = [
'<table border="2"><tbody id="outBody">',
rowsOut,
'<\/tbody><\/table>'
]

//fncts.length // vertical
//args.length){ //horezontal
var c, d, len = fncts.length, dLen = args.length;
var st = '<th><\/th>'
for(d = 0;d < dLen;++d){
st += '<th colspan="2">N<sup>o</sup> args = ';
st += args[d].length+'<\/th>'
}
rowsOut.push(st);
for(d = 0;d < dLen;++d){
tdsOut.push('%');
tdsOut.push('Average');
}
rowsOut.push(String(tdsOut));
for(c = 1;c < len;++c){
tdsOut.length = 0;
tdsOut.push(c+':'+fncts[c].description);
for(d = 0;d < dLen;++d){
tdsOut.push('');
tdsOut.push('');
}
rowsOut.push(String(tdsOut));
}
document.write(tableOut.join(''));
</script>
<br><br>
<input type="button" value="Test" name="bt" onclick="startTests();">
Repeat tests to reduce/expose the influence of background tasks.
<br><br>
</form>
</div>
</body>
</html>

It may be observed that the process with the description - switch - is
actually the fasted across the board. Suggesting that short-circuiting
the process in the (probably common) case of having zero arguments is
beneficial . However, I have not proposed this as the fasted approach
because I was interested in a single expression that could replace the -
$A(arguments) - expression, and - switch - is a statement not an
expression. Moving the switch statement into a function and calling that
in place of - $A - would add the function call overheads and so may
negate its advantages (indeed a quick test of that in IE6 shows the
function call overheads have that approach drop to 120% of - $A -
performance at two arguments and it does not overtake - $A - again until
the number of arguments gets up to 5).

If the zero arguments case really is expected to be common then the
expression:-

(
(!arguments.length)?
[]:
(
(arguments.length == 1)?
[arguments[0]]:
Array.apply(this, arguments)
)
)

- might prove advantageous (performance-wise, otherwise it is getting
too big/complex). Though it would still be possible to recognise the
common case at the point of calling and so call a faster alternative
instead and so avoid any need to be processing arguments object for that
case.

Richard.
 
R

Richard Cornford

Jorge said:
Now let's see who's got the balls to argue with Richard for
having posted a benchmark... a *benchmark* !

Your point being?
Duh. And Safari wins one more time, again, as ever. LOL.
<snip>

That is not implied by anything that I posted. Even if one process runs
in 16% of the time taken by another on some Safari browsers that does
not mean that the time taken by either process was shorter then those
exhibited by all other browsers.

And where Mac Safari is concerned direct comparisons become extremely
difficult due to the impossibility of running tests against IE on
equivalent hardware. We can expect Mac Safari to be in a position to
take advantage of low-level OS details unavailable to other browser
manufacturers, in the same way as we can expect Windows IE to be in a
position to take advantage of a similar understanding of Window OS
details.

Richard.
 
J

Jorge

Your point being?

That whenever I've posted here a benchmark showing that a certain code
runs x times faster in browser a than in browser b (in my machine)
they all jump on me saying that JS benchmarks are meaningless.
<snip>

That is not implied by anything that I posted. Even if one process runs
in 16% of the time taken by another on some Safari browsers that does
not mean that the time taken by either process was shorter then those
exhibited by all other browsers.

Now I'm lost. Unless the trick here is in the word *all* other
browsers.
Certainly it was faster than *all* other browsers in which you've run
the test. Or not (?), and that info is useful.
And where Mac Safari is concerned direct comparisons become extremely
difficult due to the impossibility of running tests against IE on
equivalent hardware.

Boot the Mac (intel) in Windows.
We can expect Mac Safari to be in a position to
take advantage of low-level OS details unavailable to other browser
manufacturers,

Safari´s source code unlike IE's is open sourced. Darwin unlike
Windows is open sourced. So unless the code we're talking about was a
call to a Cocoa framework, that's impossible as there aren't any
"unavailable details".

And "expecting" that is being quite paranoic unless you discover
(studying the source code) the (highly unlikely) event that whenever
something runs faster in Safari it happens to be a call to propietary
code, as a Cocoa framework for example.
in the same way as we can expect Windows IE to be in a
position to take advantage of a similar understanding of Window OS
details.

That's not only possible, but very likely, as history has shown us
already.

--Jorge.
 
P

Peter Michaux

On Jul 21, 2:37 pm, "Richard Cornford" <[email protected]>
wrote:

[snip]
Function.prototype.bind = function(obj){
var args, fnc;
if(arguments.length > 1){
fnc = this;
args = Array.prototype.slice.call(arguments, 1);

[snip]

If I remember correctly, David Mark suggested he new of
an implementation where the above code would error. I use a loop to
accomplish the goal in the above line.

I'm cc'ing David about this.

David, am I remembering incorrectly?

Thanks,
Peter
 
D

dhtml

Didn't I make the point that they were not exhaustive clearly enough?

And the fact that NodeList has been given numeric-named properties,
e.g
document.childNodes["0"]
What - $A - may or may not be is irrelevant to the question. The
approach used in converting an - arguments - object into an array can be
chosen at the point of needing to do it.

By choosing this approach, the overhead of an extra function call can
be avoided.

function getReversedChildNodes(el) {
var kids = Array.prototype.slice.call(el.childNodes);
return kids.reverse();
}

But we can be certain that - arguments - objects will not have -
toArray - methods (unless they are explicitly assigned them).

For an arguments object, Array.prototype.slice.call(arguments) inline
would be faster.

I think it would be faster and clearer as to what the code is doing by
using an inline call.

Inline code would be fastest:

// Get the arguments as a real Array.
[].slice.call(arguments).

It would be concise, simple, and easy to understand.

If the Array were needed from:

A string:
s.split('');

An arguments object:
Array.prototype.push.apply(arguments);

An Array-like object:
Array.prototype.slice.call(nodeList);

A native ecmascript object:
var r = [];
for(var p in o) r.push(o[p]);

The requirements of Prototype.js is that the $A function convert an
array-like object into an Array. (array-like - has sequential numeric
properties and a length property).

Function.prototype.apply requires the second argument to be an Array
or arguments object. Using push.apply() or unshift.apply as a
replacement in $A would impose a new restriction that will probably
break existing code in or using Prototype.js.

A generalized approach would not be able to differentiate an arguments
object from a NodeList, so that would reduce the switch to: A string,
an Array-like object.

The potential value of creating an abstraction for an object that has
a specific toArray seems to be only that there are different types of
objects that can be converted to an array in different ways.

Enumerable has a toArray:-

toArray -> map -> each, with n calls to iterator/Prototype.K, which
returns the item. It is a long string of function calls. The "Write a
Loop Once" Pattern is powerful, when needed. It can also be used in a
template pattern, and a Template pattern can also use other types of
iteration, such as setInterval.

map is just an alias to collect:

collect: function(iterator, context) {
iterator = iterator ? iterator.bind(context) : Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator(value, index));
});
return results;
}

If there is no iterator function, and if the each function is simply a
Mapper that calls the iterator function, then it would be much more
efficient to simply return the object as an array.

However, for other needs, if a unique mapper function is desired, then
the Array extras can be used on the object after converting it to an
Array (which can also be sorted, et c). This has the benefit of using
the standard built-in Array.

Why is toArray called in $A? The only benefit would seem to be with:

A native ecmascript object:
var r = [];
for(var p in o) r.push(o[p]);

Which is something that is seen in Hash._each:-

_each: function(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
},

Where the iterator takes a "pair" type object that is an array with
key and value properties. The Hash object's prototype chain is also
enumerated over, which seems undesirable and unexpected, as far as a
HashMap type of functionality goes.

Iteration abstractions are powerful but not always needed. The costs
are complexity (especially in PrototypeJS) and performance.

I would not mind a native Array.fromArrayLike(obj) in a newer version
of ES, but it doesn't seem necessary to me. Mountains out of
molehills.

It's nice out. That will be all for today!


Garrett
 
P

Peter Michaux

On Jul 21, 2:37 pm, "Richard Cornford" <[email protected]>
wrote:
Function.prototype.bind = function(obj){
var args, fnc;
if(arguments.length > 1){
fnc = this;
args = Array.prototype.slice.call(arguments, 1);
[snip]

If I remember correctly, David Mark suggested he new of
an implementation where the above code would error. I use a loop to
accomplish the goal in the above line.

I'm cc'ing David about this.

David, am I remembering incorrectly?

David replied to me

"Yes. DOM node collections throw exceptions on slice, not arguments."

ECMA-262 3rd 15.4.4.10

"The slice function is intentionally generic; it does not require that
its this value be an Array object. Therefore it can be transferred to
other kinds of objects for use as a method. Whether the slice function
can be applied successfully to a host object is implementation-
dependent."

Peter
 
D

dhtml

On Jul 24, 2:46 am, kangax wrote:
Didn't I make the point that they were not exhaustive clearly enough?

And the fact that NodeList has been given numeric-named properties,
e.g
document.childNodes["0"]
What - $A - may or may not be is irrelevant to the question. The
approach used in converting an - arguments - object into an array can be
chosen at the point of needing to do it.

By choosing this approach, the overhead of an extra function call can
be avoided.

function getReversedChildNodes(el) {
  var kids = Array.prototype.slice.call(el.childNodes);
  return kids.reverse();

}
But we can be certain that - arguments - objects will not have -
toArray - methods (unless they are explicitly assigned them).

For an arguments object, Array.prototype.slice.call(arguments) inline
would be faster.

I think it would be faster and clearer as to what the code is doing by
using an inline call.

Inline code would be fastest:

// Get the arguments as a real Array.
[].slice.call(arguments).

It would be concise, simple, and easy to understand.

If the Array were needed from:

A string:
  s.split('');

An arguments object:
  Array.prototype.push.apply(arguments);

Would have to be:-
var a = [];
a.push.apply(a, arguments);


Example:-
var argsIsArray = (function(){

var a = [];
a.push.apply(a, arguments);
return a;

})(1,2,3).constructor === Array;

alert(argsIsArray);

"true"
 
R

Richard Cornford

dhtml said:
On Jul 26, 8:18 am, Richard Cornford wrote:

By choosing this approach, the overhead of an extra function
call can be avoided.

function getReversedChildNodes(el) {
var kids = Array.prototype.slice.call(el.childNodes);
return kids.reverse();
}

Why don't you see that it is superfluous to respond to me asserting the
very thing that I not only have already suggested but gong to some
effort to ascertain the truth of?

<snip - more pointless noise>

It's nice out. That will be all for today!

Ah, doing something smart for a change.

Richard.
 
R

Richard Cornford

Jorge said:
That whenever I've posted here a benchmark showing that a
certain code runs x times faster in browser a than in
browser b (in my machine) they all jump on me saying that
JS benchmarks are meaningless.

On the 3GHz 4 core CPU I used for some of the tests differences in the
durations of single operations was tenths of a nanosecond, and on an OS
that does not necessarily report the time with a precision of better
than +/- 10 milliseconds. It is trivially easy to undertake such a test
and come up with results that are meaningless (and/or misleading).

It is meaningless to post numbers in isolation. It is important to say
how those numbers were obtained, what they are supposed to mean, how
they show that meaning and that the numbers are reported in reality. To
which end it is a very good idea to post a demonstration page that will
demonstrate the method and facilitate third party verification of the
results.
Now I'm lost. Unless the trick here is in the word *all*
other browsers.

The word 'all' is their following consideration. I did state that
Firefox was slower in these tests than any other browser tested on the
same hardware/OS combination, and so implied it was slower than Safari
(which it was). But being faster than Firefox is not the same as being
fastest.
Certainly it was faster than *all* other browsers in which
you've run the test. Or not (?), and that info is useful.

No. In reality Opera 9.20 and Windows Safari 3 tested on the same box
running the same OS reported vary similar durations for the various
expressions tested. For some Safari was fractionally ahead and for
others Opera was ahead. Safari, for some reason, singled out -
Array.prototype.splice.call(arguments, 0, arguments.length) - for
particularly bad performance (worse than - $A(arguments) - with more
than about 6 arguments and getting worse as the number of arguments
increased, while Opera showed that expression to be between ~50% and
~35% better than - $A(arguments) - at zero and 20 arguments
respectively.
Boot the Mac (intel) in Windows.

And would that be the same hardware? The box may contain the same
hardware but what was being used in what way by each operating system
would be difficult to know. Imagine, for example, on OS using a generic
hardware drives while the other fully exploited the capabilities of a
dedicated driver.
Safari´s source code unlike IE's is open sourced. Darwin
unlike Windows is open sourced. So unless the code we're
talking about was a call to a Cocoa framework, that's
impossible as there aren't any "unavailable details".

OK, not unavailable, but unlikely to as obvious to the readers of open
source code than they are to its authors. Details hide well among very
large collections of similar details.
And "expecting" that is being quite paranoic

The word is 'cynical', and I am.
unless you discover (studying the source code) the (highly
unlikely) event that whenever something runs faster in Safari
it happens to be a call to propietary code, as a Cocoa
framework for example.

Ah, so heaven forbid a corporation might ever use inside information to
help negotiate itself into a monopoly position.
That's not only possible, but very likely, as history has
shown us already.

So you are saying that Microsoft have already done this thing but Apple
never would even if they could? You have not understood capitalism.

Richard.
 
R

Richard Cornford

Peter said:
On Jul 21, 2:37 pm, Richard Cornford wrote:
Function.prototype.bind = function(obj){
var args, fnc;
if(arguments.length > 1){
fnc = this;
args = Array.prototype.slice.call(arguments, 1);
[snip]

If I remember correctly, David Mark suggested he new of
an implementation where the above code would error.
I use a loop to accomplish the goal in the above line.

I'm cc'ing David about this.

David, am I remembering incorrectly?

David replied to me

"Yes. DOM node collections throw exceptions on slice,
not arguments."

ECMA-262 3rd 15.4.4.10
... . Whether the slice function can be applied successfully
to a host object is implementation-dependent."

I suspected that if there had been an example of an implementation with
an issue handling arguments object then that would have come to my
attention by now.

(Incidentally, I do intend responding to your last substantial response
to me in this thread. I have started writing that response but I don't
think I will have time to finish it tonight.)

Richard.
 
J

Jorge

The word is 'cynical', and I am.

I read many of your posts and you don't look like a cynical. Unlike
you, instead, some others in cljs do.

I really meant paranoic :

"A tendency on the part of an individual or group toward *excessive or
irrational suspiciousness and distrustfulness* of others."

Regards,
--Jorge.
 
J

Jorge

So you are saying that Microsoft have already done this thing but Apple
never would even if they could? You have not understood capitalism.

I understand capitalism. Up until now it has proved to be the less bad
of the solutions.

And Apple could move right now as much of the Safari code as they wish
into private, propietary OSX frameworks.
If they don't it's not because they can't.

--Jorge.
 
J

Jorge

And would that be the same hardware? The box may contain the same
hardware but what was being used in what way by each operating system
would be difficult to know. Imagine, for example, on OS using a generic
hardware drives while the other fully exploited the capabilities of a
dedicated driver.

That would not be very smart. The drivers are included in the OSX
install DVD. Whats prevents you to install the appropiate drivers (?).
Or are you "irrationaly suspicious" about the bundled Windows drivers
as well ?

:)

--Jorge.
 
R

Richard Cornford

Jorge said:
I read many of your posts and you don't look like a cynical.

A person who is cynical is a cynic.
Unlike you, instead, some others in cljs do.

I really meant paranoic :

"A tendency on the part of an individual or group
toward *excessive or irrational suspiciousness and
distrustfulness* of others."

So you are disputing the degree to which the suspicion and
distrustfulness is excessive and/or irrational? Personally I see taking
both as a starting position as infinitely preferable to blind faith,
unconditional trust and taking everything at face value.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,533
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top