OLD | NEW |
1 // Magnitude verifies time complexity of a function. | 1 // Magnitude verifies time complexity of a function. |
2 // Magnitude.run can be called multiple times in a single script if desired, | 2 // Magnitude.run can be called multiple times in a single script if desired, |
3 // optionally with different parameters each time. | 3 // optionally with different parameters each time. |
4 // | 4 // |
5 // Usage: | 5 // Usage: |
6 // <script src="../resources/magnitude-perf.js"></script> | 6 // <script src="../resources/magnitude-perf.js"></script> |
7 // <script> | 7 // <script> |
8 // function setup(magnitude) | 8 // function setup(magnitude) |
9 // { | 9 // { |
10 // ... | 10 // ... |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
129 }; | 129 }; |
130 | 130 |
131 Magnitude._logRunTimes = function() | 131 Magnitude._logRunTimes = function() |
132 { | 132 { |
133 // Print out the magnitudes and times in CSV | 133 // Print out the magnitudes and times in CSV |
134 // to afford easy copy-paste to a charting application. | 134 // to afford easy copy-paste to a charting application. |
135 Magnitude._debug('magnitudes: ' + Magnitude._magnitudes.join(',')); | 135 Magnitude._debug('magnitudes: ' + Magnitude._magnitudes.join(',')); |
136 Magnitude._debug('times: ' + Magnitude._times.join(',')); | 136 Magnitude._debug('times: ' + Magnitude._times.join(',')); |
137 }; | 137 }; |
138 | 138 |
| 139 Magnitude._for = function(n, body, callback) |
| 140 { |
| 141 var i = 0; |
| 142 var results = []; |
| 143 |
| 144 function iteration(result) { |
| 145 results.push(result); |
| 146 if (++i === n) |
| 147 callback(results); |
| 148 else |
| 149 body(i, iteration); |
| 150 } |
| 151 |
| 152 if (Magnitude._async) { |
| 153 body(i, iteration); |
| 154 } else { |
| 155 for (; i < n; ++i) { |
| 156 body(i, function(result) { |
| 157 results.push(result); |
| 158 }); |
| 159 } |
| 160 callback(results); |
| 161 } |
| 162 }; |
| 163 |
| 164 Magnitude._while = function(condition, body, callback) |
| 165 { |
| 166 function iteration() { |
| 167 if (condition()) |
| 168 body(iteration); |
| 169 else |
| 170 callback(); |
| 171 } |
| 172 |
| 173 if (Magnitude._async) { |
| 174 iteration(); |
| 175 } else { |
| 176 while (condition()) { |
| 177 body(function() {}); |
| 178 } |
| 179 callback(); |
| 180 } |
| 181 }; |
| 182 |
139 // Main | 183 // Main |
140 Magnitude.run = function(setup, test, expected) | 184 Magnitude.run = function(setup, test, expected) |
141 { | 185 { |
| 186 function runTest(magnitude, callback) { |
| 187 test(magnitude); |
| 188 callback(); |
| 189 } |
| 190 Magnitude._run(setup, runTest, expected, false); |
| 191 }; |
| 192 |
| 193 Magnitude.runAsync = function(setup, test, expected) |
| 194 { |
| 195 if (window.testRunner) |
| 196 testRunner.waitUntilDone(); |
| 197 window.addEventListener('load', function() { |
| 198 Magnitude._run(setup, test, expected, true); |
| 199 }, false); |
| 200 }; |
| 201 |
| 202 Magnitude._run = function(setup, test, expected, async) |
| 203 { |
| 204 Magnitude._async = async; |
142 Magnitude._debugLog = '\nDEBUG LOG:\n'; | 205 Magnitude._debugLog = '\nDEBUG LOG:\n'; |
143 Magnitude._debug('Expected complexity: ' + expected); | 206 Magnitude._debug('Expected complexity: ' + expected); |
144 | 207 |
145 Magnitude._exponents = []; | 208 Magnitude._exponents = []; |
146 Magnitude._magnitudes = []; | 209 Magnitude._magnitudes = []; |
147 var maxExponent = Magnitude.initialExponent + Magnitude.numPoints; | 210 var maxExponent = Magnitude.initialExponent + Magnitude.numPoints; |
148 for (var i = Magnitude.initialExponent; i < maxExponent; i++) { | 211 for (var i = Magnitude.initialExponent; i < maxExponent; i++) { |
149 Magnitude._exponents.push(i); | 212 Magnitude._exponents.push(i); |
150 Magnitude._magnitudes.push(Math.pow(2, i)); | 213 Magnitude._magnitudes.push(Math.pow(2, i)); |
151 } | 214 } |
152 | 215 |
153 var numSuccesses = 0; | 216 Magnitude._numSuccesses = 0; |
154 for (var trialNumber = 0; trialNumber < Magnitude.numTrials; trialNumber++)
{ | 217 function runTrial(trialNumber, nextTrial) |
| 218 { |
| 219 Magnitude._trialNumber = trialNumber; |
155 Magnitude._debug('\nTrial #' + trialNumber); | 220 Magnitude._debug('\nTrial #' + trialNumber); |
156 Magnitude._times = []; | 221 Magnitude._for(Magnitude.numPoints, Magnitude._runTime.bind(null, setup,
test), completeTrial.bind(null, nextTrial)); |
157 for (var i = 0; i < Magnitude.numPoints; i++) | 222 } |
158 Magnitude._times.push(Magnitude._runTime(setup, test, Magnitude._mag
nitudes[i])); | 223 function completeTrial(nextTrial, times) |
| 224 { |
| 225 Magnitude._times = times; |
159 Magnitude._logRunTimes(); | 226 Magnitude._logRunTimes(); |
160 switch (expected) { | 227 switch (expected) { |
161 case Magnitude.CONSTANT: | 228 case Magnitude.CONSTANT: |
162 passed = Magnitude._isConstant(); | 229 passed = Magnitude._isConstant(); |
163 break; | 230 break; |
164 case Magnitude.LINEAR: | 231 case Magnitude.LINEAR: |
165 passed = Magnitude._isLinear(); | 232 passed = Magnitude._isLinear(); |
166 break; | 233 break; |
167 case Magnitude.POLYNOMIAL: | 234 case Magnitude.POLYNOMIAL: |
168 passed = Magnitude._isPolynomial(); | 235 passed = Magnitude._isPolynomial(); |
169 break; | 236 break; |
170 default: | 237 default: |
171 Magnitude._log('FAIL: unrecognized order of growth: ' + expected
); | 238 Magnitude._log('FAIL: unrecognized order of growth: ' + expected
); |
172 passed = false; | 239 passed = false; |
173 break; | 240 break; |
174 } | 241 } |
175 Magnitude._debug('Trial #' + trialNumber + ': ' + | 242 Magnitude._debug('Trial #' + Magnitude._trialNumber + ': ' + |
176 (passed ? 'SUCCESS' : 'FAILURE')); | 243 (passed ? 'SUCCESS' : 'FAILURE')); |
177 if (passed) | 244 if (passed) |
178 numSuccesses++; | 245 Magnitude._numSuccesses++; |
| 246 nextTrial(); |
179 } | 247 } |
| 248 Magnitude._for(Magnitude.numTrials, runTrial, Magnitude._finish); |
| 249 }; |
| 250 |
| 251 Magnitude._finish = function() |
| 252 { |
180 var neededToPass = Magnitude.numTrials * Magnitude.successThreshold; | 253 var neededToPass = Magnitude.numTrials * Magnitude.successThreshold; |
181 Magnitude._debug('Successes: ' + numSuccesses + ', need ' + | 254 Magnitude._debug('Successes: ' + Magnitude._numSuccesses + ', need ' + |
182 neededToPass + ' (' + (100 * Magnitude.successThreshold) + '%) ' + | 255 neededToPass + ' (' + (100 * Magnitude.successThreshold) + '%) ' + |
183 'to pass'); | 256 'to pass'); |
184 var passedOverall = (numSuccesses >= neededToPass); | 257 var passedOverall = (Magnitude._numSuccesses >= neededToPass); |
185 Magnitude._log(passedOverall ? 'PASS' : 'FAIL'); | 258 Magnitude._log(passedOverall ? 'PASS' : 'FAIL'); |
186 | 259 |
187 // By default don't log detailed information to layout test results, | 260 // By default don't log detailed information to layout test results, |
188 // in order to keep expected results consistent from run to run. | 261 // in order to keep expected results consistent from run to run. |
189 if (!window.testRunner || !passedOverall) | 262 if (!window.testRunner || !passedOverall) |
190 Magnitude._log(Magnitude._debugLog); | 263 Magnitude._log(Magnitude._debugLog); |
| 264 |
| 265 if (Magnitude._async && window.testRunner) |
| 266 testRunner.notifyDone(); |
191 }; | 267 }; |
192 | 268 |
193 Magnitude._runTime = function(setup, test, magnitude) | 269 Magnitude._runTime = function(setup, test, pointIndex, callback) |
194 { | 270 { |
| 271 var magnitude = Magnitude._magnitudes[pointIndex]; |
195 setup(magnitude); | 272 setup(magnitude); |
196 | 273 |
197 var debugStr = 'run for magnitude ' + magnitude; | 274 var debugStr = 'run for magnitude ' + magnitude; |
198 if (window.GCController) { | 275 if (window.GCController) { |
199 if (GCController.getJSObjectCount) | 276 if (GCController.getJSObjectCount) |
200 debugStr += ' jsObjectCountBefore ' + GCController.getJSObjectCount(
); | 277 debugStr += ' jsObjectCountBefore ' + GCController.getJSObjectCount(
); |
201 | 278 |
202 GCController.collectAll(); | 279 GCController.collectAll(); |
203 | 280 |
204 if (GCController.getJSObjectCount) | 281 if (GCController.getJSObjectCount) |
205 debugStr += ' jsObjectCountAfter ' + GCController.getJSObjectCount()
; | 282 debugStr += ' jsObjectCountAfter ' + GCController.getJSObjectCount()
; |
206 } | 283 } |
207 | 284 |
208 Magnitude._debug(debugStr); | 285 Magnitude._debug(debugStr); |
209 | 286 |
210 var nowFunction = window.performance && window.performance.now ? | 287 var nowFunction = window.performance && window.performance.now ? |
211 window.performance.now.bind(window.performance) : Date.now; | 288 window.performance.now.bind(window.performance) : Date.now; |
212 | 289 |
213 // Possibly run multiple times, to deal with delays at end | 290 // Possibly run multiple times, to deal with delays at end |
214 var runOk = false; | 291 var runOk = false; |
215 var attempt = 0; | 292 var attempt = 0; |
216 var maxAttempts = 5; | 293 var maxAttempts = 5; |
217 while (!runOk) { | 294 var millisecondsPerIteration; |
| 295 var totalTimeMilliseconds; |
| 296 var iterations; |
| 297 |
| 298 function iteration(nextIteration) |
| 299 { |
218 var start = nowFunction(); | 300 var start = nowFunction(); |
219 var iterations = 0; | 301 iterations = 0; |
220 var totalTimeMilliseconds = 0; | 302 totalTimeMilliseconds = 0; |
221 while (totalTimeMilliseconds < Magnitude.millisecondsPerRun) { | 303 function completeRun(nextRun) |
222 test(magnitude); | 304 { |
223 iterations++; | 305 iterations++; |
224 totalTimeMilliseconds = nowFunction() - start; | 306 totalTimeMilliseconds = nowFunction() - start; |
| 307 nextRun(); |
225 } | 308 } |
226 var millisecondsPerIteration = totalTimeMilliseconds / iterations; | 309 Magnitude._while(function() { return totalTimeMilliseconds < Magnitude.m
illisecondsPerRun; }, function(nextRun) { test(magnitude, completeRun.bind(null,
nextRun)); }, completeIteration.bind(null, nextIteration)); |
| 310 } |
| 311 |
| 312 function completeIteration(nextIteration) |
| 313 { |
| 314 millisecondsPerIteration = totalTimeMilliseconds / iterations; |
227 Magnitude._debug(iterations + ' iterations in ' + | 315 Magnitude._debug(iterations + ' iterations in ' + |
228 totalTimeMilliseconds + ' milliseconds, ' + | 316 totalTimeMilliseconds + ' milliseconds, ' + |
229 'average ' + millisecondsPerIteration + | 317 'average ' + millisecondsPerIteration + |
230 ' milliseconds per iteration'); | 318 ' milliseconds per iteration'); |
231 | 319 |
232 var overTime = totalTimeMilliseconds - Magnitude.millisecondsPerRun; | 320 var overTime = totalTimeMilliseconds - Magnitude.millisecondsPerRun; |
233 var relativeOverTimeTolerance = 0.001; | 321 var relativeOverTimeTolerance = 0.001; |
234 var overTimeTolerance = Magnitude.millisecondsPerRun * | 322 var overTimeTolerance = Magnitude.millisecondsPerRun * |
235 relativeOverTimeTolerance; | 323 relativeOverTimeTolerance; |
236 // If overTime is more than the duration of a run, | 324 // If overTime is more than the duration of a run, |
237 // there was some delay, which introduces noise. | 325 // there was some delay, which introduces noise. |
238 // Allow a small amount of tolerance. | 326 // Allow a small amount of tolerance. |
239 if (overTime < (millisecondsPerIteration + overTimeTolerance)) | 327 if (overTime < (millisecondsPerIteration + overTimeTolerance)) |
240 runOk = true; | 328 runOk = true; |
241 else { | 329 else { |
242 Magnitude._debug('over-time: ' + overTime + | 330 Magnitude._debug('over-time: ' + overTime + |
243 ' exceeds average run-time ' + millisecondsPerIteration + | 331 ' exceeds average run-time ' + millisecondsPerIteration + |
244 ' by more than tolerance of ' + overTimeTolerance + | 332 ' by more than tolerance of ' + overTimeTolerance + |
245 ' assuming delay'); | 333 ' assuming delay'); |
246 attempt++; | 334 attempt++; |
247 if (attempt < maxAttempts) | 335 if (attempt < maxAttempts) |
248 Magnitude._debug('Retrying to reduce delay noise'); | 336 Magnitude._debug('Retrying to reduce delay noise'); |
249 else { | 337 else { |
250 Magnitude._debug('Failed to reduce delay noise after ' + | 338 Magnitude._debug('Failed to reduce delay noise after ' + |
251 maxAttempts + ' attempts, proceeding with noisy data'); | 339 maxAttempts + ' attempts, proceeding with noisy data'); |
252 runOk = true; | 340 runOk = true; |
253 } | 341 } |
254 } | 342 } |
| 343 nextIteration(); |
255 } | 344 } |
256 | 345 Magnitude._while(function() { return !runOk; }, iteration, function() { call
back(millisecondsPerIteration); }); |
257 return millisecondsPerIteration; | |
258 }; | 346 }; |
259 | 347 |
260 // Auxiliary computations | 348 // Auxiliary computations |
261 Magnitude._log2Ratios = function() | 349 Magnitude._log2Ratios = function() |
262 { | 350 { |
263 // Compute base-2 logarithm of ratios of times, | 351 // Compute base-2 logarithm of ratios of times, |
264 // equivalently the difference of the base-2 logarithms: | 352 // equivalently the difference of the base-2 logarithms: |
265 // log_2(t[i+1]/t[i]) = log_2(t[i+1]) - log_2(t[i]) | 353 // log_2(t[i+1]/t[i]) = log_2(t[i+1]) - log_2(t[i]) |
266 // Since times are exponentially spaced (base 2), | 354 // Since times are exponentially spaced (base 2), |
267 // this is the slope of the step on the log-log scale, | 355 // this is the slope of the step on the log-log scale, |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
347 }; | 435 }; |
348 | 436 |
349 // Register listener | 437 // Register listener |
350 window.addEventListener('load', function() { | 438 window.addEventListener('load', function() { |
351 // FIXME: Add Magnitude.waitUntilDone/notifyDone for tests that need to | 439 // FIXME: Add Magnitude.waitUntilDone/notifyDone for tests that need to |
352 // operate after the load event has fired. | 440 // operate after the load event has fired. |
353 if (window.testRunner) | 441 if (window.testRunner) |
354 document.body.innerHTML = ''; | 442 document.body.innerHTML = ''; |
355 document.body.appendChild(Magnitude._container); | 443 document.body.appendChild(Magnitude._container); |
356 }, false); | 444 }, false); |
OLD | NEW |