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.
5 // <script src="../resources/magnitude-perf.js"></script>
8 // Magnitude.run(setup, test, expected)
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).
16 if (window.layoutTestController)
17 layoutTestController.dumpAsText();
22 // The description of what this test is testing. Gets prepended to the dumped markup.
23 Magnitude.description = function(description)
25 Magnitude._log(description);
28 Magnitude._numPoints = 10;
29 Magnitude._minIterations = 5;
30 Magnitude._maxIterations = 1000;
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";
38 Magnitude._log = function(msg)
40 if (!Magnitude._container)
41 Magnitude._container = document.createElement('pre');
42 Magnitude._container.appendChild(document.createTextNode(msg + '\n'));
45 Magnitude._debug = function(msg)
47 Magnitude._debugLog += msg + '\n';
50 Magnitude.run = function(setup, test, expected)
52 Magnitude._debugLog = "\nDEBUG LOG:\n";
54 Magnitude._magnitudes = [];
55 for (var i = 0; i < Magnitude._numPoints; i++) {
56 Magnitude._magnitudes.push(Math.pow(2, i));
59 var milliseconds = 50;
60 var runsPerIteration = 1;
62 Magnitude._run(setup, test, expected, milliseconds, runsPerIteration, numTries);
65 Magnitude._run = function(setup, test, expected, milliseconds, runsPerIteration, numTriesLeft)
67 Magnitude._iterations = {};
68 var maxMagnitude = Magnitude._magnitudes[Magnitude._magnitudes.length - 1];
70 // We want the largest magnitude to do between Magnitude._minIterations and Magnitude._maxIterations iterations.
71 // If it's too fast, we increase the runsPerIteration to do more runs per iteration.
72 // If it's too slow, we increase milliseconds to give each iteration more time.
74 var iterations = Magnitude._runIteration(setup, test, maxMagnitude, milliseconds, runsPerIteration);
75 Magnitude._debug("iterations " + iterations);
77 // If we get too few or too many on the largest magnitude iterations, then we can't trust this run.
78 // Too many runs means the the test loop itself may be taking more time than running the test.
79 if (iterations <= Magnitude._minIterations)
80 milliseconds = Math.max(2, Math.floor(Magnitude._minIterations / iterations)) * milliseconds;
81 else if (iterations > Magnitude._maxIterations)
82 runsPerIteration = Math.max(2, Math.floor(iterations / Magnitude._maxIterations)) * runsPerIteration;
84 Magnitude._iterations[maxMagnitude] = iterations;
89 for (var i = 0; i < Magnitude._magnitudes.length - 1; i++) {
90 var magnitude = Magnitude._magnitudes[i];
91 Magnitude._iterations[magnitude] = Magnitude._runIteration(setup, test, magnitude, milliseconds, runsPerIteration);
94 Magnitude._logIterationInfo(milliseconds, runsPerIteration);
97 var bigO = Magnitude._bigOGuess(milliseconds);
98 if (bigO == expected || numTriesLeft < 1) {
99 Magnitude._log(bigO == expected ? "PASS" : "FAIL: got " + bigO + " expected " + expected);
101 // By default don't log detailed information to layout test results to keep expected results
102 // consistent from run to run.
103 if (!window.layoutTestController || bigO != expected)
104 Magnitude._log(Magnitude._debugLog);
106 Magnitude._debug("numTriesLeft: " + numTriesLeft);
107 arguments.callee(setup, test, expected, milliseconds, runsPerIteration, numTriesLeft);
111 Magnitude._rSquared = function(milliseconds, opt_xTransform, opt_yTransform)
113 // Implement http://www.easycalculation.com/statistics/learn-correlation.php.
122 var numPoints = Magnitude._magnitudes.length;
124 for (var i = 0; i < numPoints; i++) {
125 var x = Magnitude._magnitudes[i];
127 x = opt_xTransform(x);
129 var y = milliseconds / Magnitude._iterations[Magnitude._magnitudes[i]];
131 y = opt_yTransform(y);
135 sumXSquared += x * x;
136 sumYSquared += y * y;
140 var r = (numPoints * sumXTimesY - sumX * sumY) /
141 Math.sqrt((numPoints * sumXSquared - sumX * sumX) *
142 (numPoints * sumYSquared - sumY * sumY));
144 if (isNaN(r) || r == Math.Infinity)
149 var slope = (numPoints * sumXTimesY - sumX * sumY) /
150 (numPoints * sumXSquared - sumX * sumX);
151 var intercept = sumY / numPoints - slope * sumX / numPoints;
152 Magnitude._debug("numPoints " + numPoints + " slope " + slope + " intercept " + intercept + " rSquared " + rSquared);
157 Magnitude._logIterationInfo = function(milliseconds, runsPerIteration)
159 var iterationsArray = [];
160 for (var i = 0; i < Magnitude._magnitudes.length; i++) {
161 var magnitude = Magnitude._magnitudes[i];
162 var iterations = Magnitude._iterations[magnitude];
163 iterationsArray.push(iterations);
164 Magnitude._debug("magnitude: " + magnitude + " iterations: " + iterations + " runsPerIteration " + runsPerIteration +
165 " loop-time " + milliseconds + " time/iteration(ms): " + milliseconds / iterations);
168 // Print out the magnitudes/arrays in CSV to afford easy copy-paste to a charting application.
169 Magnitude._debug("magnitudes: " + Magnitude._magnitudes.join(','));
170 Magnitude._debug("iterations: " + iterationsArray.join(','));
171 Magnitude._debug("milliseconds/iteration: " + iterationsArray.map(function(iterations) {return milliseconds / iterations}).join(','));
174 Magnitude._bigOGuess = function(milliseconds)
176 var rSquared = Magnitude._rSquared(milliseconds);
177 var rSquaredXLog = Magnitude._rSquared(milliseconds, Math.log);
178 var rSquaredXYLog = Magnitude._rSquared(milliseconds, Math.log, Math.log);
179 Magnitude._debug("rSquared " + rSquared + " rSquaredXLog " + rSquaredXLog + " rSquaredXYLog " + rSquaredXYLog);
181 var rSquaredMax = Math.max(rSquared, rSquaredXLog, rSquaredXYLog);
183 var bigO = Magnitude.INDETERMINATE;
185 // FIXME: These numbers were chosen arbitrarily.
186 // Are there a better value to check against? Do we need to check rSquaredMax?
187 if (rSquared < 0.8 && rSquaredMax < 0.9)
188 bigO = Magnitude.CONSTANT;
189 else if (rSquaredMax > 0.9) {
190 if (rSquared == rSquaredMax)
191 bigO = Magnitude.LINEAR;
192 else if (rSquaredXLog == rSquaredMax)
193 bigO = Magnitude.LOGARITHMIC;
194 else if (rSquaredXYLog == rSquaredMax)
195 bigO = Magnitude.POLYNOMIAL;
201 Magnitude._runIteration = function(setup, test, magnitude, milliseconds, runsPerIteration)
205 var debugStr = 'run iteration. magnitude ' + magnitude + " milliseconds " + milliseconds + " runsPerIteration " + runsPerIteration;
206 if (window.GCController) {
207 if (GCController.getJSObjectCount)
208 debugStr += " jsObjectCountBefore " + GCController.getJSObjectCount();
210 // Do a gc to reduce likelihood of gc during the test run.
211 // Do multiple gc's for V8 to clear DOM wrappers.
212 GCController.collect();
213 GCController.collect();
214 GCController.collect();
216 if (GCController.getJSObjectCount)
217 debugStr += " jsObjectCountAfter " + GCController.getJSObjectCount();
220 Magnitude._debug(debugStr);
223 if (window.chromium) {
224 // FIXME: If using microseconds turns out to be less flaky, expose microseconds
225 // from JSC or layoutTestController and use them. Otherwise, get rid of this block.
226 var microseconds = milliseconds * 1000;
227 var interval = new chromium.Interval();
229 while (interval.microseconds() < microseconds) {
230 // Loop runsPerIteration times to reduce errors due to the overhead and granularity of the Date object.
231 for (var i = 0; i < runsPerIteration; i++) {
238 var start = Date.now();
239 while (Date.now() - start < milliseconds) {
240 // Loop runsPerIteration times to reduce errors due to the overhead and granularity of the Date object.
241 for (var i = 0; i < runsPerIteration; i++) {
250 window.addEventListener('load', function() {
251 // FIXME: Add Magnitude.waitUntilDone/notifyDone for tests that need to operate after the load event has fired.
252 if (window.layoutTestController)
253 document.body.innerHTML = '';
254 document.body.appendChild(Magnitude._container);