Reputation: 51201
In a question it was discussed on how jQuery and native JS would perform against each other.
While of course the vanilla solution performs a lot faster because it does not process the whole array I proposed the usage of Array.filter
which I was pretty confident would be at least faster than $.grep
.
Surprisingly after adding it to the test I was taught a lesson: Testsuite
Edgecases of course have a different outcome.
Anyone having an idea why $.grep
is supposed to be over 3 times faster than the native method Arrray.filter
?
Edit: I modified the test to use the filter shim from MDN and the results are pretty interesting:
and finally a result like i was hoping it to see in
Upvotes: 39
Views: 23092
Reputation: 21
Isn't your script wrong?
For array.filter
you are doing the measurement 1000 times and present it by taken the sum divided by 1000
For JQuery.grep
you are doing the measurement 1 time and present it by taken the sum divided by 1000.
That would mean your grep is actually 1000 times slower than the value you use for comparison.
Quick test in firefox gives:
Machine 1:
average filter - 3.864
average grep - 4.472
Machine2:
average filter - 1.086
average grep - 1.314
Quick test in chrome gives:
Machine 1:
average filter - 69.095
average grep - 34.077
Machine2:
average filter - 18.726
average grep - 9.163
Conclusion in firefox (50.0) is a lot faster for your code path, and filter is about 10-15% faster than jquery.grep.
Chrome is extremely slow for your code path, but grep seems to be 50% faster than array.filter here making it 900% slower than the firefox run.
Upvotes: 2
Reputation: 61401
TLDR; Grep is faster by a magnitude... (hint on why can be found here)
It looks to me like .filter forces it's this to Object, checks the callback IsCallable and sets this in it as well as checking for existence of property in each iteration whereas .grep assumes and skips these steps, meaning there is slightly less going on.
Here is the script I used for testing:
function test(){
var array = [];
for(var i = 0; i<1000000; i++)
{
array.push(i);
}
var filterResult = []
for (var i = 0; i < 1000; i++){
var stime = new Date();
var filter = array.filter(o => o == 99999);
filterResult.push(new Date() - stime);
}
var grepResult = [];
var stime = new Date();
var grep = $.grep(array,function(i,o){
return o == 99999;
});
grepResult.push(new Date() - stime);
$('p').text('average filter - '+(filterResult.reduce((pv,cv)=>{ return pv +cv},0)/1000))
$('div').text('average grep - '+(grepResult.reduce((pv,cv)=>{ return pv + cv},0)/1000))
}
test();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p></p>
<div></div>
Upvotes: -1
Reputation: 6110
As found on this blog post (which also does the same kind of tests):
If you read the documentation for
filter
, you will see why it's so much slower.
- It ignores deleted values and gaps in the array
- It optionally sets the execution context of the predicate function
- It prevents the predicate function from mutating the data
Upvotes: 17
Reputation: 2055
I found out something interesting. As explained by MarcoK, $.grep is just a simple implementation with a for loop. Filter is slower in most cases, so the implementation must be different. I think i found the answer:
function seak (e) { return e === 3; }
var array = [1,2,3,4,5,6,7,8,9,0], i, before;
array[10000] = 20; // This makes it slow, $.grep now has to iterate 10000 times.
before = new Date();
// Perform natively a couple of times.
for(i=0;i<10000;i++){
array.filter(seak);
}
document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 8515 ms (8s)
before = new Date();
// Perform with JQuery a couple of times
for(i=0;i<10000;i++){
$.grep(array, seak);
}
document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 51790 ms (51s)
The native 'filter' is much faster in this case. So i think it iterates the properties rather than the array index.
Now lets get back to 'big' problems ;).
Upvotes: 3
Reputation: 149564
Section 15.4.4.20 of the ECMAScript 5.1 spec defines Array.prototype.filter(callbackfn, thisArg)
as follows:
callbackfn
should be a function that accepts three arguments and returns a value that is coercible to the Boolean valuetrue
orfalse
.filter
callscallbackfn
once for each element in the array, in ascending order, and constructs a new array of all the values for whichcallbackfn
returnstrue
.callbackfn
is called only for elements of the array which actually exist; it is not called for missing elements of the array.If a
thisArg
parameter is provided, it will be used as thethis
value for each invocation ofcallbackfn
. If it is not provided,undefined
is used instead.
callbackfn
is called with three arguments: the value of the element, the index of the element, and the object being traversed.
filter
does not directly mutate the object on which it is called but the object may be mutated by the calls tocallbackfn
.The range of elements processed by filter is set before the first call to
callbackfn
. Elements which are appended to the array after the call to filter begins will not be visited bycallbackfn
. If existing elements of the array are changed their value as passed tocallbackfn
will be the value at the time filter visits them; elements that are deleted after the call to filter begins and before being visited are not visited.
That in itself is already a lot of work; a lot of steps that the ECMAScript engine needs to perform.
Then it goes on to say the following:
When the filter method is called with one or two arguments, the following steps are taken:
Let
O
be the result of callingToObject
passing thethis
value as the argument. LetlenValue
be the result of calling the[[Get]]
internal method ofO
with the argumentlength
. Letlen
beToUint32(lenValue)
. If IsCallable(callbackfn) is false, throw a TypeError exception. If thisArg was supplied, let T be thisArg; else let T be undefined. Let A be a new array created as if by the expression new Array() where Array is the standard built-in constructor with that name. Let k be 0. Let to be 0. Repeat, while k < len Let Pk be ToString(k). Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk. If kPresent is true, then Let kValue be the result of calling the [[Get]] internal method of O with argument Pk. Let selected be the result of calling the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O. If ToBoolean(selected) is true, then Call the [[DefineOwnProperty]] internal method of A with arguments ToString(to), Property Descriptor {[[Value]]: kValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false. Increase to by 1. Increase k by 1. Return A.The length property of the filter method is 1.
NOTE The filter 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 filter function can be applied successfully to a host object is implementation-dependent.
Some things to note about this algorithm:
In a lot of cases, none of these things are needed. So, when writing a filter
method of your own, most of the time you wouldn’t even bother to perform these steps.
Every ES5.1-compliant JavaScript engine must conform to that algorithm, and must thus perform all those steps every time you use Array#filter
.
It should be no surprise that any custom-written method that only performs a part of those steps will be faster :)
If you write your own filter
function, chances are it’s not gonna be as complex as the above algorithm. Perhaps you won’t be converting the array to an object at all, as depending on the use case it may not be needed just to filter the array.
Upvotes: 8