
How I Made My Ruby Project 10x Faster - jjhageman
http://www.adit.io/posts/2013-03-04-How-I-Made-My-Ruby-Project-10x-Faster.html
======
orclev
Is it wrong that I was secretly hoping the answer was "I rewrote it in
something that isn't ruby"?

~~~
joemaller1
At this point on HN, I was expecting a page that just said "I rewrote it in
Go"

~~~
unknownian
Or Erlang/Elixir.

~~~
SkyMarshal
Damn, busted. Elixir was the first thing that popped into my head on seeing
this title. It looks almost like a Ruby port to the Erlang VM.

That said, good writeup, even for a non-Rubyist, enjoyed seeing the process.

One thing that stuck out is how control flow via if/then/else is faster than
via case (for more than 2 options) in Ruby. I always assumed the opposite, for
any language.

~~~
ics
There's an older (but still interesting) post about case in Ruby here[1] and
another here[2] with some extra insight into the MRI implementation. Having
learned Ruby before most of the other stuff I play with these days, I always
second-guess myself when using a switch/case construct in my code.

[1] [http://www.skorks.com/2009/08/how-a-ruby-case-statement-
work...](http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-
what-you-can-do-with-it/) [2] [http://www.daniellesucher.com/2013/07/ruby-
case-versus-if/](http://www.daniellesucher.com/2013/07/ruby-case-versus-if/)

~~~
SkyMarshal
Nice writeups, thanks!

------
jfarmer
alias_method is faster than what he did before because he's replicating a
bunch of internal logic at run-time, e.g., binding "self" to the correct
thing. Somewhere deep in the bowels of the Ruby interpreter it's going to be
doing the same thing, except it has the opportunity to create the binding
statically at define-time. Likewise, he's calling a method on a Method object
to invoke the original method vs. invoking the original method directly.

This might speed things up a little more, too:

    
    
      alias_method :"original_#{name}", name
      class_eval %{
        def #{name}(*args)
          self.original_#{name}(*args)
        end
      }
    

"send" is going to be using extra machinery in the same way, although I think
"send" also circumvents certain protections like public/private, so I'm not
100% sure where in the Ruby method invocation process it fits in. "send" might
be closer to the metal, but a pre-defined method name would have the
opportunity to cache certain things in the invocation process.

------
scosman
All of the performance improvements combined were about 0.00002s per call.
They came at the cost of simplicity.

~~~
cortesoft
This was my thought as well. This is one of the main fallacies of code
optimization; unless these functions are called a LOT, you very well might be
optimizing something that is not your bottleneck. You can see the author is
missing this point because he talks about going from "20 seconds to 1.5
seconds." He is using those numbers as if they are real use case times, when
in fact they are generated from an arbitrary amount of calls to a function.

So many times I will see people make code optimizations of this type to shave
fractions of a millisecond from a function call at the cost of complexity,
memory usage, etc. Meanwhile, most of the time their program is actually
waiting on IO or something of the like.

Blind optimization is counter productive.

~~~
nonchalance
You don't know what project he was using that gem for. It's very well possible
that he did some tests and found that this contracts library _was_ the
bottleneck. If that were the case, then the approach makes sense.

Although, given the nature of the library, I highly doubt that was the
bottleneck

~~~
cortesoft
Yes, it is possible. If this was the case, however, it would have made much
more sense to include this information in the blog post. I think people would
have gotten more use out of someone explaining the process of finding the
bottleneck and then optimizing.

------
aneth4
Sort of takes away from the "power" and "beauty" of the language when you need
to dissect your code and understand the runtime to get reasonable performance.

~~~
rubydoobydoo
I can't believe this is the top comment at the moment.

First, let's get something straight. This project wasn't necessary, because
you shouldn't be type checking in Ruby if it is written correctly. That's
basically what contracts are, at least in this case.

To address your concern, Ruby's performance is plenty fast- like Java entering
the early 2000's fast. (You probably don't get my reference, but in the
mid-1990's, people complained Java was slow.) No one at work has complained
about the performance of our Rails app running on Unicorn, and we've not had
to do one performance tweaking iteration ever.

~~~
aneth4
I do get your reference. I was a java developer since 1996. 2000 was 13 years
ago and ruby is far older than Java was then, so hardly a valid comparison.

I am a ruby developer and ruby is dog slow.

~~~
rubydoobydoo
Almost the same # years experience here in Java, then in Ruby for the last
several.

Ruby is NOT dog slow compared in duration of time per activity on the
(virtual) hardware we have now than Java was in the early 2000s on the
hardware in those days.

Java is slower than writing machine code, but that doesn't mean I'm going to
start coding in machine code.

If Ruby is dog slow for you, you are probably trying to run JRuby in
development (vs. on server) which _is_ dog slow (though on server after
startup and compilation it is pretty darn fast), or you're starting Rails up
everytime you need to do anything (vs. the many ways around that).

If you've read JRuby is faster- they are talking about the server. They didn't
mean locally, recompiling all of the time.

It's true Ruby is older, but Ruby is usable today. I'm sorry that you had a
bad experience but please don't spread FUD.

------
Pxtl
tl;dr: bolting on a dynamic-typed imitation of static-typing is slow.

~~~
Locke1689
Contracts are not types and do not really behave like types in many ways.

~~~
catnaroek
And their core, types and contracts are the same thing: logical propositions
asserting properties of a program that have to be enforced somehow. The
difference is the method of enforcement: types are enforced via static
analysis and contracts are enforced via runtime checks.

Oh, and if what you meant was "types cannot check pre/postconditions", then
you should have a look at dependently typed languages. Some dependently typed
systems can express any proposition expressive in first-order predicate logic.
(The downside is that satisfying the type checker becomes bloody hard.)

~~~
Locke1689
This is not correct.

Thank you for seeking to educate me about dependent typing, but I have a
Masters in PL theory, have done numerous proofs about PL and type theory in
Coq, and did my Masters' thesis on contract systems, under the person who
invented contracts in higher-order languages. I do, in fact, know what I'm
talking about. :) Unfortunately, it is quite a long explanation and needs a
full blog post to cover in detail.

