How I Made My Ruby Project 10x Faster
adit.io
adit.io
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.
[1] http://www.skorks.com/2009/08/how-a-ruby-case-statement-work... [2] http://www.daniellesucher.com/2013/07/ruby-case-versus-if/
Contracts are not needed and it obfuscates the error raising. Just use plain Ruby if you need to raise an exception. I'm will seriously quit using Ruby if someone makes me use contracts.
But it looks like a fun project to write. Nothing wrong with that.
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.This may or may not translate to ease of use depending on the user.
1. Facilitating local reasoning: I do not want to read the whole program to understand what a single line of code may or may not do.
2. Compartmentalizing concepts: There should be a single, easily identifiable feature for each of: defining classes of data, separating concerns between different code units, enabling code reuse.
3. Keeping abstraction leaks to a minimum: Abstraction leaks force corner cases to be handled outside of the abstraction (that is, low-level programming). So abstractions should ideally not be leaky, and leaks should only be tolerated if 1. there is no nonleaky (or less leaky) alternative, 2. the benefits of the abstraction outweigh the cost of handling the leak.
Simplicity leads to conceptual clarity.
On the other hand, ease of use is more about the pragmatic desire to invest the least amount of effort possible in making a working program. At its most short-sighted extreme, the easiest thing to do is simply following the path of least resistance. A more refined version is following design patterns that experience tells work okay.
Simplicity and ease of use are most certainly positively correlated, but it is perfectly possible for a horribly contrived construct to be easy to use, if its complex internal details happen to match nicely the problem domain one is working on.
My heuristic take (admittedly not justified on anything beyond aesthetical preference) is that simplicity is the more fundamental concept, and ease of use is just a corollary. And my impression of Ruby is that it seeks to maximize ease of use, resorting to non-simple means if necessary.
In any case, this just reinforces my point that Ruby emphasizes ease of use, not simplicity.
Of course I hope nobody starts jumping through all kinds of hoops before they know that it matters...
If you want to guarantee that a property holds for all the elements of a set (every possible running instance of a program), forget about the elements and work with the set's definition (the program itself).
Judging the project from its feature list in the README.md, it does not seem like LASER is capable of handling arbitrary invariants.
And in the end, smaller overhead on the contract checker means that the programmer has more room to add extra tests and less reason to turn off the checks in production.
(Of course, this analysis of the situation changes drastically if you take into consideration the overhead on the poor programmers' brains. Static analysis is intellectually more demanding.)
If you need/want static analysis, the option for the foreseeable future is to use another language (and I say this as a huge Ruby fan that also would love to see an evolution of Ruby that'd make some/more static analysis feasible, but for that to happen I believe you need an effort to provide a module encapsulating common eval() behaviour and a huge effort to replace eval in common Ruby libraries - it's not likely to happen anytime soon).
It's almost always a horrible anti-pattern in Ruby to do "kind_of?" tests or similar, unless you depend on a huge portion of the interface of said class. But almost always code that does tests like that really only depends on a method or two, and doing class based tests makes the code vastly less reusable.
I don't want to dissuade you from this project. There are useful things you can do with contract specifications in Ruby that'd be interesting to explore. But I do hope to dissuade you from having all those class based tests in your tutorial for example...
Removing the unnecessary callback, for example.
And ".times" is certainly more idiomatic than ".zip" for his case. It's a toss up between .each_with_index and .times, but really both will be readable enough. And from experience, you'll find Ruby devs that are unfamiliar with "zip", but not with "each_with_index" or "times".
"alias_method" is also far more idiomatic than grabbing a reference to the method - I don't think I've ever actually seem method() used in production code (not that I've explicitly looked)
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.
Because it is. It's a clear case of premature optimization. Especially since nothing was offered up to to suggest otherwise.
> When do you think an optimization like this would be appropriate?
When it's being reported back that it's one of the top bottlenecks. Before that, it's mostly a educational trip. It's great to learn how to write optimized code, but, frankly, removing empty function calls isn't an optimization.
> Can you give an example of a performance optimization you have made recently?
Eliminating the need for excessive memcache calls by not only changing the objects being called, but also by eliminating the need to make the calls in the first place by intelligently using existing locally cached… cache. If that makes sense. Anyways, that improved performance by ~20ms. It wasn't the slowest call running, but it was the slowest call running that could be optimized at this point.
Although, given the nature of the library, I highly doubt that was the bottleneck
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.
I don't have enough experience with Ruby to comment about it, but as far as Java is concerned, people (myself included) are still complaining it is slooooooooo... <let me pause a bit to collect garbage>... ooow. Don't get me wrong, Java is an excellent language which has plenty of other benefits (as I guess Ruby does too), but raw performance is not one of them.
I am a ruby developer and ruby is dog slow.
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.
Constraining inputs to a specific class, for example is a huge, giant no-no. If you're going to make Ruby statically typed, then you're better off picking a language that's easier to make fast.
This is a pet peeve of mine as I've more than once had to work around arbitrary, stupid checks against specific classes in cases where meeting the "actual" contract of the method called would be down to implementing a simple method or two.
The tutorial hints at some possibly Ruby-ish possibilities, like contracts based on whether an object responds to a method, but all the focus seems to be on class comparisons.
I do like the idea of specifying contracts, fwiw, as long as they're focused on behaviour and not on matching specific classes.
I don't, however like the idea of specifying them in the class files. Specifying them in separate files, that could be loaded when wanted, and that could also be used by a testing app to do some degree of automated testing of limits etc., or by other apps to do limited static analysis would be interesting.
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.)
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).
- 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....
- 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).
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 :)
Avoid creating objects that aren't needed, and avoid method calls.
For the most part that's what his changes boils down to. In other words: Reduce complexity. It's very easy to cause Ruby code to blow up performance wise because you don't realize how many extra objects you create (arrays and hashes + lots of chaining or functional inspired method that returns copies instead of mutates == boom) or how many extra method calls you trigger behind the scenes.
Some of them have pretty cool original illustrations, too!
OK, now that I've read it, profiling ftw :-)