Repository navigation
module.exports = {} approach and objects with fast / slow properties #11430
Description
Activity
Considering that it takes nanoseconds to access them, I wonder if this improvement is visible at all.
@indutny ... in the benchmarks that I have run, there has been an average improvement of about 5% when switching, particularly on modules that are frequently used.
- addedperformanceIssues and PRs related to the performance of Node.js.Issues and PRs related to the performance of Node.js.
on Feb 16, 2017 I just wanted to add that using
module.exports = ....instead of pepperedexports.* = ...statements is not only about performance but about organization (a nice side effect). It's much easier to see what is being exported when it's all in one place.@indutny If I get it right, it takes nanoseconds if a user imports a separate property or a method, like
const read = require('fs').read;. However, if a user does likeconst fs = require('fs');, the wholemodule.exportsis imported. If it is a slow object and a user uses anyfs.*property in a heavy loop, there could be a performance loss.Hmm, it is strange. It seems all the imported core modules are fast objects:
require('repl')._builtinLibs.forEach((name) => { const mod = require(name); const keyNum = Reflect.ownKeys(mod).length; console.log(`${name}: ${%HasFastProperties(mod) ? 'fast' : 'slow'} (${keyNum} keys)`); });
assert: fast (17 keys) buffer: fast (5 keys) child_process: fast (9 keys) cluster: fast (15 keys) crypto: fast (44 keys) dgram: fast (3 keys) dns: fast (42 keys) domain: fast (5 keys) events: fast (8 keys) fs: fast (81 keys) http: fast (13 keys) https: fast (6 keys) net: fast (12 keys) os: fast (19 keys) path: fast (15 keys) punycode: fast (6 keys) querystring: fast (7 keys) readline: fast (7 keys) repl: fast (8 keys) stream: fast (10 keys) string_decoder: fast (1 keys) tls: fast (17 keys) tty: fast (3 keys) url: fast (9 keys) util: fast (28 keys) v8: fast (3 keys) vm: fast (8 keys) zlib: fast (74 keys)Maybe v8 somehow optimizes them before/after importing or in another time.
@vsemozhetbyt You might be interested in a look at https://github.com/nodejs/node/blob/master/deps/v8/test/mjsunit/fast-non-keyed.js and https://github.com/nodejs/node/blob/master/deps/v8/test/mjsunit/es6/symbols.js
Reacted by Vse Mozhe Buty@joyeecheung Oh, I see. Thank you. It seems the limit of fast non-keyed incrementation is 133 keys:
Test (click me):
const obj = {}; obj.x001 = 0; obj.x002 = 0; obj.x003 = 0; obj.x004 = 0; obj.x005 = 0; obj.x006 = 0; obj.x007 = 0; obj.x008 = 0; obj.x009 = 0; obj.x010 = 0; obj.x011 = 0; obj.x012 = 0; obj.x013 = 0; obj.x014 = 0; obj.x015 = 0; obj.x016 = 0; obj.x017 = 0; obj.x018 = 0; obj.x019 = 0; obj.x020 = 0; obj.x021 = 0; obj.x022 = 0; obj.x023 = 0; obj.x024 = 0; obj.x025 = 0; obj.x026 = 0; obj.x027 = 0; obj.x028 = 0; obj.x029 = 0; obj.x030 = 0; obj.x031 = 0; obj.x032 = 0; obj.x033 = 0; obj.x034 = 0; obj.x035 = 0; obj.x036 = 0; obj.x037 = 0; obj.x038 = 0; obj.x039 = 0; obj.x040 = 0; obj.x041 = 0; obj.x042 = 0; obj.x043 = 0; obj.x044 = 0; obj.x045 = 0; obj.x046 = 0; obj.x047 = 0; obj.x048 = 0; obj.x049 = 0; obj.x050 = 0; obj.x051 = 0; obj.x052 = 0; obj.x053 = 0; obj.x054 = 0; obj.x055 = 0; obj.x056 = 0; obj.x057 = 0; obj.x058 = 0; obj.x059 = 0; obj.x060 = 0; obj.x061 = 0; obj.x062 = 0; obj.x063 = 0; obj.x064 = 0; obj.x065 = 0; obj.x066 = 0; obj.x067 = 0; obj.x068 = 0; obj.x069 = 0; obj.x070 = 0; obj.x071 = 0; obj.x072 = 0; obj.x073 = 0; obj.x074 = 0; obj.x075 = 0; obj.x076 = 0; obj.x077 = 0; obj.x078 = 0; obj.x079 = 0; obj.x080 = 0; obj.x081 = 0; obj.x082 = 0; obj.x083 = 0; obj.x084 = 0; obj.x085 = 0; obj.x086 = 0; obj.x087 = 0; obj.x088 = 0; obj.x089 = 0; obj.x090 = 0; obj.x091 = 0; obj.x092 = 0; obj.x093 = 0; obj.x094 = 0; obj.x095 = 0; obj.x096 = 0; obj.x097 = 0; obj.x098 = 0; obj.x099 = 0; obj.x100 = 0; obj.x101 = 0; obj.x102 = 0; obj.x103 = 0; obj.x104 = 0; obj.x105 = 0; obj.x106 = 0; obj.x107 = 0; obj.x108 = 0; obj.x109 = 0; obj.x110 = 0; obj.x111 = 0; obj.x112 = 0; obj.x113 = 0; obj.x114 = 0; obj.x115 = 0; obj.x116 = 0; obj.x117 = 0; obj.x118 = 0; obj.x119 = 0; obj.x120 = 0; obj.x121 = 0; obj.x122 = 0; obj.x123 = 0; obj.x124 = 0; obj.x125 = 0; obj.x126 = 0; obj.x127 = 0; obj.x128 = 0; obj.x129 = 0; obj.x130 = 0; obj.x131 = 0; obj.x132 = 0; obj.x133 = 0; console.log(%HasFastProperties(obj)); obj.x134 = 0; console.log(%HasFastProperties(obj));
true falseBut actually this is another limit here: limit of the overall number of keys:
var o1 = {}; var o2 = {}; for (var i = 1; %HasFastProperties(o2); i++) { o1['p' + i] = 1; o2 = JSON.parse(JSON.stringify(o1)); } console.log(Object.keys(o2).length);
134Sorry, I will close it as not relevant for this case.
There were some PRs recently that refactor
module.exportsfilling from incremental to atomic.There also were some concerns about the gain of this approach.
It came to pass that I've recently read some stuff about v8 optimization concerning objects. There is a distinction between objects with fast and slow properties, and one way to make an object slow is exactly incremental property definition.
So I've written a simple test to define how many added properties make an object slow:
Then I've run this code with some Node.js versions:
You could also check this code in the some last Node.js:
This is also a double benchmark, with benchmark.js and a naive approach:
I am not completely sure this should be considered as a reason for the mentioned refactoring, but I feel somehow obliged to share this data.
cc @nodejs/v8