测试驱动javascript开发 -- 5.性能测试
自动化测试另外一个比较重要的内容点是性能测试,很多问题可能有不止一种解决方案,很多时候并不知道哪个是最好的解决方案。例如有很多创建javascript对象的方法,使用javascript构造器、使用函数的方法或者使用闭包。我们可能会从可测试化、灵活性和性能的角度去考虑使用哪种方式。足见性能是相当重要的一点。
1.基准和相对性能
当一个问题我们有两个以上的解决方案的时候,判断哪个解决方案更好的原则很简单,就是哪个的性能更好。判断的原则也很简单:1.创建new Date()作为开始时间;2.执行要衡量的代码;3.代码执行完毕,创建new Date()作为结束时间,减去开始时间算出总时长;4.替换执行代码,重复上述步骤;5.比较各种方案的执行时长。
每个要比较的代码我们需要执行很多次,通常会把他们放在一个循环中。因为windows xp和vista操作系统的浏览器的timer的间隔时间是15ms,让这个问题变的更加复杂,测试会变的相当不准确,所以我们需要保证测试代码运行时长在500ms以上。
下面是我们用来做性能测试的代码,文件为benchmark.js:
var ol;
function runBenchmark(name, test) { if (!ol) { ol = document.createElement("ol"); document.body.appendChild(ol); } setTimeout(function () { var start = new Date().getTime(); test(); var total = new Date().getTime() - start; var li = document.createElement("li"); li.innerHTML = name + ": " + total + "ms"; ol.appendChild(li); }, 15); }
下面的代码使用runBenchmark()用来做测试,文件名为loops.js:
var loopLength = 500000; // 填充循环数组 var array = []; for (var i = 0; i < loopLength; i++) { array[i] = "item" + i; } function forLoop() { for (var i = 0, item; i < array.length; i++) { item = array[i]; } }function forLoopCachedLength() { for (var i = 0, l = array.length, item; i < l; i++) { item = array[i]; } } function forLoopDirectAccess() { for (var i = 0, item; (item = array[i]); i++) { } } function whileLoop() { var i = 0, item; while (i < array.length) { item = array[i]; i++; } } function whileLoopCachedLength() { var i = 0, l = array.length, item; while (i < l) { item = array[i]; i++; } } function reversedWhileLoop() { var l = array.length, item; while (l--) { item = array[l]; } } function doubleReversedWhileLoop() { var l = array.length, i = l, item; while (i--) { item = array[l - i - 1]; } } // Run tests runBenchmark("for-loop", forLoop); runBenchmark("for-loop, cached length", forLoopCachedLength); runBenchmark("for-loop, direct array access", forLoopDirectAccess); runBenchmark("while-loop", whileLoop); runBenchmark("while-loop, cached length property", whileLoopCachedLength); runBenchmark("reversed while-loop", reversedWhileLoop); runBenchmark("double reversed while-loop", doubleReversedWhileLoop);
使用setTimeout方法的目的是,在测试中避免浏览器的堵塞。因为浏览器只有一个单线程,他用这一个线程完成javascript的运行、事件的触发和页面的渲染工作。timer则为浏览器提供了一个任务队列,可以用他来处理长时间运行的工作,这样就可以避免在运行测试代码时弹出超时警告。
下面我们看看loops.html页面的内容:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Relative performance of loops</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <h1>Relative performance of loops</h1> <script type="text/javascript" src="benchmark.js"></script> <script type="text/javascript" src="loops.js"></script> </body> </html>
所有的测试做的事情都差不多,遍历数组,并访问每个成员。通过测试,我们可以看到哪种方式效率最高。
我们可以把runBenchmark方法做一下重构:
var benchmark = (function () { function init(name) { var heading = document.createElement("h2"); heading.innerHTML = name; document.body.appendChild(heading); var ol = document.createElement("ol"); document.body.appendChild(ol); return ol; }
function runTests(tests, view, iterations) { for (var label in tests) { if (!tests.hasOwnProperty(label) || typeof tests[label] != "function") { continue; } (function (name, test) { setTimeout(function () { var start = new Date().getTime(); var l = iterations; while (l--) { test(); } var total = new Date().getTime() - start; var li = document.createElement("li"); li.innerHTML = name + ": " + total + "ms (total), " + (total / iterations) + "ms (avg)"; view.appendChild(li); }, 15); }(label, tests[label])); } }
function benchmark(name, tests, iterations) { iterations = iterations || 1000; var view = init(name); runTests(tests, view, iterations); }
return benchmark; }());
使用benchmark也很简单,如下:
var loopLength = 100000; var array = []; for (var i = 0; i < loopLength; i++) { array[i] = "item" + i; } benchmark("Loop performance", { "for-loop": function () { for (var i = 0, item; i < array.length; i++) { item = array[i]; } }, "for-loop, cached length": function () { for (var i = 0, l = array.length, item; i < l; i++) { item = array[i]; } }, // ... "double reversed while-loop": function () { var l = array.length, i = l, item; while (i--) { item = array[l - i - 1]; } } }, 1000);
我们也可以为benchmark扩展更多的功能,例如把最快和最慢的测试用高亮的方式显示:
// 记录次数 var times; function runTests (tests, view, iterations) { // ... (function (name, test) { // ... var total = new Date().getTime() - start; times[name] = total; // ... }(label, tests[label])); // ... }
function highlightExtremes(view) { // The timeout is queued after all other timers, ensuring // that all tests are finished running and the times // object is populated setTimeout(function () { var min = new Date().getTime(); var max = 0; var fastest, slowest; for (var label in times) { if (!times.hasOwnProperty(label)) { continue; } if (times[label] < min) { min = times[label]; fastest = label; } if (times[label] > max) { max = times[label]; slowest = label; } } var lis = view.getElementsByTagName("li"); var fastRegexp = new RegExp("^" + fastest + ":"); var slowRegexp = new RegExp("^" + slowest + ":"); for (var i = 0, l = lis.length; i < l; i++) { if (slowRegexp.test(lis[i].innerHTML)) { lis[i].style.color = "#c00"; } if (fastRegexp.test(lis[i].innerHTML)) { lis[i].style.color = "#0c0"; } } }, 15); } // Updated benchmark function function benchmark (name, tests, iterations) { iterations = iterations || 1000; times = {}; var view = init(name); runTests(tests, view, iterations); highlightExtremes(view); }
2.分析和定位瓶颈
很多浏览器提供了调试工具,例如firefox的firebug。这些工具可以为我们提供很多信息,帮助我们跟踪代码,判断代码中的问题。例如下图就是使用firebug的实例。
代码下载: