| 1 | // Magnitude gives a best guess estimate of the runtime of a function. |
|---|
| 2 | // Magnitude.run can be called multiple times in a single test if desired. |
|---|
| 3 | // |
|---|
| 4 | // Usage: |
|---|
| 5 | // <script src="../resources/magnitude-perf.js"></script> |
|---|
| 6 | // <script> |
|---|
| 7 | // ... |
|---|
| 8 | // Magnitude.run(setup, test, expected) |
|---|
| 9 | // </script> |
|---|
| 10 | // |
|---|
| 11 | // -setup is the function to run once before the test-loop. It takes a magnitude argument |
|---|
| 12 | // that is the value of "n". |
|---|
| 13 | // -test is the code whose runtime is being tests. It also takes a magnitude argument. |
|---|
| 14 | // -expected is one of the run-time constants listed below (e.g. Magnitude.CONSTANT). |
|---|
| 15 | |
|---|
| 16 | if (window.testRunner) |
|---|
| 17 | testRunner.dumpAsText(); |
|---|
| 18 | |
|---|
| 19 | // Namespace. |
|---|
| 20 | var Magnitude = {}; |
|---|
| 21 | |
|---|
| 22 | // The description of what this test is testing. Gets prepended to the dumped markup. |
|---|
| 23 | Magnitude.description = function(description) |
|---|
| 24 | { |
|---|
| 25 | Magnitude._log(description); |
|---|
| 26 | } |
|---|
| 27 | |
|---|
| 28 | Magnitude.numPoints = 8; |
|---|
| 29 | Magnitude.millisecondsPerIteration = 25; |
|---|
| 30 | Magnitude.initialMagnitude = 1; |
|---|
| 31 | |
|---|
| 32 | Magnitude.CONSTANT = "O(1)"; |
|---|
| 33 | Magnitude.LINEAR = "O(n)"; |
|---|
| 34 | Magnitude.LOGARITHMIC = "O(log n)"; |
|---|
| 35 | Magnitude.POLYNOMIAL = ">=O(n^2)"; |
|---|
| 36 | Magnitude.INDETERMINATE = "indeterminate result"; |
|---|
| 37 | |
|---|
| 38 | Magnitude._order = {} |
|---|
| 39 | Magnitude._order[Magnitude.CONSTANT] = 1; |
|---|
| 40 | Magnitude._order[Magnitude.LINEAR] = 2; |
|---|
| 41 | Magnitude._order[Magnitude.LOGARITHMIC] = 3; |
|---|
| 42 | Magnitude._order[Magnitude.POLYNOMIAL] = 4; |
|---|
| 43 | Magnitude._order[Magnitude.INDETERMINATE] = 5; |
|---|
| 44 | |
|---|
| 45 | Magnitude._log = function(msg) |
|---|
| 46 | { |
|---|
| 47 | if (!Magnitude._container) |
|---|
| 48 | Magnitude._container = document.createElement('pre'); |
|---|
| 49 | Magnitude._container.appendChild(document.createTextNode(msg + '\n')); |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | Magnitude._debug = function(msg) |
|---|
| 53 | { |
|---|
| 54 | Magnitude._debugLog += msg + '\n'; |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | Magnitude.run = function(setup, test, expected) |
|---|
| 58 | { |
|---|
| 59 | Magnitude._debugLog = "\nDEBUG LOG:\n"; |
|---|
| 60 | |
|---|
| 61 | Magnitude._magnitudes = []; |
|---|
| 62 | for (var i = Magnitude.initialMagnitude; i < Magnitude.numPoints + Magnitude.initialMagnitude; i++) |
|---|
| 63 | Magnitude._magnitudes.push(Math.pow(2, i)); |
|---|
| 64 | |
|---|
| 65 | var numTries = 3; |
|---|
| 66 | Magnitude._run(setup, test, expected, numTries); |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | Magnitude._run = function(setup, test, expected, numTriesLeft) |
|---|
| 70 | { |
|---|
| 71 | Magnitude._iterations = {}; |
|---|
| 72 | for (var i = 0; i < Magnitude._magnitudes.length; i++) { |
|---|
| 73 | var magnitude = Magnitude._magnitudes[i]; |
|---|
| 74 | Magnitude._iterations[magnitude] = Magnitude._runIteration(setup, test, magnitude); |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | Magnitude._logIterationInfo(); |
|---|
| 78 | |
|---|
| 79 | numTriesLeft--; |
|---|
| 80 | var bigO = Magnitude._bigOGuess(); |
|---|
| 81 | var passed = Magnitude._order[bigO] <= Magnitude._order[expected]; |
|---|
| 82 | if (passed || numTriesLeft < 1) { |
|---|
| 83 | Magnitude._log(passed ? "PASS" : "FAIL: got " + bigO + " expected " + expected); |
|---|
| 84 | |
|---|
| 85 | // By default don't log detailed information to layout test results to keep expected results |
|---|
| 86 | // consistent from run to run. |
|---|
| 87 | if (!window.testRunner || !passed) |
|---|
| 88 | Magnitude._log(Magnitude._debugLog); |
|---|
| 89 | } else { |
|---|
| 90 | Magnitude._debug("numTriesLeft: " + numTriesLeft); |
|---|
| 91 | arguments.callee(setup, test, expected, numTriesLeft); |
|---|
| 92 | } |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | Magnitude._rSquared = function(opt_xTransform, opt_yTransform) |
|---|
| 96 | { |
|---|
| 97 | // Implement http://www.easycalculation.com/statistics/learn-correlation.php. |
|---|
| 98 | // x = magnitude |
|---|
| 99 | // y = iterations |
|---|
| 100 | var sumX = 0; |
|---|
| 101 | var sumY = 0; |
|---|
| 102 | var sumXSquared = 0; |
|---|
| 103 | var sumYSquared = 0; |
|---|
| 104 | var sumXTimesY = 0; |
|---|
| 105 | |
|---|
| 106 | var numPoints = Magnitude._magnitudes.length; |
|---|
| 107 | |
|---|
| 108 | for (var i = 0; i < numPoints; i++) { |
|---|
| 109 | var x = Magnitude._magnitudes[i]; |
|---|
| 110 | if (opt_xTransform) |
|---|
| 111 | x = opt_xTransform(x); |
|---|
| 112 | |
|---|
| 113 | var y = Magnitude.millisecondsPerIteration / Magnitude._iterations[Magnitude._magnitudes[i]]; |
|---|
| 114 | if (opt_yTransform) |
|---|
| 115 | y = opt_yTransform(y); |
|---|
| 116 | |
|---|
| 117 | sumX += x; |
|---|
| 118 | sumY += y; |
|---|
| 119 | sumXSquared += x * x; |
|---|
| 120 | sumYSquared += y * y; |
|---|
| 121 | sumXTimesY += x * y; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | var r = (numPoints * sumXTimesY - sumX * sumY) / |
|---|
| 125 | Math.sqrt((numPoints * sumXSquared - sumX * sumX) * |
|---|
| 126 | (numPoints * sumYSquared - sumY * sumY)); |
|---|
| 127 | |
|---|
| 128 | if (isNaN(r) || r == Math.Infinity) |
|---|
| 129 | r = 0; |
|---|
| 130 | |
|---|
| 131 | rSquared = r * r; |
|---|
| 132 | |
|---|
| 133 | var slope = (numPoints * sumXTimesY - sumX * sumY) / |
|---|
| 134 | (numPoints * sumXSquared - sumX * sumX); |
|---|
| 135 | var intercept = sumY / numPoints - slope * sumX / numPoints; |
|---|
| 136 | Magnitude._debug("numPoints " + numPoints + " slope " + slope + " intercept " + intercept + " rSquared " + rSquared); |
|---|
| 137 | |
|---|
| 138 | return rSquared; |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | Magnitude._logIterationInfo = function() |
|---|
| 142 | { |
|---|
| 143 | var iterationsArray = []; |
|---|
| 144 | for (var i = 0; i < Magnitude._magnitudes.length; i++) { |
|---|
| 145 | var magnitude = Magnitude._magnitudes[i]; |
|---|
| 146 | var iterations = Magnitude._iterations[magnitude]; |
|---|
| 147 | iterationsArray.push(iterations); |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | // Print out the magnitudes/arrays in CSV to afford easy copy-paste to a charting application. |
|---|
| 151 | Magnitude._debug("magnitudes: " + Magnitude._magnitudes.join(',')); |
|---|
| 152 | Magnitude._debug("iterations: " + iterationsArray.join(',')); |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | Magnitude._bigOGuess = function() |
|---|
| 156 | { |
|---|
| 157 | var rSquared = Magnitude._rSquared(); |
|---|
| 158 | var rSquaredXLog = Magnitude._rSquared(Math.log); |
|---|
| 159 | var rSquaredXYLog = Magnitude._rSquared(Math.log, Math.log); |
|---|
| 160 | Magnitude._debug("rSquared " + rSquared + " rSquaredXLog " + rSquaredXLog + " rSquaredXYLog " + rSquaredXYLog); |
|---|
| 161 | |
|---|
| 162 | var rSquaredMax = Math.max(rSquared, rSquaredXLog, rSquaredXYLog); |
|---|
| 163 | |
|---|
| 164 | var bigO = Magnitude.INDETERMINATE; |
|---|
| 165 | |
|---|
| 166 | // FIXME: These numbers were chosen arbitrarily. |
|---|
| 167 | // Are there a better value to check against? Do we need to check rSquaredMax? |
|---|
| 168 | if (rSquared < 0.8 && rSquaredMax < 0.9) |
|---|
| 169 | bigO = Magnitude.CONSTANT; |
|---|
| 170 | else if (rSquaredMax > 0.9) { |
|---|
| 171 | if (rSquared == rSquaredMax) |
|---|
| 172 | bigO = Magnitude.LINEAR; |
|---|
| 173 | else if (rSquaredXLog == rSquaredMax) |
|---|
| 174 | bigO = Magnitude.LOGARITHMIC; |
|---|
| 175 | else if (rSquaredXYLog == rSquaredMax) |
|---|
| 176 | bigO = Magnitude.POLYNOMIAL; |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | return bigO; |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | Magnitude._runIteration = function(setup, test, magnitude) |
|---|
| 183 | { |
|---|
| 184 | setup(magnitude); |
|---|
| 185 | |
|---|
| 186 | var debugStr = 'run iteration. magnitude ' + magnitude; |
|---|
| 187 | if (window.GCController) { |
|---|
| 188 | if (GCController.getJSObjectCount) |
|---|
| 189 | debugStr += " jsObjectCountBefore " + GCController.getJSObjectCount(); |
|---|
| 190 | |
|---|
| 191 | // Do a gc to reduce likelihood of gc during the test run. |
|---|
| 192 | // Do multiple gc's for V8 to clear DOM wrappers. |
|---|
| 193 | GCController.collect(); |
|---|
| 194 | GCController.collect(); |
|---|
| 195 | GCController.collect(); |
|---|
| 196 | |
|---|
| 197 | if (GCController.getJSObjectCount) |
|---|
| 198 | debugStr += " jsObjectCountAfter " + GCController.getJSObjectCount(); |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | Magnitude._debug(debugStr); |
|---|
| 202 | |
|---|
| 203 | var iterations = 0; |
|---|
| 204 | var nowFunction = window.performance && window.performance.now ? window.performance.now.bind(window.performance) : Date.now; |
|---|
| 205 | var start = nowFunction(); |
|---|
| 206 | while (nowFunction() - start < Magnitude.millisecondsPerIteration) { |
|---|
| 207 | test(magnitude); |
|---|
| 208 | iterations++; |
|---|
| 209 | } |
|---|
| 210 | return iterations; |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | window.addEventListener('load', function() { |
|---|
| 214 | // FIXME: Add Magnitude.waitUntilDone/notifyDone for tests that need to operate after the load event has fired. |
|---|
| 215 | if (window.testRunner) |
|---|
| 216 | document.body.innerHTML = ''; |
|---|
| 217 | document.body.appendChild(Magnitude._container); |
|---|
| 218 | }, false); |
|---|