3 ms·
I could be wrong (and if so, pie my face), but I believe it's mostly due to one of the many the inline cache optimizations that v8 employs. Let's consider the
by chewxy 12y ago
I could be wrong (and if so, pie my face), but I believe it's mostly due to one of the many the inline cache optimizations that v8 employs.
Let's consider the receiver (i.e the `this` value) of Example 1 and 2. The receiver of Example 1 is Benchmark, if invoked normally. The receiver of Example 2 is the empty function object function(){}.
When you call makeKeyCodepointObj.makeKey() - the VM looks up the object's prototype chain and finds the function. This call site is cached (think of it as a K:V store, where the key is "makeKeyCodepointObj.makeKey" and the value is the call site of the function.)
When you call makeKeyCodepoint(), the VM has to, for each call, look up the prototype chain until it finds the variable. The variable is then resolved into the function call site. Because of scoping issues in JS, I don't think this is cached (or if it's cached, it'd be invalidated a lot), and a lookup has to happen every time. (I know in my JS engine, I tried to perform caching optimization for global object properties and I gave up).
TL;DR: Function lookups happen all the time when the function is a method of the global object. When a function is a method of an object, the lookup is cached.
If I am talking out of my arse, please feel free to correct me.
- Stratoscope 12y agoI don't think a global variable lookup is the reason for the difference. Here is the code that jsperf generates for the function version of the test: (Benchmark.uid1400600789397runScript || function() {})(); Benchmark.uid1400600789397createFunction = function(window, t14006007893970) { var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout; var r14006007893970, s14006007893970, m14006007893970 = this, f14006007893970 = m14006007893970.fn, i14006007893970 = m14006007893970.count, n14006007893970 = t14006007893970.ns; // Test Setup var makeKeyCodepoint = function(word) { var len = word.length; if (len > 255) { return undefined; } var i = len >> 2; return String.fromCharCode( (word.charCodeAt( 0) & 0x03) << 14 | (word.charCodeAt( i) & 0x03) << 12 | (word.charCodeAt( i+i) & 0x03) << 10 | (word.charCodeAt(i+i+i) & 0x03) << 8 | len ); }; s14006007893970 = n14006007893970.now(); while (i14006007893970--) { // Test Code var key; key = makeKeyCodepoint('www.wired.com'); key = makeKeyCodepoint('www.youtube.com'); key = makeKeyCodepoint('scorecardresearch.com'); key = makeKeyCodepoint('www.google-analytics.com'); } r14006007893970 = (n14006007893970.now() - s14006007893970) / 1e3; return { elapsed: r14006007893970, uid: "uid14006007893970" } } The test setup and the test itself are all part of the same function, and makeKeyCodepoint is a local variable in that function.
- chewxy 12y agovariable and property lookups do go through different processes (and hence optimized differently). variables are stored on activation records (a Context object in v8), while properties are stored in well, a magic hidden class type of thing (for v8). The latter can be cached, the former not so much. Plus, the former also creates quite a bit of garbage, so gc should theroetically kick in more often
- comex 12y agoI would expect any local variables to be stored in registers once the optimizer kicks in. I bet there's a different explanation.
- Stratoscope 12y agoThat is true, but I don't see how it explains the difference either. The function version and method version both reference a local variable, named makeKeyCodepoint or makeKeyCodepointObj respectively. The method version doesn't appear to have any fewer variable references than the function version.
- thedufer 12y agoThe first two tests on that jsperf don't show the same behavior, though, and they differ in the same way.