Simple performance profiling in JavaScript
Today let’s do some JavaScript.
Mesure-Kit
I’ve wrote a small class to do basic performance profiling. You can get mesure-kit.js in my repository jQuery-vs-JS-performances.
This library uses jQuery and the test pages are using Bootstrap for the styles. Some Bootstrap class names are used by the library to generate the result summary table.
Enough talk. Just see some sample tests about loops, proxy functions and also creating DOM nodes.
What can it be used for?
The idea is to be able to compare different implementations of the same feature. Library was written to compare native JavaScript against jQuery methods.
Each provided example consists of multiple tests.
Running each test individually will keep the timing and running all test will also dump a table of timings with average time as the last row.
Building a test suite
First step is to create a Bench
object.
var bench = new Bench({
nbLoop: 10000,
summaryId: "summary"
});
Constructor accepts two options:
nbLoop
is the number of time your code will be executed in a single runsummaryId
is the DOM identifier of a container where all run timing will be displayed.
What is a test?
A test is some code to run multiple times. Each run time will be mesured and all times will be stored to compute average run time.
You have to write multiple implementations to be compared. Each one will have:
- An identifier. This is also a DOM identifier where each run result will be output.
- Code to test, specified as a closure (anonymous function).
- You may also provide some test setup and cleanup code.
Before running tests you have to register each test case in your bench
object:
bench.addTest('some-test-id', function() {
// Do something to test here
},
function() {
// Some init code run once when a single test is run.
}
);
How to run tests?
You can run test in multiple ways:
Run a single test the number of times specified in the nbLoop
option.
Setup function will be called once before runs and cleanup once after.
Can be usefull for long tests or unitary tests.
bench.runTest("some-test-id");
Run all declared test that were declared. This will run each test.
bench.runAllTests();
Run a longer benchmark. The previous behavior is executed the number of times specified.
bench.runBench(50);
Inserting summary table
The summary table will not be inserted automatically.
You can build it at any time and insert the table at will.
bench.insertSummaryTable();
Table will be inserted in the element with the identifier you specified at construction time.
Calling this method will:
- compute the average time for each ran test
- build a table
- insert the table in the page.
Test results
jQuery versus JavaScript “for” loop
This page compare two kind of iterations:
- Iteration on a jQuery list of nodes to change the html content of each node with
html()
function.- Using
$.each()
- Using a raw
for ( var idx = 0 ; idx < $list.length ; ++idx )
loop
- Using
- Iteration on a jQuery list of nodes with jQuery access on each item like
$(item).height()
- Using
$.each()
- Using a raw
for ( var idx = 0 ; idx < $list.length ; ++idx )
loop
- Using
Average timing for a single bench run using Safari I had the following results:
- Iteration to update HTML:
$.each()
: 23.9msfor
: 19.1ms
- Iteration with jQuery read action:
$.each()
: 23.9msfor
: 18.1ms
“proxy” functions versus closures
In this scenarios I try to compare performance of native closure against the jQuery proxy functions.
It seems obvious that a generic function will be slower than raw code. But how much?
First serie of test: closures:
- simple closure with captured objects: 0.22ms
- closure with one object that is captured reference to used object: 0.24ms
Roughly same results. No big differences. So now, let’s try with proxies:
- Dumb way to use proxies, created on each run: 17.43ms
- Best way to use proxy in a catched reference: 8.1ms
That’s a big difference. Just try with your browser of choice and compare. I bet you’ll get similar results.
That’s where the JIT and native compilation implemented by JavaScript engines prove its value.
So next time you implement a listener you’ll know which pattern you need to choose.
DOM node creation
Last test try to show what is the most efficient way to build a DOM.
You have to choose between jQuery ease of use or DOM API speed. What distance is there between confort and speed?
- jQuery
$("<div>", { class: 'dummy' })
: 76.94ms$("<div class='dummy'>")
: 365.48ms$("<div>")
: 48.02ms
- DOM
document.createElement('div')
: 4.24msdocument.createElement('div'); newDiv.setAttribute('class', 'dummy')
: 6.64ms
Parsing string costs a lot. If you were to use jQuery the conclusion is to try to specify attributes as a map instead of string.
But if you really need performances, stick with native DOM API.
Conclusion
Winner of those tests might be obvious. Native JavaScript cannot be slowler than jQuery.
What can be surprising is the difference between the two.
This difference can really make a difference when you develop for low-end devices (yes, I mean mobiles and tablets).
So if you have any doubt, try tocompare solutions with mesure-kit.
Updated example on 2022-06-01 to use non deprecated jQuery and Bootstrap versions.