| // Magnitude gives a best guess estimate of the runtime of a function. |
| // Magnitude.run can be called multiple times in a single test if desired. |
| // |
| // Usage: |
| // <script src="../resources/magnitude-perf.js"></script> |
| // <script> |
| // ... |
| // Magnitude.run(setup, test, expected) |
| // </script> |
| // |
| // -setup is the function to run once before the test-loop. It takes a magnitude argument |
| // that is the value of "n". |
| // -test is the code whose runtime is being tests. It also takes a magnitude argument. |
| // -expected is one of the run-time constants listed below (e.g. Magnitude.CONSTANT). |
| |
| if (window.testRunner) |
| testRunner.dumpAsText(); |
| |
| // Namespace. |
| var Magnitude = {}; |
| |
| // The description of what this test is testing. Gets prepended to the dumped markup. |
| Magnitude.description = function(description) |
| { |
| Magnitude._log(description); |
| } |
| |
| Magnitude.numPoints = 8; |
| Magnitude.millisecondsPerIteration = 25; |
| Magnitude.initialMagnitude = 1; |
| |
| Magnitude.CONSTANT = "O(1)"; |
| Magnitude.LINEAR = "O(n)"; |
| Magnitude.LOGARITHMIC = "O(log n)"; |
| Magnitude.POLYNOMIAL = ">=O(n^2)"; |
| Magnitude.INDETERMINATE = "indeterminate result"; |
| |
| Magnitude._order = {} |
| Magnitude._order[Magnitude.CONSTANT] = 1; |
| Magnitude._order[Magnitude.LINEAR] = 2; |
| Magnitude._order[Magnitude.LOGARITHMIC] = 3; |
| Magnitude._order[Magnitude.POLYNOMIAL] = 4; |
| Magnitude._order[Magnitude.INDETERMINATE] = 5; |
| |
| Magnitude._log = function(msg) |
| { |
| if (!Magnitude._container) |
| Magnitude._container = document.createElement('pre'); |
| Magnitude._container.appendChild(document.createTextNode(msg + '\n')); |
| } |
| |
| Magnitude._debug = function(msg) |
| { |
| Magnitude._debugLog += msg + '\n'; |
| } |
| |
| Magnitude.run = function(setup, test, expected) |
| { |
| Magnitude._debugLog = "\nDEBUG LOG:\n"; |
| |
| Magnitude._magnitudes = []; |
| for (var i = Magnitude.initialMagnitude; i < Magnitude.numPoints + Magnitude.initialMagnitude; i++) |
| Magnitude._magnitudes.push(Math.pow(2, i)); |
| |
| var numTries = 3; |
| Magnitude._run(setup, test, expected, numTries); |
| } |
| |
| Magnitude._run = function(setup, test, expected, numTriesLeft) |
| { |
| Magnitude._iterations = {}; |
| for (var i = 0; i < Magnitude._magnitudes.length; i++) { |
| var magnitude = Magnitude._magnitudes[i]; |
| Magnitude._iterations[magnitude] = Magnitude._runIteration(setup, test, magnitude); |
| } |
| |
| Magnitude._logIterationInfo(); |
| |
| numTriesLeft--; |
| var bigO = Magnitude._bigOGuess(); |
| var passed = Magnitude._order[bigO] <= Magnitude._order[expected]; |
| if (passed || numTriesLeft < 1) { |
| Magnitude._log(passed ? "PASS" : "FAIL: got " + bigO + " expected " + expected); |
| |
| // By default don't log detailed information to layout test results to keep expected results |
| // consistent from run to run. |
| if (!window.testRunner || !passed) |
| Magnitude._log(Magnitude._debugLog); |
| } else { |
| Magnitude._debug("numTriesLeft: " + numTriesLeft); |
| arguments.callee(setup, test, expected, numTriesLeft); |
| } |
| } |
| |
| Magnitude._rSquared = function(opt_xTransform, opt_yTransform) |
| { |
| // Implement http://www.easycalculation.com/statistics/learn-correlation.php. |
| // x = magnitude |
| // y = iterations |
| var sumX = 0; |
| var sumY = 0; |
| var sumXSquared = 0; |
| var sumYSquared = 0; |
| var sumXTimesY = 0; |
| |
| var numPoints = Magnitude._magnitudes.length; |
| |
| for (var i = 0; i < numPoints; i++) { |
| var x = Magnitude._magnitudes[i]; |
| if (opt_xTransform) |
| x = opt_xTransform(x); |
| |
| var y = Magnitude.millisecondsPerIteration / Magnitude._iterations[Magnitude._magnitudes[i]]; |
| if (opt_yTransform) |
| y = opt_yTransform(y); |
| |
| sumX += x; |
| sumY += y; |
| sumXSquared += x * x; |
| sumYSquared += y * y; |
| sumXTimesY += x * y; |
| } |
| |
| var r = (numPoints * sumXTimesY - sumX * sumY) / |
| Math.sqrt((numPoints * sumXSquared - sumX * sumX) * |
| (numPoints * sumYSquared - sumY * sumY)); |
| |
| if (isNaN(r) || r == Math.Infinity) |
| r = 0; |
| |
| rSquared = r * r; |
| |
| var slope = (numPoints * sumXTimesY - sumX * sumY) / |
| (numPoints * sumXSquared - sumX * sumX); |
| var intercept = sumY / numPoints - slope * sumX / numPoints; |
| Magnitude._debug("numPoints " + numPoints + " slope " + slope + " intercept " + intercept + " rSquared " + rSquared); |
| |
| return rSquared; |
| } |
| |
| Magnitude._logIterationInfo = function() |
| { |
| var iterationsArray = []; |
| for (var i = 0; i < Magnitude._magnitudes.length; i++) { |
| var magnitude = Magnitude._magnitudes[i]; |
| var iterations = Magnitude._iterations[magnitude]; |
| iterationsArray.push(iterations); |
| } |
| |
| // Print out the magnitudes/arrays in CSV to afford easy copy-paste to a charting application. |
| Magnitude._debug("magnitudes: " + Magnitude._magnitudes.join(',')); |
| Magnitude._debug("iterations: " + iterationsArray.join(',')); |
| } |
| |
| Magnitude._bigOGuess = function() |
| { |
| var rSquared = Magnitude._rSquared(); |
| var rSquaredXLog = Magnitude._rSquared(Math.log); |
| var rSquaredXYLog = Magnitude._rSquared(Math.log, Math.log); |
| Magnitude._debug("rSquared " + rSquared + " rSquaredXLog " + rSquaredXLog + " rSquaredXYLog " + rSquaredXYLog); |
| |
| var rSquaredMax = Math.max(rSquared, rSquaredXLog, rSquaredXYLog); |
| |
| var bigO = Magnitude.INDETERMINATE; |
| |
| // FIXME: These numbers were chosen arbitrarily. |
| // Are there a better value to check against? Do we need to check rSquaredMax? |
| if (rSquared < 0.8 && rSquaredMax < 0.9) |
| bigO = Magnitude.CONSTANT; |
| else if (rSquaredMax > 0.9) { |
| if (rSquared == rSquaredMax) |
| bigO = Magnitude.LINEAR; |
| else if (rSquaredXLog == rSquaredMax) |
| bigO = Magnitude.LOGARITHMIC; |
| else if (rSquaredXYLog == rSquaredMax) |
| bigO = Magnitude.POLYNOMIAL; |
| } |
| |
| return bigO; |
| } |
| |
| Magnitude._runIteration = function(setup, test, magnitude) |
| { |
| setup(magnitude); |
| |
| var debugStr = 'run iteration. magnitude ' + magnitude; |
| if (window.GCController) { |
| if (GCController.getJSObjectCount) |
| debugStr += " jsObjectCountBefore " + GCController.getJSObjectCount(); |
| |
| // Do a gc to reduce likelihood of gc during the test run. |
| // Do multiple gc's for V8 to clear DOM wrappers. |
| GCController.collect(); |
| GCController.collect(); |
| GCController.collect(); |
| |
| if (GCController.getJSObjectCount) |
| debugStr += " jsObjectCountAfter " + GCController.getJSObjectCount(); |
| } |
| |
| Magnitude._debug(debugStr); |
| |
| var iterations = 0; |
| var nowFunction = window.performance && window.performance.now ? window.performance.now.bind(window.performance) : Date.now; |
| var start = nowFunction(); |
| while (nowFunction() - start < Magnitude.millisecondsPerIteration) { |
| test(magnitude); |
| iterations++; |
| } |
| return iterations; |
| } |
| |
| window.addEventListener('load', function() { |
| // FIXME: Add Magnitude.waitUntilDone/notifyDone for tests that need to operate after the load event has fired. |
| if (window.testRunner) |
| document.body.innerHTML = ''; |
| document.body.appendChild(Magnitude._container); |
| }, false); |