wonko.com

Hi! I'm Ryan Grove: Sorcerer at SmugMug, lover of movies, eater of pie, connoisseur of awesome.

Posts tagged with “performance”

Displaying items 1 - 10 of 15

Achieving Performance Zen with YUI 3

Last week I gave an internal tech talk at Yahoo! entitled Achieving Performance Zen with YUI 3. The full video of the talk is now available on YUI Theater. The slides are available on SlideShare (or you can download the Keynote deck).

Synopsis: Following codified guidelines can help you build fast websites, but building applications that are clean, fast and extensible also involves taking a balanced approach to performance at every level of your F2E work. YUI 3 is designed to help you in this process, providing a right-sized abstraction layer with built-in performance magic and a variety of tools that make fast frontend code easy and fun to produce. In this session, we’ll explore the zen of performant JavaScript in the YUI 3 world and introduce you to some of the powerful tools YUI 3 puts at your disposal in every app you write.

LazyLoad 2.0.0 released

After quite a while without updates, I’ve finally released version 2.0.0 of LazyLoad.

LazyLoad is a tiny (only 1,541 bytes minified), dependency-free JavaScript library that makes it super easy to load external JavaScript and (new in this version) CSS files on demand. It’s ideal for quickly and unobtrusively loading large external scripts and stylesheets either lazily after the rest of the page has finished loading or on demand as needed.

In addition to CSS support, this version of LazyLoad also adds support for parallel loading of multiple resources in browsers that support it. To load multiple resources in parallel, simply pass an array of URLs in a single LazyLoad call.

Downloads

Usage

Using LazyLoad is simple. Just call the appropriate method — css() to load CSS, js() to load JavaScript — and pass in a URL or array of URLs to load. You can also provide a callback function if you’d like to be notified when the resources have finished loading, as well as an argument to pass to the callback and a scope in which to execute the callback.

// Load a single JavaScript file and execute a callback when it finishes loading.
LazyLoad.js('http://example.com/foo.js', function () {
  alert('foo.js has been loaded');
});

// Load multiple JS files and execute a callback when they've all finished.
LazyLoad.js(['foo.js', 'bar.js', 'baz.js'], function () {
  alert('all files have been loaded');
});

// Load a CSS file and pass an argument to the callback function.
LazyLoad.css('foo.css', function (arg) {
  alert(arg);
}, 'foo.css has been loaded');

// Load a CSS file and execute the callback in a different scope.
LazyLoad.css('foo.css', function () {
  alert(this.foo); // displays 'bar'
}, null, {foo: 'bar'});

Supported Browsers

  • Firefox 2+
  • Google Chrome (all versions)
  • Internet Explorer 6+
  • Opera 9+
  • Safari 3+
  • Mobile Safari (all versions)

Other browsers may work, but haven’t been tested. It’s a safe bet that anything based on a recent version of Gecko or WebKit will probably work.

Caveats

All browsers support parallel loading of CSS. However, only Firefox and Opera currently support parallel script loading while preserving execution order. To ensure that scripts are always executed in the correct order, LazyLoad will load all scripts sequentially in browsers other than Firefox and Opera. Hopefully other browsers will improve their parallel script loading behavior soon.

Sadly, Firefox, Safari, and Google Chrome don’t provide any indication when a CSS file has finished loading. In these browsers, CSS load callbacks will execute after a short delay, but there’s no way to automatically guarantee that the CSS has finished loading before the callback is executed. Luckily, there’s a fairly painless manual workaround that you can use to detect when CSS has finished loading, but it’s not possible for LazyLoad to do it for you.

Trogdor: Burninatingly fast search using Yahoo! BOSS

Everyone and their dog seems to have written an example of how to use the Yahoo! Search BOSS API to build a simple search tool. I wanted to take that one step further and build something that would serve both as an example and as a usable service, and that could be extended and enhanced by other developers. Since my day job involves constant tradeoffs between making Yahoo! Search slower (by adding features) and making it faster (by optimizing those features), my primary goal here was to make something as fast as technically possible.

