ptg 4.2 Performance Tests 63 // 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); The setTimeout call is important to avoid choking the browser while testing. The browser uses a single thread to run JavaScript, fire events and render web pages, and the timers allow the browser some “breathing room” to pick up on queued tasks between tests that are potentially long running. Breaking the workload up with timers also avoids browsers interrupting the tests to warn us about “slow scripts.” To run these benchmarks, all we need is a simple HTML file, like the one in Listing 4.7, that loads the script. Save the file in benchmarks/loops.html. Listing 4.7 YUI Test HTML fixture file <!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=" /lib/benchmark.js"> </script> <script type="text/javascript" src="loops.js"></script> </body> </html> All the tests do the exact same thing: loop over all items in the array and access the current item. Accessing the current item adds to the footprint of the test, but it also allows us to compare the loop that accesses the current item in the loop From the Library of WoweBook.Com Download from www.eBookTM.com ptg 64 Test to Learn conditional with the rest. This is not always a safe choice, because empty strings, null, 0, and other false values will terminate the loop. Also, this style of looping performs terribly on some browsers and should be avoided. Because all the tests access the current item, we can disregard the overhead as fluctuations in the test results will be the result of the different looping styles. Note that the reversed while-loop is not directly comparable as it loops the array backwards. However, whenever order is not important, it’s commonly the fastest way to loop an array, as seen by running the above benchmark. Benchmarks such as that in Listing 4.6 are dead easy to set up. Still, to make them easier to integrate into our workflow, we can craft a simple benchmark function that removes all unnecessary cruft from writing benchmarks. Listing 4.8 shows one possible such function. The function accepts a label for the series of tests and then an object where the property names are taken as test names and property values are run as tests. The last argument is optional and instructs benchmark as to how many times a test should be run. Results are printed in both full and average time per test. Listing 4.8 A simple benchmarking tool 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(); } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 4.2 Performance Tests 65 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; }()); The benchmark function does one thing noticeably different from our previ- ous example. It runs each iteration as a function. The test is captured as a function, which is run the specified number of times. This function call itself has a footprint, so the end result is less accurate as to how long the test took, especially for small test functions. However, in most cases the overhead is ignorable because we are testing relative performance. To avoid having the function call skew tests too much, we can write the tests so that they are sufficiently complex. An alternative way to implement this is to take advantage of the fact that functions have a length prop- erty that reveals how many formal parameters a function takes. If this number is zero, then we loop. Otherwise, we will assume that the test expects the number of iterations as an argument and simply call the function, passing the iteration count. This can be seen in Listing 4.9. Listing 4.9 Using Function.prototype.length to loop or not // Inside runTests (function (name, test) { setTimeout(function () { var start = new Date().getTime(); var l = iterations; if (!test.length) { while (l ) { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 66 Test to Learn test(); } } else { test(l); } 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])); As an example of benchmark’s usage, we can reformat the loop tests using it. In this example, the length of the array to loop is somewhat reduced, and the total number of iterations is increased. Listing 4.10 shows the rewritten test. Some of the tests have been removed for brevity. Listing 4.10 Using 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 () { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 4.2 Performance Tests 67 var l = array.length, i = l, item; while (i ) { item = array[l - i - 1]; } } }, 1000); This sort of benchmarking utility can be extended to yield more helpful reports. Highlighting the fastest and slowest tests comes to mind as a useful extension. Listing 4.11 shows a possible solution. Listing 4.11 Measuring and highlighting extremes // Record times 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; } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 68 Test to Learn 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); } To further enhance benchmark we could decouple the DOM manipulation that displays results to allow for alternate report generators. This would also allow us to benchmark code in environments without a DOM, such as server-side JavaScript runtimes. 4.2.2 Profiling and Locating Bottlenecks Firebug, the web developer add-on for Firefox, offers a profiler that can profile code as it runs. For instance, we can launch a live site, start the profiler and click a link that triggers a script. After the script finishes we stop the profiler. At this point the profile report will show us a breakdown of all functions run, along with how much time was spent on each of them. Many times the number of functions run to perform some task can in itself be valuable information that points us to overly From the Library of WoweBook.Com Download from www.eBookTM.com ptg 4.3 Summary 69 Figure 4.1 Profiling Twi tt er’s search feature. complex code. As an example of the Firebug profiler, Figure 4.1 shows the profile report after having used Twi tt er ’s search feature, which uses an XMLHttpRequest to fetch data, and manipulates the DOM to display the results. The profile report shows a lot going on inside jQuery, and a total of over 31,000 function calls. 4.3 Summary In this chapter we have seen how unit tests can be utilized not necessarily only to support production code, but also to help us learn more about JavaScript. Keeping a suite of learning tests is a great way to document our learning, and they provide a handy reference over issues we have encountered in the past. While reading this book I encourage you to try out some of the examples and play with them to understand what is going on. If you don’t already have a learning test suite, now would be a great time to start one, and you can start writing tests to further your understanding of examples from this book. Benchmarks can help guide decisions when there are several viable ways of solving a given problem. By measuring relative performance we can learn patterns From the Library of WoweBook.Com Download from www.eBookTM.com ptg 70 Test to Learn that tend to perform better, and keeping benchmarks along with learning tests makes for a powerful personal knowledge bank. This chapter concludes the introduction to automated testing. In Part II, JavaScript for Programmers, we will take a deep dive into JavaScript, specifically focusing on aspects of the language that sets it apart from other programming lan- guages. This means a detailed look at objects, constructors, and prototypes, as well as JavaScript scoping and functions. From the Library of WoweBook.Com Download from www.eBookTM.com ptg Part II JavaScript for Programmers From the Library of WoweBook.Com Download from www.eBookTM.com ptg This page intentionally left blank From the Library of WoweBook.Com Download from www.eBookTM.com . testing. In Part II, JavaScript for Programmers, we will take a deep dive into JavaScript, specifically focusing on aspects of the language that sets it apart from other programming lan- guages access", forLoopDirectAccess); runBenchmark("while-loop", whileLoop); runBenchmark("while-loop, cached length property", whileLoopCachedLength); runBenchmark("reversed while-loop", reversedWhileLoop); runBenchmark("double. http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body> <h1>Relative performance of loops</h1> <script type="text /javascript& quot;