JavaScript string building benchmarks
From Trephine
| « Efficient JavaScript string building | JavaScript task chaining » |
[subscribe] Recent blog entries
- Simple prototypal inheritance new!
- Adventures in Rhino - setters and getters
- Site improvements - fighting with Disqus
- JavaScript task chaining
- JavaScript string building benchmarks
- Efficient JavaScript string building
- Alternative JavaScript worker thread API
- Implementing JavaScript worker threads
- Thread safe DOM manipulation
- Site improvements - CSS sprites
- Trephine worker threads made easy
- Pitfalls of multithreaded browser development
- Site improvements - reducing dependencies
- The unsplittability of XML
Live Demos
JavaScript string building benchmarks
Many readers pointed out (correctly) that my exposition on efficient JavaScript string building was in need of benchmarking numbers to be of real value. This article provides those number for several browsers, along with a simple test suite you can try yourself.
Tip: To run the suite yourself, see the String builder benchmark test.
Running the benchmark suite produces a table with the following four columns:
| column name | description |
|---|---|
| implementation | Description of which implementation is being tested |
| small (ms) | Number of milliseconds it takes to execute the selected implementation, passing in 128 arguments of 128 elements each (16,384 total items) |
| medium (ms) | Number of milliseconds it takes to execute the implementation, passing in passing in 256 arguments of 256 elements each (65,536 total items) |
| large (ms) | Number of milliseconds it takes to execute the implementation, passing in passing in 512 arguments of 512 elements each (262,144 total items) |
The implementations can roughly be described as follows:
- string buffer - Repeatedly appends to a string variable used as a buffer.
- push array buffer - Uses
push()to append items to an array buffer. - native assignment - Uses
buf[buf.length]to append items to an array. - direct join - Uses
join()on each input list, then joins the results. - pre-count buffer - Counts exactly how many items there are in total, then allocates a buffer array of that length.
Here are some results gathered from my Ubuntu 8.04 machine, starting with the fastest and working backwards.
Please note that this is not intended to be an exhaustive set of all browsers, just those that I had readily available at the time of this writing. I plan to provide more stats for more platforms and browsers. In the mean time, the benchmark suite is available if you'd like to collect your own data.
Firefox 3
Firefox 3 is by far the fastest browser I've tested. As you can see, even for the large data set, all times are sub-second:
| implementation | small (ms) | medium (ms) | large (ms) |
|---|---|---|---|
| 1. string buffer | 20.8 | 126.2 | 417.6 |
| 2. push array buffer | 22.2 | 87.6 | 807 |
| 3. native assignment | 22.2 | 137.2 | 625.4 |
| 4. direct join | 10.2 | 51.8 | 344.6 |
| 5. pre-count buffer | 21.8 | 93.8 | 288.8 |
Interesting things to note:
- Both the push array buffer implementation and the native assignment implementation are significantly slower than the string buffer method.
- Native assignment is faster than using
push(). - Direct join is significantly faster than all other implementations except the pre-count buffer for the large data set.
Flock 1.2.7 (Firefox 2)
Flock 1.2, which I use in place of a canonical install of Firefox 2, was the next best performing browser I tested:
| implementation | small (ms) | medium (ms) | large (ms) |
|---|---|---|---|
| 1. string buffer | 75 | 273.4 | 1240.8 |
| 2. push array buffer | 107.2 | 457.4 | 2033.6 |
| 3. native assignment | 90.8 | 451.4 | 1983.6 |
| 4. direct join | 18.8 | 103.6 | 373 |
| 5. pre-count buffer | 25.4 | 169 | 660.8 |
Interesting things to note:
- Both the push array buffer implementation and the native assignment implementation are significantly slower than the string buffer method.
- Native assignment is faster than using
push(). - Direct join is significantly faster than all other implementations, followed by the pre-count buffer.
Opera 9.2
Opera 9 performed significantly worse than either of the previous two browsers:
| implementation | small (ms) | medium (ms) | large (ms) |
|---|---|---|---|
| 1. string buffer | 87 | 224.4 | 13503.8 |
| 2. push array buffer | 55 | 287.6 | 9599.2 |
| 3. native assignment | 52.6 | 284.6 | 9460.6 |
| 4. direct join | 29.8 | 158.4 | 640 |
| 5. pre-count buffer | 35.2 | 158.8 | 595.8 |
Interesting things to note:
- Unlike the Firefox derivatives, in Opera the string buffer implementation is the slowest over all.
- Native assignment is (again) faster than using
push(). - Direct join and pre-count buffer both perform well compared to the other methods.
Internet Explorer 6
Internet Explorer 6 (via wine) is by far the slowest browser I've tested so far. In fact, it was so slow at performing the string buffer benchmark, I had to skip it for the regular sized data sets:
| implementation | small (ms) | medium (ms) | large (ms) |
|---|---|---|---|
| 1. string buffer | -- | -- | -- |
| 2. push array buffer | 1190.2 | 5671.2 | 39931 |
| 3. native assignment | 1173.4 | 5685.6 | 41460.2 |
| 4. direct join | 537.8 | 2079.2 | 4680 |
| 5. pre-count buffer | 95 | 380.4 | 1395 |
By paring down the three data set sizes, I was able to get IE 6 to at least complete a string buffer test:
| implementation | 16x16 (ms) | 32x32 (ms) | 64x64 (ms) |
|---|---|---|---|
| 1. string buffer | 70.8 | 1041 | 14483.2 |
| 2. push array buffer | 9 | 28.4 | 115.4 |
| 3. native assignment | 7.8 | 32.8 | 99.2 |
| 4. direct join | 2.6 | 9.8 | 41.4 |
| 5. pre-count buffer | 3.2 | 6.2 | 24.2 |
Interesting things to note:
- The string buffer method is orders of magnitude slower than all other methods.
- The push array buffer and native assignment methods perform similarly and are both significantly slower than direct join and pre-count buffer.
- The pre-count buffer method is by far the fastest, especially so for larger data sets.
Conclusions
At the time of this writing, IE 6 on Windows has about 20% global market share. Given that the speed differences between implementations in Firefox are small in comparison to IE's differences, and IE 6 still captures a non-negligible portion of the market, I feel the prudent thing to do is to stick with the guidelines established at the end of efficient JavaScript string building.
Hope this helps, thanks for reading!
--Jim R. Wilson (jimbojw) 08:29, 1 May 2009 (UTC)