To do this, I wrote a very simple JavaScript module called Trogdor that uses dynamic script nodes to make cross-domain JSONP requests to the BOSS API as you type your query. Search results are returned and rendered almost instantly on each keystroke, and you can use the up and down arrow keys (or tab) and enter to quickly select the result you want—no mouse necessary.

Trogdor doesn’t require a JavaScript framework and works great in all modern browsers (and even some ancient, crappy browsers like IE6). The entire package (HTML, CSS and JS) weighs just a smidge under 2KB after minification and gzip, and it’s wonderfully fast.

Try it out for yourself at pieisgood.org/search and be sure to grab the heavily-commented source code on GitHub. If you’ve got ideas for features and improvements, fork the repo and go nuts (and be sure to let me know what you come up with). You’re also welcome to use Trogdor (modified or unmodified) in your own projects, although I do ask that you please use your own BOSS API key rather than the one included in the example.

Update, 11/26: Changed the name of the library from FastSearch to Trogdor, since dragons are awesome (and apparently there’s a Microsoft search product called FastSearch).

Why you probably shouldn't use cookies to store session data

Browser cookies have a very specific purpose: they are meant to store tiny bits of data and to pass that data to the web server on every request. Because they must be uploaded to the server along with every single request the browser makes, browsers limit cookies to a maximum size of 4 kilobytes per domain.

Given this storage limitation and the fact that even a 4KB cookie is absolutely guaranteed to increase client/server latency, you’d think web developers would avoid packing cookies full of data, right? Sadly, web developers are a silly bunch and are prone to sheeplike behavior.

Standard practice up until about 2007 was to store session data on the server, typically in a file, a database, or a distributed memory cache. Cookies were used only to store the session keys—short strings that uniquely identified a user and allowed the server to retrieve session data from the session store as necessary. This had little effect on client/server latency since the cookies were small, and it was reasonably secure since users couldn’t tamper with the actual contents of their session data; the best a malicious user could hope to do was to sniff out another user’s session id and impersonate them, which could be easily prevented using SSL.

Then, in 2007, Ruby on Rails added support for using cookies themselves as session stores. Rails wasn’t the first web framework to try to shove a bunch of crap into cookies, but it was possibly the most popular one to do so. Naturally this feature was enabled by default, which meant that web developers around the world instantly began to abuse the hell out of it.

The justification for the feature—and for all the added complexity involved in verifying that users haven’t tampered with the data inside their cookies—was that “cookie-based sessions are dramatically faster than the alternatives”1. This is undoubtedly true if you invite your users to come to your data center and plug their computers into the local network before using your website. But if, as I suspect is often the case, your users are using your website via DSL, Cable, or—God forbid—dialup, this is almost certainly a naughty naughty lie.

Performance problems

According to research carried out by the Yahoo! Exceptional Performance team, a 1000-byte cookie adds 16ms to the response time of a single request made over a broadband DSL connection2. Now imagine a typical web page that includes one external CSS file, two external JavaScript files, and five images, all hosted on the same domain. That’s a total of nine requests, which means that little 1000-byte cookie has added 144ms to the response time of the page (and that’s for broadband users, a best-case scenario).

“Pish posh!” you say. “144ms is nothing! The snap of a finger! The blink of an eye!” Nevertheless, Amazon found that a 100ms increase in response time resulted in a 1% decrease in sales, and Google found that a 500ms increase resulted in a 20% decrease in traffic3. In my day job at Yahoo! Search I’ve seen these findings verified again and again. The tiniest change in response time can have significant effects on abandonment, user satisfaction, and overall traffic.

Security problems

When you store session data in cookies, you must be absolutely certain that users can’t tamper with the data in any way. There’s no way to keep users from altering the data in a cookie; it’s ridiculously easy. So, in order to ensure that your website doesn’t accept cookies containing altered data, you need to either encrypt the cookie values or sign them with a hash that allows you to verify their integrity. This introduces more complexity into your application and increases the number of possible attack vectors that malicious users can exploit.

