Ben Giddings wrote:
…
Alex Martelli wrote:
Heh – matter of taste, I guess; what I personally find ugly is
…
Sorry, I wasn’t completely clear. It’s the leading underscores that I
object to. I don’t mind underscores_in_names at all. The reason I
dislike leading underscores is that they tend to be holdovers to
languages that don’t have proper access control. (C mostly)
Or Python – no “access control” whatsoever, just advisory indications
of what’s meant to be “published” and what’s meant to be for internal use
only. Just the time saved not having to decide what’s private, public,
or protected (Stroustrup regrets having introduced the complication of
that third intermediate classifier – see his book “Design and Evolution
of the C++ programming language”) is by itself a huge performance boost.
In any situation where someone is tempted to use them, I find that they
normally should be using something else (like using instance variables
which are by their nature private).
Given that in Ruby one can always, anywhere, reopen a class and add
getters and setters for all attributes of interest in a few keystrokes,
I guess the “by their nature private” issue doesn’t cost you much (except
some wasted performance, perhaps?) but doesn’t buy you much either (no
more than in C++ with its typical “#define private public” trick, say).
Java, at least in theory, does enforce privacy strictly (but I’ve
seen too many security issues with JVM’s to trust it, personally;-),
so there are “real” costs and benefits involved; the same might be said
of a very few implementations of C++ (I’m thinking of SOM, but it’s SO
many years since I used it that I may have the details wrong – clearly
the strict enforcement didn’t do much for the market success of those
implementations of C++ – against MS, Borland, and gcc, say;-). But as
long as the “protection” is fundamentally just advisory, I don’t think
there’s much in it, either as a cost or as a benefit. I’d rather have
a simpler language relying on a simple naming convention than a more
complicated language with elaborate advisory mechanisms, personally.
As for the ugliness of ‘$’, it has been said that it is supposed to be
ugly. Global variables in general are ugly code, and so people tend to
avoid using ‘$’, making their programs better.
I have nothing against this line of reasoning. I’m still allowed to
make the code a BIT less ugly (in my eyes) by using $_foo instead of
base $foo, though. By the same token, literals in your code are ugly
(they should all be constants, right?) – so do you object to Ruby
letting me write a prettier (and more readable, IMHO) 1_000_000
rather than an uglier (harder to read, must count 0’s) 1000000 …?-)
However, it will be very rare for any given x to NOT be already
memoized (i.e., fact is called with a very modest variety of
arguments, compared to the number of calls to it, in any typical
combinatorial-arithmetic application); while calls with x<0 are
going to be exceedingly rare. So, the relative costs of array
indexing vs (operator< + conditional) shouldn’t matter – trying
to get the result off the array first should be a win anyway
(or at least, that’s what my optimization experience as built on
a wide variety of languages tells me – admittedly I have no such
experience in Ruby, but I fail to see how it would differ on this
specific point).
Ah, I see what you mean. I’m sure you’re right that it’s faster the way
you now have it. I didn’t think things through fully and realize that
95% of the time the number will be positive and so it will have to
evaluate the conditional then do the array lookup. It is also going to
be much faster to do the array lookup rather than the triple
conditional for the “comb” case.
Ayup. I did try both ways, btw – hard to measure the difference
in my current benchmark (it just doesn’t exercise fact enough, and
comb barely enough), but fwiw it does seem to be faster with the
“if” inside. (Anybody who’s done enough optimization will tell you
that rationalizing why A is faster than B is all nice and good, but
until and unless you’ve MEASURED them, you don’t really KNOW…!-).
Actually, I find the oneliner body
$_fact_memo[x] ||= if x<0 then 0 else x * fact(x-1) end
quite clear and readable, given that one must grok the semantics
of ||= anyway.
Yeah. That “||=” is a little hard the first time you notice it, but
Yeah, it was a little hard, about 15 years ago I think, when I
first met it – in Griswold’s “Icon” programming language;-).
once you realize how useful it is, you’ll use it so often it becomes
second nature.
No doubt. The Python equivalent (for a dict only, not a list, but
Python dicts are so fast that they’re preferable to lists in all of
these cases – yep I did measure both ways;-) would be:
return fact_memo.setdefault(x, ...whatever...)
BUT the applicative-order (vs normal-order – aka eager vs lazy)
semantics of function calls means that “whatever” would have to be
computed in all cases (even when x IS already a key in fact_memo),
making this construct useful in substantially fewer cases.
You could also use the other form, which as a C++ coder I’m sure you’d
recognize:
$_fact_memo ||= (x<0) ? 0 : fact(x-1)
Which also works in Ruby. I don’t like it as much, but it’s a
preference I guess.
A dream I’ll surely never be able to afford would be to have the same
projects tackled by pair of otherwise equivalent teams, but with two
languages that differ in just ONE characteristic – surely, in theory,
that would be a wonderful way to measure the worth of that one
feature. Out of all the possible "ONE characteristic"s to be so
tested, the single one I’d be most interested in studying that way
(if I had a few megabucks to spare in order to pay the temas;-) might
be the enthusiastic “more than one way to do it” philosophy of Perl
and Ruby, vs the principle which the C Standard phrases as “provide
only one way to do an operation”, and the Python Zen phrases as
“There should be one-- and preferably only one --obvious way to do it.”.
My guess is that, while there might be tradeoffs depending on the
quality, personality, etc, of the programmers, for small teams,
as teams grow larger, uniformity (with its attendant help in
moving towards the “collective code ownership” ideal of XP) would
no doubt become preferable. Of course, one could try to enforce
uniformity by other means (static code checkers, regular code
inspections, etc, etc), but they’re all costlier than not having
the variability in the language in the first place (and not having
the variability also makes the language smaller and thus faster to
learn, to master, to implement – again, a “spirit of C” principle,
“Keep the language small and simple”, which I revere).
Oh well, it will no doubt remain an (educated) guess – and the
uniformity, even in C or Python, is only an ideal, anyway.
But for the life of me I just can’t see why, when one has
“if a then b else c end” working perfectly as both an expression
and a control statement, one would WANT to weigh down the language
with an alternative but equivalent syntax “a?b:c”. I guess a
definitely-NOT-postmodern aesthetics which appreciates simplicity,
uniformity, etc, makes me characterially unsuited to appreciate
this “enthusiastic redundance” idea of Perl and Ruby!-)
Very good point, and an excellent suggestion at least for concision &
readability purposes – I’ll definitely keep the idiom
[] ||=
in mind for the future – thanks!
Note it also works for non container objects:
var ||= “new val”;
Noted, thanks (I also notice the redundant trailing “;”“”…?-) – I
guess this means that keeping variables initially undefined may be
a sensible strategy in Ruby where it wouldn’t be in C or Python.
and in other types of expression:
var = other_var || “default val”
Sure (but that’s quite OK in Python too, since you can more legibly
spell “||” as “or” in this case, and then it does short-circuit –
the Python issue is that there’s no shortcircuiting “or=”, as well
as the fact that undefined variables raise exceptions rather than
quiety returning nil [or None, in Python]).
Performance-wise, there ain’t all that much in it for this app.
I’ve managed to scrounge some diskspace on Linux and compile both
Python and Ruby (1.6.8 only, unfortunately – the link I downloaded
from mentioned 1.8, but I found out only when everything was built
that I had 1.6.8 instead [how I found out: I had thoughtlessly left
a trailing colon on an “if” statement – 1.8 was quite cool about
it, while 1.6.8 gave me three weird errors for that one mistake;-)].
If you have the chance to try it on Ruby 1.8 I (and others I’m sure)
would be interested in hearing how it stacks up both to Python and to
Ruby 1.6.
No doubt I will once I’m back home – I find that I can’t work on
this as much as I had hoped while on this business trip.
(In another post) Alex Martelli wrote:
While I doubt you should compare these languages on performance merits
blink why not, pray? While clarity, productivity, simplicity, and so
on, are surely all crucial attributes in a language, what’s wrong with
wanting to ascertain how it performs (when used in the best/fastest way,
at least) in typical problems in one’s domains of interest…?
I don’t think that it’s wrong to compare the speed of the languages. I
just wouldn’t be surprised if Python is faster by a healthy margin,
especially for a very simple numerical program like this. In Ruby,
everything is an object, including numbers. This has certain
advantages, but definitely carries some speed penalties.
I fail to see where the speed penalties are inherent, given that the
semantics of integer numbers in Python and Ruby are so similar now
(e.g., fixnums, aka “int”, now transparently give bignum, aka “long”,
results in Python, too). Indeed, in Python, there isn’t that deep
a distinction between int and anything else as there seems to be in
Ruby between Fixnum (and true and false and nil - that’s all I think?)
and everything else – surely by singling out fixnums that way Ruby
IS gaining some speed advantage, at least for those computations (a
fair chunk) that stay within the roughly-one-billion limit in this
benchmark. Besides, it appears to me (from that one attempt to run
the profiler) that the bottleneck isn’t in the arithmetic, anyway,
but rather in the generation of the subsets with that recursive
iterator in each language (so perhaps my next step should be some
attempt at recursion-elimination, muses he consequently). In other
words, I’m not really sure this IS “a very simple numerical program”;
where I come from (Fortran, my first language, back then…) by “a
very simple numerical program” we mean something for which enumerating
subset by recursion would never qualify (let alone iterators).
Anyway, I’ll grow this to definitely-NOT-simple eventually. Will
the extra complication of the problem being solved favour Ruby’s
performance? Stay tuned…;-).
I’ll accept your evaluation that you’re not surprised if Python is
faster by a healthy margin, but I’d rather a different explanation
than the one you give. WHAT do you think is NOT “an object” in
the Python program, giving Python an intrinsic speed advantage…?
I can’t see anything, myself. Indeed, I’d look at the reverse issue:
in Python “a tuple of numbers” is an object and thus can index right
into a dictionary – in Ruby, I was informed, when it LOOKS like I’m
using a tuple to index into a Hash, in fact the interpreter is working
hard under the covers at forming a string out of that tuple – so, it
seems to me, the performance handicap is that “tuple” is NOT an
object which can (directly) index a Ruby Hash (here I’ve fixed it, as
suggested, by using x<<16+y, rather than [x,y], as the key into the
Hash, but I wonder about what happens when I have several numbers
instead of just a pair of them – as will soon be the case as my
example script grows more ambitious… but the numbers will be small,
so perhaps enough << and + will work well anyway – we’ll see!).
By using a scripting language rather than assembly or C, you’ve already
decided you’re willing to sacrifice some performance for ease of use. I
You might be surprised. Try coding in standard C or assembly a program
that must work with integers as large as 52!, without using some finely
tuned external library for the pupose, and we’ll see what performance
you do get…;-). [In practice, multiple-precision arithmetic is NOT
trivial to code with decent performance]. Sure, I could use GMP, say –
but then I could use it for Ruby or C just as well, and the variability
becomes unbounded. I am interested in comparing standard languages
as opposed to languages coupled with any one of a zillion potential
add-on libraries, after all.
And I’m not interested in blowing up my program by an order of magnitude
so I can include in it complete sources for multi-precision arithmetic
and hash tables. I want to compare programs of comparable complexity,
thus, languages of comparable semantic level – as indeed Python and
Ruby easily prove to be on this problem, with the code in the two
languages in such an obvious, nearly 1:1 correspondence. Given that the
semantic level is just about the same, performance discrepancies as
high as 2 or 3 times are STILL pretty mysterious to me.
expect that both Perl and Python would beat Ruby in nearly any
benchmark, but I find Ruby much easier to use than both. For tasks
where a scripting language is appropriate, I choose Ruby.
I know of no tasks where “a scripting language” is NOT appropriate,
except operating systems (kernels and drivers), and accelerators for
“scripting languages” (and, in the pypy project, we’re trying to drop
the second proviso, and prove that higher level languages CAN in
fact be made intrinsically faster than lower level ones – but that’s
a longer-term goal, of course;-). So, anyway, I’m trying to see the
“easier to use” part – which includes, IMHO, performance issues where
appropriate. I can easily see it wrt Perl – the OO, the fact that
the language is smaller and more elegant. But where does it come wrt
Python, given that Python is the smaller language (no deliberate
redundance) and the “elegance” pro’s and con’s clearly go both ways
(Ruby has some very elegant parts, those designed from scratch, but
e.g. all of those $_ $` $’ $! etc etc that it borrowed from Perl
surely can’t be considered elegant…???)…? That’s what I’m trying
to find out. Perhaps it’s domain dependent and just doesn’t show up
at all in combinatorial-arithmetic? OK, then once I’ve exhausted that
I’ll move on to something else – after all Python and Ruby are clearly
both perfectly general-purpose languages, so it’s not as if I’ll soon
run out of application domains to explore, hm?-)
Alex