A few notes, though: contracts can feature blame, which types cannot, and
types are proofs and contracts aren't (which can be both a plus and a minus
depending on your situation).

~~~
chrismonsanto
Another PL theory person checking in...

\- The word 'contract' has different meanings depending on which subcommunity
you are talking to. For example, the Penn crowd often uses the word 'contract'
in relation to refinement typing. The Scheme folks tend to think of the
problem in different terms, but I am not convinced there is any substantial or
insightful difference between the various definitions. As you seem to be an
expert in the subject, I am sure you have read the manifest contracts paper,
but I will leave this link here for the others:
[http://www.cis.upenn.edu/~mgree/papers/popl2010_contracts.pd...](http://www.cis.upenn.edu/~mgree/papers/popl2010_contracts.pdf).

\- I have never heard anyone say that types cannot feature blame. I would
argue that, for example, Haskell's type checker is quite good at blaming the
correct party for a type violation. To argue the opposite you would need a
precise definition of blame, and I am aware there are several papers written
on this subject. I am sure you could find a definition of blame that excludes
static type systems, but I am not sure that definition would be useful.

\- Types are not proofs, but typing derivations are. Even if you consider
types and contracts to be different, the execution trace serves as a proof of
a dynamically checked 'contract' (which is really just a derivation in an
operational semantics).

~~~
Locke1689
_Types are not proofs, but typing derivations are._

This is definitely true, thanks for the correction.

 _I have never heard anyone say that types cannot feature blame. I would argue
that, for example, Haskell 's type checker is quite good at blaming the
correct party for a type violation._

I agree this is dependent on how you construct blame. In the sense I was
talking about, it would be difficult to construct a type system which blamed
in the same way -- the type system would essentially require a runtime checker
for independent pieces of the program crossing the contract boundary, which I
have seen constructions of, but is generally not seen as standard.

For background, my contract work was in Racket, so I am of the Scheme school
:)

------
blakeshall
I really enjoy write-ups like these. Anyone have a resource that is primarily
blog posts/articles like this?

~~~
karlmageddon
The rest of his block is pretty great --
[http://www.adit.io/index.html](http://www.adit.io/index.html)

Some of them have pretty cool original illustrations, too!

------
nfm
Does anyone know what's going on behind the scenes with `alias_method`? I'm
curious to know how it's implemented such that it was actually faster!

------
mosselman
Would have been more useful if there were more before-after examples.

------
Demiurge
Did you rewrite it in Go? I guess I should read the article.

OK, now that I've read it, profiling ftw :-)

