index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. // Make sure to run node with --expose-gc option!
  8. // The times are reliable if about 1% relative mean error if you run it:
  9. // * immediately after restart
  10. // * with 100% battery charge
  11. // * not connected to network
  12. /* eslint import/no-extraneous-dependencies: "off" */
  13. const Benchmark = require('benchmark');
  14. const diffBaseline = require('diff').diffLines;
  15. const diffImproved = require('../build/index.js').default;
  16. const testBaseline = (a, b) => {
  17. const benchmark = new Benchmark({
  18. fn() {
  19. diffBaseline(a, b);
  20. },
  21. name: 'baseline',
  22. onCycle() {
  23. global.gc(); // after run cycle
  24. },
  25. onStart() {
  26. global.gc(); // when benchmark starts
  27. },
  28. });
  29. benchmark.run({async: false});
  30. return benchmark.stats;
  31. };
  32. const testImproved = function(a, b) {
  33. const benchmark = new Benchmark({
  34. fn() {
  35. // Split string arguments to make fair comparison with baseline.
  36. const aItems = a.split('\n');
  37. const bItems = b.split('\n');
  38. const isCommon = (aIndex, bIndex) => aItems[aIndex] === bItems[bIndex];
  39. // This callback obviously does less than baseline `diff` package,
  40. // but avoiding double work and memory churn is the goal.
  41. // For example, `jest-diff` has had to split strings that `diff` joins.
  42. const foundSubsequence = () => {};
  43. diffImproved(aItems.length, bItems.length, isCommon, foundSubsequence);
  44. },
  45. name: 'improved',
  46. onCycle() {
  47. global.gc(); // after run cycle
  48. },
  49. onStart() {
  50. global.gc(); // when benchmark starts
  51. },
  52. });
  53. benchmark.run({async: false});
  54. return benchmark.stats;
  55. };
  56. const writeHeading2 = () => {
  57. console.log('## Benchmark time for `diff-sequences` versus `diff`\n');
  58. console.log('A ratio less than 1.0 means `diff-sequences` is faster.');
  59. };
  60. const writeHeading3 = n => {
  61. console.log(`\n### n = ${n}\n`);
  62. console.log('| name | % | ratio | improved | rme | baseline | rme |');
  63. console.log('| :--- | ---: | :--- | :--- | ---: | :--- | ---: |');
  64. };
  65. const writeRow = (name, percent, statsImproved, statsBaseline) => {
  66. const {mean: meanImproved, rme: rmeImproved} = statsImproved;
  67. const {mean: meanBaseline, rme: rmeBaseline} = statsBaseline;
  68. const ratio = meanImproved / meanBaseline;
  69. console.log(
  70. `| ${name} | ${percent}% | ${ratio.toFixed(
  71. 4,
  72. )} | ${meanImproved.toExponential(4)} | ${rmeImproved.toFixed(
  73. 2,
  74. )}% | ${meanBaseline.toExponential(4)} | ${rmeBaseline.toFixed(2)}% |`,
  75. );
  76. };
  77. const testDeleteInsert = (tenths, more, less) => {
  78. // For improved `diff-sequences` package, delete is often slower than insert.
  79. const statsDeleteImproved = testImproved(more, less);
  80. const statsDeleteBaseline = testBaseline(more, less);
  81. writeRow('delete', tenths * 10, statsDeleteImproved, statsDeleteBaseline);
  82. // For baseline `diff` package, many insertions is serious perf problem.
  83. // However, the benchmark package cannot accurately measure for large n.
  84. const statsInsertBaseline = testBaseline(less, more);
  85. const statsInsertImproved = testImproved(less, more);
  86. writeRow('insert', tenths * 10, statsInsertImproved, statsInsertBaseline);
  87. };
  88. const testChange = (tenths, expected, received) => {
  89. const statsImproved = testImproved(expected, received);
  90. const statsBaseline = testBaseline(expected, received);
  91. writeRow('change', tenths * 10, statsImproved, statsBaseline);
  92. };
  93. const getItems = (n, callback) => {
  94. const items = [];
  95. for (let i = 0; i !== n; i += 1) {
  96. const item = callback(i);
  97. if (typeof item === 'string') {
  98. items.push(item);
  99. }
  100. }
  101. return items.join('\n');
  102. };
  103. // Simulate change of property name which is usually not same line.
  104. // Expected: 0 1 2 3 4 5 6 7 8 9 and so on
  105. // Received: 1 2 3 4 x0 5 6 7 8 9 and so on
  106. const change2 = i => {
  107. const j = i % 10;
  108. return j === 4 ? `x${i - 4}` : j < 4 ? `${i + 1}` : `${i}`;
  109. };
  110. const testLength = n => {
  111. const all = getItems(n, i => `${i}`);
  112. writeHeading3(n);
  113. [2, 4, 8].forEach(tenth => {
  114. testDeleteInsert(tenth, all, getItems(n, i => i % 10 >= tenth && `${i}`));
  115. });
  116. testChange(1, all, getItems(n, i => (i % 10 === 0 ? `x${i}` : `${i}`)));
  117. testChange(2, all, getItems(n, change2));
  118. testChange(5, all, getItems(n, i => (i % 2 === 0 ? `x${i}` : `${i}`)));
  119. testChange(10, all, getItems(n, i => `x${i}`)); // simulate TDD
  120. };
  121. writeHeading2();
  122. testLength(20);
  123. testLength(200);
  124. testLength(2000);