Benchmark.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. var Class = require('./Class');
  2. var defaults = require('./defaults');
  3. var Promise = require('./Promise');
  4. var perfNow = require('./perfNow');
  5. var delay = require('./delay');
  6. var average = require('./average');
  7. var reduce = require('./reduce');
  8. var each = require('./each');
  9. var map = require('./map');
  10. var table = require('./table');
  11. var toStr = require('./toStr');
  12. exports = Class(
  13. {
  14. initialize: function Benchmark(fn) {
  15. var options =
  16. arguments.length > 1 && arguments[1] !== undefined
  17. ? arguments[1]
  18. : {};
  19. defaults(options, defOpts);
  20. this._fn = fn;
  21. this._isRunning = false;
  22. this._options = options;
  23. },
  24. run: function() {
  25. var _this = this;
  26. if (this._isRunning) {
  27. return this._pendingPromise;
  28. }
  29. this._reset();
  30. this._isRunning = true;
  31. var options = this._options;
  32. var pendingPromise = new Promise(function(resolve, reject) {
  33. var runSample = function() {
  34. var initCount =
  35. arguments.length > 0 && arguments[0] !== undefined
  36. ? arguments[0]
  37. : 1;
  38. delay(function() {
  39. _this
  40. ._runSample(initCount)
  41. .then(function(_ref) {
  42. var period = _ref.period,
  43. count = _ref.count;
  44. var sample = _this._sample;
  45. sample.push(period);
  46. if (
  47. perfNow() - _this._timeStamp <
  48. options.maxTime ||
  49. sample.length < options.minSamples
  50. ) {
  51. runSample(count);
  52. } else {
  53. resolve(_this._calcResult());
  54. }
  55. })
  56. .catch(function(err) {
  57. reject(err);
  58. });
  59. }, options.delay);
  60. };
  61. runSample();
  62. });
  63. function complete() {
  64. this._isRunning = false;
  65. delete this._pendingPromise;
  66. }
  67. pendingPromise.then(complete).catch(complete);
  68. this._pendingPromise = pendingPromise;
  69. return pendingPromise;
  70. },
  71. _reset: function() {
  72. this._timeStamp = perfNow();
  73. this._sample = [];
  74. },
  75. _calcResult: function() {
  76. var sample = this._sample;
  77. var result = {
  78. sample: sample,
  79. toString: function() {
  80. var hz = this.hz,
  81. rme = this.rme,
  82. name = this.name;
  83. var size = this.sample.length;
  84. return ''
  85. .concat(name, ' x ')
  86. .concat(
  87. formatNumber(hz.toFixed(hz < 100 ? 2 : 0)),
  88. ' ops/sec \xB1'
  89. )
  90. .concat(rme.toFixed(2), '% (')
  91. .concat(size, ' run')
  92. .concat(size === 1 ? '' : 's', ' sampled)');
  93. }
  94. };
  95. var size = sample.length;
  96. result.name = this._options.name || this._fn.name || 'anonymous';
  97. result.mean = average.apply(null, sample);
  98. function varOf(sum, x) {
  99. return sum + Math.pow(x - result.mean, 2);
  100. }
  101. result.variance = reduce(sample, varOf, 0) / (size - 1) || 0;
  102. result.deviation = Math.sqrt(result.variance);
  103. result.sem = result.deviation / Math.sqrt(size);
  104. var critical = tTable[Math.round(size - 1) || 1] || tTable.infinity;
  105. result.moe = result.sem * critical;
  106. result.rme = (result.moe / result.mean) * 100 || 0;
  107. result.hz = 1000 / result.mean;
  108. return result;
  109. },
  110. _runSample: function(count) {
  111. var _this2 = this;
  112. var options = this._options;
  113. var minTime = options.minTime;
  114. return new Promise(function(resolve, reject) {
  115. var runCycle = function(count) {
  116. delay(function() {
  117. var elapsed = 0;
  118. try {
  119. elapsed = _this2._runCycle(count);
  120. } catch (e) {
  121. return reject(e);
  122. }
  123. var period = elapsed / count;
  124. if (elapsed < minTime) {
  125. if (elapsed === 0) {
  126. count *= 100;
  127. } else {
  128. count += Math.ceil(
  129. (minTime - elapsed) / period
  130. );
  131. }
  132. runCycle(count);
  133. } else {
  134. resolve({
  135. count: count,
  136. period: period
  137. });
  138. }
  139. }, options.delay);
  140. };
  141. runCycle(count);
  142. });
  143. },
  144. _runCycle: function(count) {
  145. var fn = this._fn;
  146. var now = perfNow();
  147. while (count--) {
  148. fn();
  149. }
  150. return perfNow() - now;
  151. }
  152. },
  153. {
  154. all: function(benches, options) {
  155. var promises = [];
  156. each(benches, function(bench) {
  157. if (!(bench instanceof exports)) {
  158. bench = new exports(bench, options);
  159. }
  160. promises.push(bench.run());
  161. });
  162. return Promise.all(promises).then(function(results) {
  163. results.toString = function() {
  164. var data = map(results, function(_ref2, idx) {
  165. var name = _ref2.name,
  166. sample = _ref2.sample,
  167. hz = _ref2.hz,
  168. rme = _ref2.rme;
  169. var columns = [];
  170. var size = sample.length;
  171. columns.push(
  172. toStr(idx + 1),
  173. name || 'anonymous',
  174. formatNumber(hz.toFixed(hz < 100 ? 2 : 0)),
  175. '\xB1'.concat(rme.toFixed(2), '%'),
  176. ''
  177. .concat(size, ' run')
  178. .concat(size === 1 ? '' : 's')
  179. );
  180. return columns;
  181. });
  182. data.unshift([
  183. 'index',
  184. 'name',
  185. 'ops/sec',
  186. 'rme',
  187. 'sampled'
  188. ]);
  189. return table(data);
  190. };
  191. return results;
  192. });
  193. }
  194. }
  195. );
  196. var defOpts = {
  197. minTime: 50,
  198. maxTime: 5000,
  199. minSamples: 5,
  200. delay: 5,
  201. name: ''
  202. };
  203. var tTable = {
  204. '1': 12.706,
  205. '2': 4.303,
  206. '3': 3.182,
  207. '4': 2.776,
  208. '5': 2.571,
  209. '6': 2.447,
  210. '7': 2.365,
  211. '8': 2.306,
  212. '9': 2.262,
  213. '10': 2.228,
  214. '11': 2.201,
  215. '12': 2.179,
  216. '13': 2.16,
  217. '14': 2.145,
  218. '15': 2.131,
  219. '16': 2.12,
  220. '17': 2.11,
  221. '18': 2.101,
  222. '19': 2.093,
  223. '20': 2.086,
  224. '21': 2.08,
  225. '22': 2.074,
  226. '23': 2.069,
  227. '24': 2.064,
  228. '25': 2.06,
  229. '26': 2.056,
  230. '27': 2.052,
  231. '28': 2.048,
  232. '29': 2.045,
  233. '30': 2.042,
  234. infinity: 1.96
  235. };
  236. function formatNumber(number) {
  237. number = String(number).split('.');
  238. return (
  239. number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
  240. (number[1] ? '.' + number[1] : '')
  241. );
  242. }
  243. module.exports = exports;