Reputation: 447
Why is it slower to search for a value in an object by key than using for in
in JavaScript?
Like this code:
const a = { a: { txt: 1 }, b: { txt: 2 }, c: { txt: 3 }, d: { txt: 4 }, e: { txt: 5 }, f: { txt: 6 } };
console.time('1');
let n = a['e'].txt;
console.log(n, '<<n')
console.timeEnd('1');
console.time('2');
for (const key in a) {
if (a[key].txt == 5) {
const m = a[key];
console.log(m, '<<m')
break;
}
}
console.timeEnd('2');
The result is
5 '<<by key' 1: 2.329ms { txt: 5 } '<<for in ' 2: 0.447ms
Isn't this weird?
Upvotes: 30
Views: 3913
Reputation: 5158
As you can see here, testing with JS can be really a mess.
const a = { a: { txt: 1 }, b: { txt: 2 }, c: { txt: 3 }, d: { txt: 4 }, e: { txt: 5 }, f: { txt: 6 } };
let test = function(x) {
console.log("Test "+x+" times")
console.time('1');
for(let i=0;i<x;i++) {
let n = a['e'].txt;
}
console.timeEnd('1');
console.time('2');
for(let i=0;i<x;i++) {
for (const key in a) {
if (a[key].txt == 5) {
const m = a[key];
break;
}
}
}
console.timeEnd('2');
}
test(1)
test(100)
test(100000)
test(100000)
test(100000)
test(10000000)
Upvotes: 5
Reputation: 633
In JavaScript you can add or remove properties to objects dynamically. HashMaps are most memory efficient and fast data structures to access the properties. But dynamic nature of JavaScript makes it more difficult and slower. To fix this problem Nodejs V8 Engine internally uses JavaScript hidden classes and inline caching. Both the topics are quite vast to explain in this answer. So please find the blog link here and following is the awesome explanation of Nodejs v8 engine performance video.
In one iteration you can not determine the performance of two algorithms in almost 99% cases. So I have just modified your code and iterated it for 11 times (which is also not sufficient) for the demo purpose. And you can see drastic change in the output.
for (let i = 0; i <= 10; i++) {
const a = { a: { txt: 1 }, b: { txt: 2 }, c: { txt: 3 }, d: { txt: 4 }, e: { txt: 5 }, f: { txt: 6 } };
console.time('Hash map access');
let n = a['e'].txt;
console.log(n, '<<n')
console.timeEnd('Hash map access');
console.time('For in loop');
for (const key in a) {
if (a[key].txt == 5) {
const m = a[key];
console.log(m, '<<m')
break;
}
}
console.timeEnd('For in loop');
}
Following is the out-put.
5 '<<n'
Hash map access: 8.580ms
{ txt: 5 } '<<m'
For in loop: 4.301ms
5 '<<n'
Hash map access: 0.177ms
{ txt: 5 } '<<m'
For in loop: 0.377ms
5 '<<n'
Hash map access: 0.170ms
{ txt: 5 } '<<m'
For in loop: 0.196ms
5 '<<n'
Hash map access: 0.162ms
{ txt: 5 } '<<m'
For in loop: 0.186ms
5 '<<n'
Hash map access: 0.483ms
{ txt: 5 } '<<m'
For in loop: 0.465ms
5 '<<n'
Hash map access: 0.435ms
{ txt: 5 } '<<m'
For in loop: 0.503ms
5 '<<n'
Hash map access: 0.500ms
{ txt: 5 } '<<m'
For in loop: 0.471ms
5 '<<n'
Hash map access: 0.528ms
{ txt: 5 } '<<m'
For in loop: 0.487ms
5 '<<n'
Hash map access: 0.492ms
{ txt: 5 } '<<m'
For in loop: 0.494ms
5 '<<n'
Hash map access: 1.033ms
{ txt: 5 } '<<m'
For in loop: 0.726ms
5 '<<n'
Hash map access: 0.484ms
{ txt: 5 } '<<m'
For in loop: 0.649ms
If you observe the output there is drastic change in hash-map access, first it was 8.580ms in second time it was 0.177ms. You can find that after first time hash-map is almost faster than "for in loop". (Sometimes it's not as my system is under a lot of pressure while runnig the code :) )
Also I reversed the order, I put "For in Loop" first and then object [hashmap] following is the result.
{ txt: 5 } '<<m'
For in loop: 16.390ms
5 '<<n'
Hash map access: 0.220ms
{ txt: 5 } '<<m'
For in loop: 0.266ms
5 '<<n'
Hash map access: 0.186ms
{ txt: 5 } '<<m'
For in loop: 0.277ms
5 '<<n'
Hash map access: 0.367ms
{ txt: 5 } '<<m'
For in loop: 0.328ms
5 '<<n'
Hash map access: 0.249ms
{ txt: 5 } '<<m'
For in loop: 0.947ms
5 '<<n'
Hash map access: 4.013ms
{ txt: 5 } '<<m'
For in loop: 0.799ms
5 '<<n'
Hash map access: 0.532ms
{ txt: 5 } '<<m'
For in loop: 0.565ms
5 '<<n'
Hash map access: 0.479ms
{ txt: 5 } '<<m'
For in loop: 0.644ms
5 '<<n'
Hash map access: 0.609ms
{ txt: 5 } '<<m'
For in loop: 0.624ms
5 '<<n'
Hash map access: 0.472ms
{ txt: 5 } '<<m'
For in loop: 0.509ms
5 '<<n'
Hash map access: 0.458ms
{ txt: 5 } '<<m'
For in loop: 0.568ms
5 '<<n'
Hash map access: 0.476ms
We can see first for-in loop has taken 16.39ms while the second only 0.266ms. As mentioned in above answer instantiation takes a lot of time, we can easily validate by seeing these numbers.
Conclusion is while writing the code in JavaScript for Nodejs v8 engine, if we don't add/remove properties on objects it will be more fast and efficient. Also code instantiation takes a lot of time while running the first time.
Upvotes: 3
Reputation: 9380
You have an error in your program
if (a[key].txt = 5)
You are not checking if the txt property is equal to 5. You are setting the property to 5 which means you are finished after the first execution of the loop regardless.
Upvotes: 10
Reputation: 7146
This is because of how the JIT compiler works.
When you start a JS script with Node, the V8 starts interpreting it, while compiling it into native machine code.
Running it in the Chrome Devtools console, I get this output :
5 "<<n"
0.167724609375ms
{txt: 5} "<<m"
2: 0.262939453125ms
NodeJS output :
5 '<<n'
1: 18.684ms
{ txt: 5 } '<<m'
2: 3.713ms
But when inverting the 2 variations :
const a = { a: { txt: 1 }, b: { txt: 2 }, c: { txt: 3 }, d: { txt: 4 }, e: { txt: 5 }, f: { txt: 6 } };
console.time('2');
for (const key in a) {
if (a[key].txt = 5) {
const m = a[key];
console.log(m, '<<m')
break;
}
}
console.timeEnd('2');
console.time('1');
let n = a['e'].txt;
console.log(n, '<<n')
console.timeEnd('1');
Output :
{ txt: 5 } '<<m'
2: 22.017ms
5 '<<n'
1: 0.245ms
As you can see, the version that is executed first takes much more time than the second.
However, if you average it, you can see that executing the key access is much faster than the for in
loop.
Upvotes: 46