Most web frameworks that support cookie-based session stores try to hide this complexity from the developer by performing the cookie validation themselves. However, in order to encrypt or sign cookies securely, you need a secret key (like a password) known only to the server; otherwise, an attacker who knows the hash inputs could easily reproduce the hash and forge a valid cookie signature.

Obviously the web framework can’t choose this secret key for you, so it’s up to the developer to do this. Many web apps that use cookie-based sessions come with pre-configured secret keys. As you might have guessed, this results in epic failure of the worst kind since web developers are lazy and often don’t bother changing the default key. Some folks on the Rails Core mailing list recently discovered this and are currently flipping out about it as if it were some kind of surprise or something.

Do the right thing

Server-based session stores are the industry standard for a very good reason: they work well and they’re relatively foolproof compared to the alternatives. They do put some extra load on the server, and sharing session data among multiple frontend servers can be a tricky problem (although there are plenty of solutions), but it’s almost always better than resorting to a cookie-based session store.

As with most techniques—even techniques with as many downsides as cookie-based session storage—there are some rare cases when it actually makes more sense than the alternative. But these cases are very rare, and cookie-based session storage certainly should not be the default in any web framework.

1 Rails Trac – Changeset 6184

2 YUI Blog – When the Cookie Crumbles

3 Make Data Useful – presentation by Greg Linden at Stanford

Try to use one var statement per scope in JavaScript

JavaScript’s var statement declares and optionally initializes one or more variables in the scope of the current function (or as global variables when used outside a function). Since var accepts multiple declarations, separated by commas, there’s usually no reason to use it more than once per function; it’s just a waste of bytes.

Overuse of var statements is one of the most common problems I see in JavaScript code. I was guilty of it myself for quite a while and it took me a long time to break the habit.

Bad:

function getElementsByClassName(className, tagName, root) {
  var elements = [];
  var root     = root || document;
  var tagName  = tagName || '*';
  var haystack = root.getElementsByTagName(tagName);
  var regex    = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');

  for (var i = 0, length = haystack.length; i < length; ++i) {
    var el = haystack[i];

    if (el.className && regex.test(el.className)) {
      elements.push(el);
    }
  }

  return elements;
}

There are several things wrong with the example above.

The most obvious problem is that I’ve used the var statement no less than seven times. Somewhat less obvious, but far worse: I’ve used it inside a loop, which means that I’m unnecessarily redeclaring a variable on each iteration. I’ve also unnecessarily redeclared two variables that were passed in as function arguments.

Naturally, there’s a much better way to do this.

Good:

function getElementsByClassName(className, tagName, root) {
  root    = root || document;
  tagName = tagName || '*';

  var elements = [],
      haystack = root.getElementsByTagName(tagName),
      length   = haystack.length,
      regex    = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'),
      el, i;

  for (i = 0; i < length; ++i) {
    el = haystack[i];

    if (el.className && regex.test(el.className)) {
      elements.push(el);
    }
  }

  return elements;
}

There are circumstances in which it is actually necessary to redeclare a variable within a single scope, but they’re very rare, and are more often than not a warning sign that you need to rethink the code you’re writing.

Improving e's performance

In my last post, I criticized e for being slow and bloated. As it turns out, e's sluggishness and high memory usage were being caused by an e.db file that had grown too large. Once I removed the huge database and started fresh, e's performance improved dramatically and its memory usage returned to acceptable levels.

As far as I've been able to tell, %APPDATA%\e\e.db is where e stores preferences and file history data. Over time, as more and more files are edited, this database grows, since it contains branching undo/redo data for every file you edit. The bigger it gets, the longer it takes e to start up and the more memory it appears to use.

E's author says that the memory usage reported by Task Manager includes non-paged virtual memory and isn't indicative of e's actual RAM usage, but he still hasn't responded to the question of why a large e.db seems to have such a detrimental effect on startup time. He did suggest creating milestones every so often to clear a file's history, so I'll try that and see if it improves things.

In any case, e is running smoothly once more and I'm happy with it again. Hopefully the issues with e.db will be resolved eventually, but for now they're a minor inconvenience offset by the fact that e is such a pleasure to use in almost every other regard.

Coincidentally, e 1.0 was released today. Get it while it's hot.