It would certainly be the best of all possible worlds
to have ruby code run two orders of magnitude faster
without giving any hints to the compiler.
In theory, then, should it truly be possible for the
ruby compiler to generate code that keeps floating
point values in registers, performs arithmetic on them,
and then passes the resulting values off to other
methods, without ever allocating the float objects on
ruby's heap?
(Or maybe such a thing would instead be achieved at
run-time through JIT'ing and sophisticated hotspot
analysis?)
Anything is possible. It's just really hard.
Even the best optimized dynamic language runtimes, many of which can
put values into registers and turn math into extremely fast low-level
native operations, still need to obey the semantics of the language.
In Ruby's case, that means you still need to ensure that the values
being passed into a numeric algorithm are actually Fixnums and make
sure that Fixnum itself has not been modified. If you don't check the
former, you may do optimized math against a non-math value. If you
don't do the former, Fixnum's mutability could lead to method
replacements (uncommon for math, admittedly) not being reflected in
already-optimized code. Performing those checks might be cheap, but
it's never free. In Duby, as in the JVM below it, if you're working
with a primitive int, you'll always be working with a primitive int.
Similar (but perhaps not as strong) guarantees apply to non-primitive
types.
Some of the more impressive Smalltalk benchmarks I've
been able to locate so far, still hedged a bit with
the following caveats:
http://www.cincomsmalltalk.com/userblogs/buck/blogView?showComments=3Dtru= e&entry=3D3354595110
"One more note about the Integer Arithmetic benchmark.
C# is smart enough to move memory variables to registers
at the start of the method and then just perform the
arithmetic in the registers. This isn't really typical
behavior in a real system where the arithmetic operations
are scattered throughout the system. In order to make it
more fair, I made the variables into instance variables
and marked them as volatile to force C# to fetch and
write them all the time. This makes the comparison to
Smalltalk much more fair."
That seems a bit disingenuous to me. =C2=A0The advent of such
in-register optimizations is what finally allowed us to
stop using the 'register' keyword in C about 15 years ago.
Now it's implied we can toss that all aside in order to be
'fair' to Smalltalk?
I'm going to have to call shenanigans on that.
Shenanigans indeed! I've made the same sorts of claims myself, like
"JRuby is only a couple times slower than Java, /for equivalent
work/". That's my subtle way of saying "Java numeric algorithms that
work with all boxed 64-bit values and do virtual dispatches for all
math operations." It evens the playing field, but of course nobody
writing Java would write numeric algorithms performing virtual calls
against boxed 64-bit values. In my defense, however, math is one of
the few areas where JRuby will probably never be as fast as Java (or
Ruby implementations that don't need to use objects for Fixnums),
since our dynamic calls are almost as fast now as Java's virtual or
interface calls. If you're working with a bunch of non-numeric objects
in JRuby, we do very well.
But anyway, if it's truly theoretically possible to achieve C-like speeds= in
ruby without giving any hints to
the compiler then I'm all for it (obviously).
It's certainly possible to do much better than we have been doing.
MacRuby and Rubinius, for example, are now showing extremely good perf
numbers for numeric algorithms. In Rubinius's case, the recent JIT
work has started to make it conceivable that a pure-Ruby standard
library may be able to get within a few times of MRI's
implementations. And experiments with JRuby have been able to double
or triple our current Ruby performance, simply by making it easier for
the JVM to optimize Ruby code the same way it optimizes Java code. We
are able to approach both MacRuby and Rubinius even for numeric
algorithms where they have an advantage. It's an uphill battle--the
nature of Ruby is such that even our best efforts are littered with
performance-stealing typechecks--but we're all making great progress
and learning from each others' efforts.
Even if Ruby could run as fast as C (or Java, in my case) it would
still not fit the other requirements of Duby, like having no runtime
library or being able to present "real" Java classes and methods. So I
think even under the best circumstances, Duby (or something like it)
is still needed.
Otherwise, the remarkably unobtrusive type hinting Charlie
is experimenting with seems to me _vastly_ preferable to
the current situation of having to drop into C to get the
speed. =C2=A0I don't *like* static typing. =C2=A0But I like having
to drop from Ruby into C even less.
Just as I find it ironic that I work all day, every day, implementing
Ruby...by writing in Java. It has led to a very solid, reasonably
performant implementation, but it would be a lot more fun to write in
something Ruby-like. If I can get much of what I like about Ruby by
using Duby and pay no performance penalty *at all*, I'd be very happy.
And you all might find it easier to contribute to JRuby, too
I suppose the worry must be that there would be a slippery
slope, where if such static type hints existed in Ruby,
they would be overused, and we might end up with a
proliferation of third party gems and libraries which were decidedly
un-ruby-like.
Perhaps it would be possible for the compiler to always
generate two versions of any method which was declared with
type hints: the static version, and a purely dynamic
standard ruby version. =C2=A0Then such type-hinted methods would
in effect provide an optional fast-path for users who were
in need of maximum speed, without imposing any un-ruby-like
restrictions on other users?
If that were possible, would there be any downside? =C2=A0(An
honest question. =C2=A0I'm not thinking of any myself, so far.)
Interesting that you suggest this. I am also going to add dynamic
dispatch to Duby.
A second language project of mine, Surinx, is essentially Ruby syntax,
Java/JVM types, but all *dynamic* calls. It requires the dynamic
dispatch support coming up in Java 7, and it has a small runtime
library, but otherwise it is also written entirely in Ruby.
Performance is rather interesting with Surinx; it's much faster than
JRuby, since it doesn't have to deal with a mutable type system and it
lets the JVM optimized dynamic calls. For "equivalent work", Surinx is
only about 50% slower than Java, and that gap is likely to narrow as
the Hotspot engineers improve dynamic call optimzations.
But there's no real reason to keep the languages separate, so I intend
to merge them. The new language will be statically typed, except where
types cannot be statically determined (or perhaps where you explicitly
declare them as dynamic). If you write with no static types at all,
there will be very little to distinguish the syntax from plain old
Ruby (though of course it's still *not* Ruby, because it's still based
on Java's type system and class libraries).
Dean Wampler suggested the name "Dubious", which seems to fit its
dubious nature extremely well.
Surinx and Duby codebases are on my github account.
- Charlie