Optimising JavaScript
By Adrian Sutton
I’ve been working to build a better filter for pasting from Microsoft Word into TinyMCE and if you know anything about filtering what Word calls HTML, you know there’s an awful lot of work to be done. As such, performance becomes a real issue – ideally we’d like to be able to filter reasonable sized documents (10-20 pages) in under the half a second that users generally call snappy.
Since filtering Word HTML is also inherently complex and we want to ensure the code is maintainable, I started out by developing the code in the most maintainable way possible, without regard to performance. There’s also a lot of tests to ensure correctness, and allow me to optimise later without fear of breaking things. With that done, it’s time to set up some performance tests and see what kind of performance behaviour it has.
Approach
A couple of notes here, the performance I’m testing is all about the filtering, so while it is making a lot of use of the DOM to parse the HTML and give me some structure to filter it, it doesn’t render anything on screen at all. I’m also not yet concerned about the actual time taken, instead focussing on identifying which areas are relatively slow and whether or not optimisations have any actual improvements. I will probably establish some performance tests where the actual timings matter to prevent future changes from slowing things down too much, but for now the best test I can do to get a sense of “is it fast enough?” is to actually paste content from Word manually. After all, my aim is only for users to perceive it as fast.
Quick Lessons Learnt
I will write up some of the more detailed lessons separately, but here’s a few quick observations1{#footlink1:1286489890934.footnote}:
- IE9 is a massive improvement in performance. The IE team deserve a huge pat on the back for what they’ve achieved. Performance was close enough to Safari and Chrome for it not to matter.
- Firefox isn’t slow but it’s certainly not fast yet. They’re putting in a big effort though so I have high hopes for the final Firefox 4, or worst case 4.5.
- Performance bottlenecks can be more surprising than you think. From jsconf.eu I knew that DOM access was going to be a bottleneck but what surprised me is that iterating attributes is dramatically slower than iterating styles.
- Despite that, it was still faster to retrieve style.cssText and use string parsing to access style values rather than using the .style properties directly2{#footlink2:1286490206243.footnote}.
- Parsing HTML by setting innerHTML is really fast across the board, even with quite large documents.
- jsperf.com is awesome for micro-benchmarks. I need to use it a lot more.
Most Important Lesson
The biggest thing I’m taking away from the experience so far is that performance is unpredictable and very unique to your situation. You need to use performance tests and keep focusing on the results for your actual use-cases. Micro-benchmarks and other people’s benchmarks can lead you astray – both in terms of potentially hurting your performance and definitely hurting maintainability. Cargo-cult optimisation simply doesn’t work.
1 – Note that the browser comparisons here are somewhat unfair – the tests aren’t running in a controlled enough environment to really compare different setups but I’m confident they are accurate enough to speak in these broad terms at least. ↩
2 – the code to do this was required anyway since in Safari non-standard styles are available via getAttribute(’style’) but not via the parsed .style attribute, and we need access to those non-standard styles to perform the filtering properly. ↩