Listing 4.7 YUI Test HTML fixture file Relative performance of loops Relative performance of loops All the tests do the exact same thing: loop over all items in the array and ac
Trang 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);
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
Download from www.eBookTM.com
Trang 2conditional 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-loopis 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();
} Download from www.eBookTM.com
Trang 3var 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 ) {
Download from www.eBookTM.com
Trang 4test();
}
} 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 () {
Download from www.eBookTM.com
Trang 5var 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;
}
Download from www.eBookTM.com
Trang 6if (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
Download from www.eBookTM.com
Trang 7Figure 4.1 Profiling Twitter’s search feature.
complex code As an example of the Firebug profiler, Figure 4.1 shows the profile
report after having used Twitter’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
Download from www.eBookTM.com
Trang 8that 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
Download from www.eBookTM.com
Trang 9Part II
JavaScript for
Programmers
Download from www.eBookTM.com
Trang 10Download from www.eBookTM.com