15 Minutes of Fame and The Joel Effect 11

Posted by Christopher Smith Wed, 13 Sep 2006 16:45:00 GMT

Last night I experienced my fifteen minutes of ‘net fame. I had submitted my article rebutting one of Joel Spolsky’s comments on Ruby’s performance to both reddit and digg. I watched how my submission performed on each while it was on the “new” lists on each, and it didn’t seem to garner much excitement. On reddit the first person to vote on it voted it down (my best guess is that they were sick of Joel articles) and I got one digg and then dropped off from view. Ah well, I already had strong evidence indicating that most of what I say isn’t of interest to anyone, so this was just further confirmation.

Just before I went to sleep, I discovered the performance problems I was having with Typo, so I tweaked things and watched the logs for a bit, and that was when I noticed that my article was getting a lot of hits, and they weren’t all coming from ‘bots (which is what my logs usually look like). Sure enough, some generous souls had voted me up on reddit (digg never bumped me up beyond the initial digg, which is either an indictment of reddit or digg depending on your point of view ;-). In fact, I was in the top 4 on the hot list on both the main page and the programming subreddit for a brief while (actually, checking now I’m still in the top 5 on the programming subreddit). As of this morning I’ve had about 800 page views of the article from the non-bot world, which by comparison to the usual attention I get, makes me famous.

But that’s not the interesting part.

The interesting part is that when I looked a the rankings, particularly on the programming subreddit, it seems that anti-Joel articles were all over the hot list. In just the top 5 there was my article, one titled Coding Horror: Has Spolsky Jumped the Shark?, one titled Why Joel Is Wrong to say that Duck-typed Languages Cannot Optimize Down To a Single Jump (interestingly the title on the web page makes no mention of Joel), and another by DHH titled Outsourcing the performance-intensive functions.

So, when I said last night that Joel had kicked up a lot of dust, I was perhaps understating it.

Joel has been criticised for running his blog as a big publicity and recruitment engine for his company (to which the rather logical retort would be: “how unusual that an entrepreneur would leverage whatever assets they have, including fame, to help their company!”). His postings almost always get bumped up high on reddit, and his articles seem to get linked from all over the net. So, there is a strong incentive for him to keep posting to his blog.

Observing reddit though, you have to wonder if this produces and an opposing force in the blogosphere: critical incentive. See, by taking on Joel, you get a fair bit of traffic (since I was the Johnny come lately to the party, I suspect some of the other posts have drawn significantly more traffic), you make reddit’s hot list, etc. At this point it’s just a theory, which I’ve dubbed as “The Joel Effect”, but one can definitely observe that Joel bashing has become a bit of sport in the programmer blogosphere.

Now, before anyone comes up with an Underpants Gnome business plan that looks like:

  1. Critique Joel Repeatedly
  2. ?
  3. Profit!

I would like to point out that out of all the page views I got, I got zero AdSense clicks. I checked, and the AdSense ads were highly relevant to programmers, so I think it’s fair to say that programmers filter out sponsored links significantly more so than your average ‘net visitor. I probably could have made more money writing about some totally unsubstantiated rumor about a celebrity, or by listing out the “ten tips for finding the best mortgage”. So, you’re going to have to fill in step 2 with something fairly creative in order to make it to step 3.

On Efficiency, Scalability, and the Wisdom to Know the Difference 4

Posted by Christopher Smith Wed, 13 Sep 2006 00:11:00 GMT

Joel Spolsky has been on a tear lately. He’s managed to really kick up a lot of dust. I’ve ignored most of the excitement, but I couldn’t ignore his latest post. He seems to have completely confused the differences between efficiency and scalability and has curious notions about the reasons for the importance of either.

Let’s review: efficiency is the ability to get something done while consuming few resources. Efficient code uses less memory, less IO bandwidth, and less CPU time to get the same job done. Scalability is a made up term used in industry to refer to the notion of software being able to handle growing levels of work gracefully, where gracefully generally translates to “costs grow no more than linearly with the size of the work”.

All of Joel’s arguments were about efficiency, not scalability. This is important because particularly for web applications, it is well recognized that non-scalable solutions are really not that useful, so there is hardly any debate about that. Scalability is generally tied to algorithms and the infamous Big O notation, so it’s very hard to point at a programming language or other low-level component and say, “that’s not scalable”. You can find frameworks (sometimes libraries) and identify places where they fail to scale, but Joel declines to do so. His beef is with Ruby, so it is all about efficiency (and actually specifically CPU efficiency), despite all his statements about it being “scalability”.

Efficiency is an interesting point of contention. People tend to make a huge deal about it even when they shouldn’t, and ironically don’t tend to make a huge deal about it for the one reason they should. I’m surprised that Joel fell in to the same trap.

First, I haven’t yet seen an implementation of Ruby that is particularly CPU efficient. I haven’t looked at memory consumption, but I’d be willing to guess it’s not so great there. Like most languages, it’s fine for IO efficiency.

So, right off the bat, if your application’s limiting factor is IO, there isn’t an efficiency disadvantage to using Ruby. Check around, and you’ll find there are a LOT of apps that are essentially IO bound. If your competition is using C and needs 10 servers, you could use even dog-slow Ruby and still only need 10 servers, not 100.

Now, Joel claims you’ll inevitably run in to some place where you are CPU bound. I’ve seen exceptions to this rule, but for the most part he’s right. There is always some performance hot spot that comes up that needs to be optimized. A lot of the time, even that hotspot can be addressed algorithmically, which means that you really don’t care about the language it’s implemented in. In the cases where it ultimately comes down to a language runtime’s CPU efficiency, the faulty logic here is that because this one part of your app can’t be implemented in language X, then you can’t use language X for the rest of your app. That’s just silly. You can always implement that hotspot in some other language, provided your language has some reasonably efficient way to hand off computation to code in another language (which Ruby seems to do reasonably well). If it is the difference between 10 and 100 servers, it is probably worth the development overhead to do it.

