baseline.benchmark.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /**
  2. * Module dependencies
  3. */
  4. var parley = require('../');
  5. var benchSync = require('./utils/bench-sync.util');
  6. var bench = require('./utils/bench.util');
  7. if (process.env.NODE_ENV !== 'production') {
  8. throw new Error('Benchmarks should be run with NODE_ENV=production!');
  9. }
  10. /**
  11. * baseline.benchmark.js
  12. *
  13. * A performance benchmark for Deferred instantiation and execution.
  14. */
  15. describe('baseline.benchmark.js', function() {
  16. // Set "timeout" and "slow" thresholds incredibly high
  17. // to avoid running into issues.
  18. this.slow(240000);
  19. this.timeout(240000);
  20. before(function(){
  21. console.log(
  22. ' • • • • • • \n'+
  23. ' • • o \n'+
  24. ' • b e n c h m a r k s • \n'+
  25. ' • (instantiation) ° \n'+
  26. '------------------------------------'+
  27. '');
  28. });
  29. // ╔═╗╦═╗ ╦╔╦╗╦ ╦╦═╗╔═╗╔═╗
  30. // ╠╣ ║╔╩╦╝ ║ ║ ║╠╦╝║╣ ╚═╗
  31. // ╚ ╩╩ ╚═ ╩ ╚═╝╩╚═╚═╝╚═╝
  32. // These functions and data are used in both benchmarks below.
  33. // (It's ok to have them up here--they are never mutated by the benchmarks or parley itself)
  34. var find = require('./fixtures/find.fixture');
  35. var validate = require('./fixtures/validate.fixture');
  36. var validateButWith9CustomMethods = require('./fixtures/validate-but-with-9-custom-methods.fixture');
  37. var NINE_CUSTOM_METHODS = {
  38. pretend1: function(x,y){ this._i = this._i || 0; this._i++; console.log(this._i, x, y); return this; },
  39. pretend2: function(x,y){ this._j = this._j || 0; this._j++; console.log(this._j, x, y); return this; },
  40. pretend3: function(x,y){ this._k = this._k || 0; this._k++; console.log(this._k, x, y); return this; },
  41. pretend4: function(x,y){ this._i = this._i || 0; this._i++; console.log(this._i, x, y); return this; },
  42. pretend5: function(x,y){ this._j = this._j || 0; this._j++; console.log(this._j, x, y); return this; },
  43. pretend6: function(x,y){ this._k = this._k || 0; this._k++; console.log(this._k, x, y); return this; },
  44. pretend7: function(x,y){ this._i = this._i || 0; this._i++; console.log(this._i, x, y); return this; },
  45. pretend8: function(x,y){ this._j = this._j || 0; this._j++; console.log(this._j, x, y); return this; },
  46. pretend9: function(x,y){ this._k = this._k || 0; this._k++; console.log(this._k, x, y); return this; },
  47. };
  48. // ╔═╗╔╗╔╔═╗╔═╗╔═╗╦ ╦╔═╗╔╦╗
  49. // ╚═╗║║║╠═╣╠═╝╚═╗╠═╣║ ║ ║
  50. // ╚═╝╝╚╝╩ ╩╩ ╚═╝╩ ╩╚═╝ ╩
  51. // Just some one-off snapshots run on a laptop.
  52. // For historical reports, see the history of this file on GitHub.
  53. //
  54. // ================================================================================================================
  55. // For the latest report...
  56. // ================================================================================================================
  57. //
  58. // * * * * * * * * * *
  59. // * See README.md! *
  60. // * * * * * * * * * *
  61. //
  62. // ================================================================================================================
  63. // ================================================================================================================
  64. // Jan 15, 2017 (take 6)
  65. // ================================================================================================================
  66. // After implementing auto-custom-method-attaching stuff:
  67. //
  68. // baseline.benchmark.js
  69. // • • • • • •
  70. // • • o
  71. // • b e n c h m a r k s •
  72. // • (instantiation) °
  73. // ------------------------------------
  74. // parley(handler)
  75. // • just_build#0 x 16,889,956 ops/sec ±2.42% (79 runs sampled)
  76. // ✓ should be performant enough (using benchSync())
  77. // parley(handler).exec(cb)
  78. // • build_AND_exec#0 x 1,612,188 ops/sec ±2.92% (80 runs sampled)
  79. // ✓ should be performant enough (using benchSync())
  80. // practical benchmark
  81. // • mock "find()"#0 x 34.82 ops/sec ±1.23% (74 runs sampled)
  82. // ✓ should be performant enough when calling fake "find" w/ .exec() (using bench())
  83. // • mock "find()"#0 x 34.68 ops/sec ±1.14% (74 runs sampled)
  84. // ✓ should be performant enough when calling NAKED fake "find" (using bench())
  85. // • mock "validate()"#0 x 621,578 ops/sec ±1.66% (85 runs sampled)
  86. // ✓ should be performant enough when calling fake "validate" w/ .exec() (using benchSync())
  87. // • mock "validate()"#0 x 7,467,393 ops/sec ±4.01% (86 runs sampled)
  88. // ✓ should be performant enough when calling NAKED "validate" (using benchSync())
  89. // ------------------------------------
  90. // • • • • • •
  91. // • • o
  92. // • < / b e n c h m a r k s > •
  93. // • °
  94. // o°
  95. //
  96. // ================================================================================================================
  97. // Jan 15, 2017 (take 5)
  98. // ================================================================================================================
  99. // baseline.benchmark.js
  100. // • • • • • •
  101. // • • o
  102. // • b e n c h m a r k s •
  103. // • (instantiation) °
  104. // ------------------------------------
  105. // parley(handler)
  106. // • just_build#0 x 18,016,705 ops/sec ±1.35% (86 runs sampled)
  107. // ✓ should be performant enough (using benchSync())
  108. // parley(handler).exec(cb)
  109. // • build_AND_exec#0 x 1,724,116 ops/sec ±1.95% (86 runs sampled)
  110. // ✓ should be performant enough (using benchSync())
  111. // practical benchmark
  112. // • mock "find()"#0 x 34.01 ops/sec ±1.00% (73 runs sampled)
  113. // ✓ should be performant enough when calling fake "find" w/ .exec() (using bench())
  114. // • mock "find()"#0 x 34.35 ops/sec ±1.06% (74 runs sampled)
  115. // ✓ should be performant enough when calling NAKED fake "find" (using bench())
  116. // • mock "validate()"#0 x 542,632 ops/sec ±2.00% (85 runs sampled)
  117. // ✓ should be performant enough when calling fake "validate" w/ .exec() (using benchSync())
  118. // • mock "validate()"#0 x 8,333,857 ops/sec ±5.42% (83 runs sampled)
  119. // ✓ should be performant enough when calling NAKED "validate" (using benchSync())
  120. // ------------------------------------
  121. // • • • • • •
  122. // • • o
  123. // • < / b e n c h m a r k s > •
  124. // • °
  125. // o°
  126. // ================================================================================================================
  127. // ================================================================================================================
  128. // Dec 20, 2016 (take 4): (after removing pretty-print, BEFORE switching to the constructor approach)
  129. // ================================================================================================================
  130. // baseline.benchmark.js
  131. // • • • • • •
  132. // • • o
  133. // • b e n c h m a r k s •
  134. // • °
  135. // ------------------------------------
  136. // parley(handler)
  137. // • just_build#0 x 527,939 ops/sec ±1.45% (85 runs sampled)
  138. // ✓ should be performant enough (using benchSync())
  139. // parley(handler).exec(cb)
  140. // • build_AND_exec#0 x 420,899 ops/sec ±1.61% (85 runs sampled)
  141. // ✓ should be performant enough (using benchSync())
  142. // practical benchmark
  143. // • mock "find()"#0 x 34.33 ops/sec ±0.90% (73 runs sampled)
  144. // ✓ should be performant enough when calling fake "find" w/ .exec() (using bench())
  145. // • mock "find()"#0 x 34.20 ops/sec ±0.95% (74 runs sampled)
  146. // ✓ should be performant enough when calling NAKED fake "find" (using bench())
  147. // • mock "validate()"#0 x 173,206 ops/sec ±3.02% (78 runs sampled)
  148. // ✓ should be performant enough when calling fake "validate" w/ .exec() (using benchSync())
  149. // • mock "validate()"#0 x 5,805,213 ops/sec ±4.04% (87 runs sampled)
  150. // ✓ should be performant enough when calling NAKED "validate" (using benchSync())
  151. // ------------------------------------
  152. // • • • • • •
  153. // • • o
  154. // • < / b e n c h m a r k s > •
  155. // • °
  156. // o°
  157. // ================================================================================================================
  158. // ╔═╗╔╗ ╔═╗╔═╗╦═╗╦ ╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
  159. // ║ ║╠╩╗╚═╗║╣ ╠╦╝╚╗╔╝╠═╣ ║ ║║ ║║║║╚═╗
  160. // ╚═╝╚═╝╚═╝╚═╝╩╚═ ╚╝ ╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
  161. //
  162. // • Removing pretty-print caused a huge performance increase
  163. // (33x instead of 317x slower than naked usage)
  164. //
  165. // • The additional time added by calling .exec() (vs. just building) is really only
  166. // visible now, AFTER removing pretty-print. It's a difference of 100,000 ops/sec.
  167. //
  168. // • By itself, switching to a Deferred constructor doesn't really improve performance
  169. // by THAT much. In some cases, it actually makes it worse (e.g. consistent decrease
  170. // in ops/sec for the first 2 benchmarks: just_build, build_and_exec). BUT it does
  171. // ever-so-slightly increase performance for both mock "find" mock "validate".
  172. // The question is: "why?" My guess is that it has something to do w/ accessing
  173. // `this` being slower than closure scope, and thus outweighing the gains of faster
  174. // construction. But even then, that wouldn't explain "just_build" being slower, so
  175. // it's probably not that...
  176. //
  177. // • Reducing the number of `this`/`self` references did not seem to make any sort of
  178. // meaningful difference on performance. (see 1d8b6239de2cd84ac76ee015d099c3c5a7013989)
  179. // *******UPDATE*******:
  180. // Actually -- it might... after removing two unncesssary `this` assignments from the
  181. // CONSTRUCTOR itself, performance for "just_build" shot up to where it was for the
  182. // original closure approach (and possibly a bit higher). Still, this is negligible
  183. // at the moment, but it's probably an effect that is more pronounced when overall ops/sec
  184. // are orders of magnitude higher (e.g. in the millions instead of the hundreds of thousands.)
  185. // Even then-- this is still less important than one might expect!
  186. //
  187. // • Aside: using a standalone function declaration (rather than invoking a self-calling function)
  188. // increases performance, like you might expect. Whether it's enough to matter is probably
  189. // situational. In the case of the commit where this observation was added to the code base,
  190. // it made a difference of ~1,000,000 ops/sec for the "NAKED mock validate" benchmark, and a
  191. // difference of ~20,000 ops/sec for the "validate w/ .exec()" benchmark. Worth it...?
  192. // No. Inline function declarations are NEVER worth it. But in some cases it might be worthwhile
  193. // to pull out shared futures used by self-invoking functions and drop them into a separate module.
  194. // *******UPDATE*******:
  195. // Just verified that, by moving the inline function to a separate file, performance for the
  196. // "NAKED mock validate" went up by an ADDITIONAL 2,000,000 ops/sec, and by an ADDITIONAL
  197. // ~20,000 ops/sec for the "validate w/ .exec()" benchmark. So, in conclusion, the answer to the
  198. // question of "Worth it?" is a resounding YES -- but only for a hot code path like this. For
  199. // other bits of code, the advantages of keeping the logic inline and avoiding a separate,
  200. // weirdly-specific file, are well worth it. And as for INLINE named function declarations?
  201. // They're still never worth it. Not only do they clutter the local scope and create scoffable
  202. // confusion about flow control (plus all the resulting bug potential), they aren't even as fast
  203. // as pulling out the code into a separate file. (Presumably this is because V8 has to make sure
  204. // the inline function can access the closure scope.)
  205. //
  206. // • It is worth noting that, despite how exciting the previous notes about pulling out self-invoking
  207. // functions was, when attempted with the mock "find" fixture, the relevant benchmarks showed no
  208. // noticeable improvement (i.e. because they're doing something asynchronous.)
  209. //
  210. // • Swapping out non-standard variable names (e.g. π) did not have any noticeable effect.
  211. //
  212. // • When using the constructor+prototype approach, accessing `this` is slow. It's not THAT bad,
  213. // but it is definitely a thing. Note that it is somewhat worse if in the constructor-- and
  214. // also worse on assignment (this.foo = x) than on access (var x = this.foo).
  215. //
  216. // • When using the closure approach, adding new methods dynamically is slow. This doesn't seem
  217. // to be because defining new functions is slow, per se. Rather it seems to have to do with
  218. // mutating the object after it's already been created. As a middle ground, it seems that relying
  219. // on Lodash's built-in optimizations is the way to go. Simply changing from `deferred.meta = ...`
  220. // to `_.extend(deferred, { meta: ... })` split the difference as far as performance. It improved
  221. // the performance of the 'mock validate with .exec()' benchmark by ~50k-60k ops/sec; i.e. ~20%)
  222. //
  223. // • STILL BE CAREFUL when using the closure approach. Even with the _.extend() trick, performance
  224. // decreases as more and more methods are added, whether or not they're within the same `.extend()`
  225. // call. BUT: What's still unclear is if this is due to function construction, or something else.
  226. // In this case, in practice, tricks would need to be used to circumvent the need for closure scope
  227. // access (i.e. prbly .bind()). But the answer to the question can actualy be figured out regardless--
  228. // by defining stub functions once per process.
  229. // *******UPDATE*******:
  230. // Well, the answer is that the function construction must have mattered somewhat, but even after
  231. // pulling the entire dictionary of methods out (and pretending they're static), the performance is
  232. // still lower than when _.extend() is used to attach only ONE method-- even when that one method is
  233. // defined inline. So, at the end of the day, we're just going to have to deal with the fact that,
  234. // if we add methods to the Deferred dynamically and construction-time, it's going to be slower and
  235. // slower for every additional method we add.
  236. //
  237. // • _.each() is slower than `for`, sometimes by a factor of 10. But this only matters in extreme
  238. // circumstances, where the logic being benchmarked is already very fast to begin with. So in
  239. // almost every case, it's still never worth using a `for` loop instead of `_.each()`.
  240. //
  241. // • See Spring-Autumn 2017 commit history of the parley repo in general for more insights.
  242. // ╔═╗╦ ╦╦╔╦╗╔═╗
  243. // ╚═╗║ ║║ ║ ║╣
  244. // ╚═╝╚═╝╩ ╩ ╚═╝
  245. describe('parley(handler)', function(){
  246. it('should be performant enough (using benchSync())', function (){
  247. benchSync('parley(handler)', [
  248. function just_build(){
  249. var deferred = parley(function(handlerCb) { return handlerCb(); });
  250. }
  251. ]);//</benchSync()>
  252. });
  253. });
  254. describe('parley(handler).exec(cb)', function(){
  255. it('should be performant enough (using benchSync())', function (){
  256. benchSync('parley(handler).exec(cb)', [
  257. function build_AND_exec(){
  258. var deferred = parley(function(handlerCb) { return handlerCb(); });
  259. deferred.exec(function (err) {
  260. if (err) {
  261. console.error('Unexpected error running benchmark:',err);
  262. }//>-
  263. // Note: Since the handler is blocking, we actually make
  264. // it in here within one tick of the event loop.
  265. });
  266. }
  267. ]);//</benchSync()>
  268. });
  269. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  270. // For additional permutations using bench() +/- extra setImmediate() calls,
  271. // see the commit history of this file. As it turn out, the setImmediate()
  272. // calls just add weight and make it harder to judge the accuracy of results.
  273. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  274. });//</ describe: parley(handler().exec(cb) )
  275. describe('parley(handler, undefined, {...}) (w/ 9 custom methods)', function(){
  276. it('should be performant enough (using benchSync())', function (){
  277. benchSync('parley(handler, undefined, {...})', [
  278. function just_build_with_9_custom_methods(){
  279. var deferred = parley(function(handlerCb) { return handlerCb(); }, undefined, NINE_CUSTOM_METHODS);
  280. }
  281. ]);//</benchSync()>
  282. });
  283. });//</ describe >
  284. describe('parley(handler, undefined, {...}).exec(cb) (w/ 9 custom methods)', function(){
  285. it('should be performant enough (using benchSync())', function (){
  286. benchSync('parley(handler, undefined, {...}).exec(cb)', [
  287. function build_AND_exec_with_9_custom_methods(){
  288. var deferred = parley(function(handlerCb) { return handlerCb(); }, undefined, NINE_CUSTOM_METHODS);
  289. deferred.exec(function (err) {
  290. if (err) {
  291. console.error('Unexpected error running benchmark:',err);
  292. }//>-
  293. // Note: Since the handler is blocking, we actually make
  294. // it in here within one tick of the event loop.
  295. });
  296. }
  297. ]);//</benchSync()>
  298. });
  299. });//</ describe >
  300. describe('practical benchmark', function(){
  301. var DOES_CURRENT_NODE_VERSION_SUPPORT_AWAIT = process.version.match(/^v8\./);
  302. if (DOES_CURRENT_NODE_VERSION_SUPPORT_AWAIT) {
  303. it('should be performant enough when calling fake "find" w/ `await` (using bench())', function (done){
  304. bench('mock "await find()"', [
  305. eval(
  306. '(()=>{\n'+
  307. ' return async function (next){\n'+
  308. ' var result;\n'+
  309. ' try {\n'+
  310. ' result = await find({ where: {id:3, x:30} });\n'+
  311. ' } catch (err) {\n'+
  312. ' return next(err);\n'+
  313. ' }\n'+
  314. ' return next();\n'+
  315. ' }\n'+
  316. '})()\n'
  317. )
  318. ], done);
  319. });
  320. }
  321. it('should be performant enough when calling fake "find" w/ .exec() (using bench())', function (done){
  322. bench('mock "find().exec()"', [
  323. function (next){
  324. find({ where: {id:3, x:30} })
  325. .exec(function (err, result) {
  326. if (err) { return next(err); }
  327. return next();
  328. });
  329. }
  330. ], done);
  331. });
  332. it('should be performant enough when calling NAKED fake "find" (using bench())', function (done){
  333. bench('mock "find(..., explicitCb)"', [
  334. function (next){
  335. find({ where: {id:3, x:30} }, function (err, result) {
  336. if (err) { return next(err); }
  337. return next();
  338. });
  339. }
  340. ], done);
  341. });
  342. it('should be performant enough when calling fake "validate" w/ .exec() (using benchSync())', function (){
  343. benchSync('mock "validate().exec()"', [
  344. function (){
  345. validate()
  346. .exec(function (err) {
  347. if (err) {
  348. console.error('Unexpected error running benchmark:',err);
  349. }//>-
  350. // Note: Since the handler is blocking, we actually make
  351. // it in here within one tick of the event loop.
  352. });
  353. }
  354. ]);
  355. });
  356. it('should be performant enough when calling fake "validate" w/ .exec() + uncaught exception handler (using benchSync())', function (){
  357. benchSync('mock "validate().exec()"', [
  358. function (){
  359. validate()
  360. .exec(function (err) {
  361. if (err) {
  362. console.error('Unexpected error running benchmark:',err);
  363. }//>-
  364. // Note: Since the handler is blocking, we actually make
  365. // it in here within one tick of the event loop.
  366. }, function (){
  367. console.error('Consistency violation: This should never happen: Something is broken!');
  368. throw new Error('Consistency violation: This should never happen: Something is broken!');
  369. });
  370. }
  371. ]);
  372. });
  373. it('should be performant enough calling fake "validateButWith9CustomMethods" w/ .exec() (using benchSync())', function (){
  374. benchSync('mock "validateButWith9CustomMethods().exec()"', [
  375. function (){
  376. validateButWith9CustomMethods()
  377. .exec(function (err) {
  378. if (err) {
  379. console.error('Unexpected error running benchmark:',err);
  380. }//>-
  381. // Note: Since the handler is blocking, we actually make
  382. // it in here within one tick of the event loop.
  383. });
  384. }
  385. ]);
  386. });
  387. it('should be performant enough when calling NAKED "validate" (using benchSync())', function (){
  388. benchSync('mock "validate(..., explicitCb)"', [
  389. function (){
  390. validate(function (err) {
  391. if (err) {
  392. console.error('Unexpected error running benchmark:',err);
  393. }//>-
  394. // Note: Since the handler is blocking, we actually make
  395. // it in here within one tick of the event loop.
  396. });
  397. }
  398. ]);
  399. });
  400. });//</describe>
  401. after(function(){
  402. console.log(
  403. '------------------------------------\n'+
  404. ' • • • • • • \n'+
  405. ' • • o \n'+
  406. ' • < / b e n c h m a r k s > • \n'+
  407. ' • ° \n'+
  408. ' o° \n'+
  409. '');
  410. });
  411. });//</describe (top-level) >