Without a doubt, Prototype.js makes a developer’s life easier. Sometimes, though, it works so well and has so many useful functions, that we often don’t stop to consider the cost of using everything Prototype.js has to offer. One of my favorite, and most used features of the library is Enumerables. They have replaced standard loops in most of my code because they offer a lot more functionality and make everything a lot more legible. Even if the task in question did not need the additional functionality of enumerables, I still used .each
for a basic loop to keep the code consistent. Unfortunately, this has turned out to be bad practice.
I just discovered that using enumerables is considerably slower than a traditional loop. While anyone could predict this off the top of their head, I didn’t quite realize the extent of the slowness. In an effort to validate the a bottleneck in one of my scripts, I ran some basic tests to see how much my program was hurting from the handy shortcut. I use a lot of nested loops for lookups, so the test was structured with two functions: one for normal loops, and one for enumerables. Here are the two functions I faced off:
Normal Loop:
function loopNormally() {
var x = 0;
for(var i = 0; i <p>
Array Elements
For Loop
Prototype .each
The numbers above are the average of 3 runs of the test. I ran each test about 20 times to look for anomalies, but it’s fairly consistent. Also, the test with 1000 elements timed out for prototype, and required me to click “continue running script.” I also ran the tests with single loops (instead of nested) and got similar results.
The numbers are based off of Firefox XP. In IE, the prototype numbers are considerably faster - almost 1 second faster for 500 elements, and 3 seconds faster for 1000 (with a timeout still). Also, the numbers for a normal loop are slower - around 0.7 for the 500 element loop. Even though IE closes the gap, the difference is still substantial.
So, the lesson learned here is to only use the powerful functionality offered in Prototype when needed. If basic JavaScript will do the task, then use basic JavaScript.
Thanks for sharing this. Very useful.
I would also be interested in knowing what UI features of prototype.js you find the most useful.
I’m exploring Javascript libraries for one of my projects and am trying to figure out what real people are actually using.
This is good advice, but I wonder if it’s a bit too Draconian — unless you’re building a really client-side-intensive web app, the performance difference should be negligible. Even in your shortest test you’re looping 10,000 times.
The way I’d put it is a little more wishy-washy: when you’re trying to improve performance, converting Prototype
each
methods to boring, old-fashionedfor
loops is a good starting point. Figure out where your code loops the most and optimize those parts.Those tests are surprising, though. For comparison’s sake, how about doing a couple tests where only one of the nested
each
loops is converted to afor
loop? I imagine converting the inner loop would save you some precious cycles.Also, what were the exact numbers on the single-loop tests?
“Beware of JavaScript Library Overkill.”
I find the title of this article a bit misleading. I came expecting an article on how js libraries are over-used in many cases and file sizes are adding up and using more bandwidth…
I only have a couple years of java experience, but even in an introductory class they tell us to use enumeration with caution… however this was a good read, especially the part about ie closing the gap… =P was that in ie5/6 or 7?
You should also avoid using “getElementsByClassName” too frequently. It also slows down the script significantly.
Generally this is true, but only in cases where you really need the performance. The most time this is not especially needed and than nice looking code is better than optiomal coded ugly looking code.
Just my 2cents.
Even though my AI in JavaScript is being spread around the Web to such sites as http://saintstephen.memebot.com/aimind/index.html I am still a rank beginner in JavaScript and I did not even know that JavaScript had libraries, so I am thankful for the information.
Accessing forms with $F(id) is very heavy as well. We’ve been working on a revenue/expense forecasting application for a customer that will run on IE 6 on hardware that is 3-4 years old. In firefox it was barely noticeable, but in IE it was nearly 14 seconds to run the new calculations when you changed values. Once we pulled it out and did a more direct style of access, document.forms, it was down to about 2.5 seconds. That’s a huge difference.
I love prototype for it’s simplicity, but you have to watch out on large/complex pages. Even with the latest and greatest version, IE still bleeds a large amount of memory.
Kumar, it was IE6.
I’ve actually been wanting to become an Enumerable convert, and you haven’t discouraged me. More complex functionality is bound to lead to slower results, but it’s usually negligible for most applications. However, thanks for doing that, because in the event that I do something dealing with more data, I’ll already know where my problem lies.
Ryan, while you’re doing these test it would be neat to test javascript 1.7 iterators in Firefox 2!
Thanks m8!
Ryan, great post, and a friendly reminder that when performance is top priority, anything that isn’t native will be considerably slower than a custom solution.
Firefox, Webkit, and Opera(to some extent) have support for Iterators like forEach, map, etc. I think we’ll probably see future versions of Prototype delegate to these native methods in future releases.
Thanks for showing us these stats. The reason Prototype’s each is so slow is that for every iteration through the loop, it runs a try/catch block around the function you passed in. Why? Because it emulates using break and continue statements. The catch is that to use them you have to use “throw $continue” or “throw $break” in your loop function to mimic the native functionality. It’s actually a rather ingenius solution, it just comes with a big performance hit.
I talk a little bit about this and the new mootools JavaScript library’s own each method at:
http://www.coryhudson.com/blog/2006/09/14/useful-utility-functions-in-mootools/
Another problem with the .each method is that regular error exceptions aren’t thrown, presumably because of the mechanism Cory Hudson described above. I had to had my own try/catch block around my code just to get a little error reporting.
I just discovered that using enumerables is considerably slower than a traditional loop.
Well done Sherlock! Next week: Ryan Campbell discovers tea is much nicer when made with hot water instead of cold ;-)