Joel also pokes fun at “advocates singing hymns about developer cycles vs. CPU cycles”, which I found surprising as well. Sure, you have small parts of your application where CPU cycles are key, and it’s worth sacrificing developer cycles for that added efficiency, but generally for apps the bulk of your code is much more sensitive to developer efficiency, because developer efficiency translates to “more features that work better”. You can find evidence of this in almost every software paradigm: interpreters in embedded systems, languages like Lua bound to high performance C++ game engines in the gaming industry, web servers written in C calling PHP/Perl/Ruby/VBScript/Python/whatever which in turn invoke functions in highly tuned databases (and it’s worth pointing out that Yahoo and Google use PHP, Python and Java despite scaling their apps to literally thousands of servers), and desktop apps like Word whose core is carefully tuned C/C++ and assembler, but that use languages like Basic to implement a lot of their features. The biggest example is the web browser. Most web apps are implemented in XML and Javascript (neither of which are about to set any efficiency records) that are executed by some very highly tuned browsers. So, there is considerable evidence that while you often need some expertise with a CPU efficient runtime, for almost any problem domain, what Joel calls an “inefficient” runtime is still useful and desirable.

Joel also makes some funny claims about duck typing effecting performance. Sure, it has an effect, but it is hardly the kind of thing that can’t be overcome. Yes, it makes type inferencing harder, but the key word there is harder, not impossible. Lots of folks have demonstrated how you can do really simple things like “hey, if self is of type X when I make this first call, it’s probably of type X when I make subsequent calls, and in fact, I can prove that it is always true until someone loads some more code in to this image”. With a sufficiently clever runtime (which Ruby lacks at this time), you can and should be able to get to the point where you are no worse than half as CPU efficient as C code. Joel’s right on one key point though: Ruby lacks this at this time, and that is a concern, but the concern is one he fails to mention.

Read that last sentence again: after two paragraphs pointing out that efficiency isn’t really that important, now I’m saying it is. Isn’t life full of contradictions?

Efficiency *is* important because it is a fairly reasonable proxy for the maturity of a platform. There’s a funny little factoid about software: there is almost always a way to write code in a way that gives the runtime enough information to execute efficiently. When your runtime doesn’t do this, you have to ask the question: why?

Joel makes the argument that you should be able to get the overhead of a function call down to the level where it’s a single CALL instruction. First, a CALL instruction can be expensive, thanks to the wonder of cache misses. That aside, you can in fact get it down to where the overhead isn’t even a single instruction, thanks the the wonders of inlining. As Herb Sutter pointed out in his article “Inline Redux”, inlining is almost always possible, because there are so many places where you can do it (Java, which Joel suggests has poor performance, has runtimes that inline far more aggressively than most C/C++ runtimes). As I mentioned above you can do tricks with type inferencing that get around the performance costs of late binding, except for when you are actually taking advantage of late binding’s benefits (in which case, as per Greenspun’s 10th law, the late binding runtime probably performs better than most attempts to get equivalent capabilities using C/C++). The same can be said for automatic heap management through all kinds of tricks. You can get message dispatch or generic dispatch to perform like function dispatch for the cases where you only need the simpler functionality of the latter. Zero overhead bounds checking can be done by code analysis or in the worst case using page faults. Really, the list of performance optimizations available tends to trump the best efforts of language designers to make things slow. ;-)

So that brings us back to the key question: if your runtime isn’t that efficient, why? The answer is that nobody has put that kind of effort in to making it that efficient. It just hasn’t been worth the effort yet, and that strongly suggests that the platform just isn’t that mature yet. If it were, that’d be one of the things that would have been addressed along with integration with legacy systems, sophisticated development and debugging tools, dealing with corner cases that could break the runtime, building out a complete set of support libraries, integration with various platforms and technologies, etc., etc. The bottom line is that if efficiency hasn’t been tackled to the point where you are within being about half as efficient as the ideal solution, some of those things haven’t been addressed. While efficiency might not matter to you, at least one of those other things probably will. Lack of CPU efficiency should be treated as a strong indicator that some other shortcoming that really matters to you might exist.

Now there are important exceptions to this to consider, in particular there are languages like Erlang, where the whole point is to deal with one very difficult domain efficiently (distributed computing/parallelism), and they actively encourage you to use another language (C) for more “regular” tasks. Even in those cases, you can expect that Erlang will show some lack of maturity if you try to use it for “regular” tasks, but you’re probably more than happy to use it for what it’s good at, and use something else for the rest.

Now, a runtime can be lacking in CPU efficiency in your application domain and still be useful for you. It could be your problem domain is all about other efficiencies, like IO or memory, and the runtime is great for that stuff (indeed, CPU and memory trade offs in particular create cases where it really only matters if a runtime can be memory efficient). You might be at a startup where the maturity of your platform just isn’t as important as your ability to get something up and running before the company runs out of capital, and some degree of risk due to platform immaturity is acceptible. You might be working on a problem that is so complex, and your resources are so constrained, that just getting something that works is such a victory that nobody cares about how efficient it is, how well it integrates with other technologies, etc. Fine, but most people benefit from the advantages of working with a mature platform, and as such, efficiency is a very good proxy for the far more difficult to quantify property of maturity.

So yes, I’d agree that efficiency is important, but in none of the ways that Joel suggests.