Re: RAD vs. performance
- From: "Dr Jon D Harrop" <jon@xxxxxxxxxxxxxxxxx>
- Date: 3 Jul 2006 08:11:35 -0700
Curtis W wrote:
Jon Harrop wrote:
Exactly. You've inferred very little. This is equivalent to OCaml
inferring "int" in this case:
# let rec f n = if n=1 then n else n*f(n-1);;
val f : int -> int = <fun>
No, I've actually inferred more than that. Your example isn't
equivalent--it only handles ints.
You're looking at the wrong example.
If you want it to handle something
else, you have to /rewrite/ it.
No, and I just gave you the code.
Actually, programming in this manner
has a name--it's called premature optimisation: you're emphasizing
performance before you even know you need to.
No, I'm emphasising static checking to catch errors. The fact that my
code will also be faster than yours is incidental.
and an "interface" in this case:
# let f ( = ) ( * ) ( - ) n =
let rec f n = if n=1 then n else n*f(n-1) in
f n;;
val f :
('a -> int -> bool) -> ('a -> 'a -> 'a) -> ('a -> int -> 'a) -> 'a -> 'a =
<fun>
The latter is precisely the kind of weak type inference that I was talking
about. It results in needlessly slow code.
In 3 out of the 4 solutions that I posted below the previous code would
be no slower than the specialized version, simply because the compiler
specializes it for you.
For factorial, yes. For functions in general, in the presence of
higher-order parameterised types, you're talking about n^m growth in
the complexity of your compiler, in the slowest part (the optimiser),
so your compile times must go through the roof in order to optimise
away the unwanted abstraction that you have imposed upon us.
ML programmers could write all of their code in the latter style, as you
advocate, but they don't because you rarely need that abstraction. For
example, I have never needed that abstraction.
That doesn't somehow not make it useful.
It is empirical evidence that you are solving a problem that doesn't
need solving.
For example, because everybody
writes like that, it's impossible to use the standard arithmetic
functions with your custom float class, which causes needless code
duplication (you can't very well change the standard library, or at
least it would be a bad thing to do).
Again, where is all of this code duplication?
There are four ways to deal with this, which all have different
strengths and weakenesses: the first approach is the most simple--don't
do anything; leave it in its generic form.
Slow run-time.
Yup. Usually this doesn't matter, though.
Do you want to compete with static typing or don't you?
The second
approach, which might sound familiar to you, is for the programmer to
override the compiler's decision specify an exact type
More verbose than static typing.
Only slightly:
def factorial(x:int):
if x == 1: return x
else: return x*factorial(x-1)
3 characters, wow, that's really verbose!
Do you want to compete with static typing or don't you?
Write down the algorithm that the programmer must execute in order to
determine which code hot spots require type annotation. It's hard and
time consuming.
Then consider what happens for non-trivial examples with more
arguments, parameterised types and so forth...
to use or manually split the function into multiple functions.
You're implying code duplication when there is none.
Actually, I just realized this could be rolled into the second one:
def factorial(x:int|float):
if x == 1: return x
else: return x*factorial(x-1)
Of course, I still find this very inflexible and unnecessary most of
the time.
In this case, I'd have an int argument and generalise the result using
a cast and multiply:
# let rec fact cast ( * ) n = if n=0 then cast 1 else cast n * fact
cast ( * ) (n-1);;
val fact : (int -> 'a) -> ('a -> 'a -> 'a) -> int -> 'a = <fun>
What about the third approach: good old HOFs (see my 2nd factorial function
above).
What about the fourth approach, OOP:
# let rec f n = if n#eq 1 then n else n#mul(f(n#sub 1));;
val f :
(< eq : int -> bool; mul : 'a -> 'a; sub : int -> 'a; .. > as 'a) -> 'a =
<fun>
All my solutions imply having this.
How? You didn't mention HOFs or OOP.
This is equally
fast as the other approaches, but requires the most work for the
programmer and therefore isn't desirable except in extreme
circumstances.
They aren't "extreme" circumstances, you drama queen. How many types of
factorial function do you want? Two?
One.
How many specialisations of your factorial function do you want?
This has the best speed to
size ratio, but is slow and doesn't deal with shared libraries.
Exactly.
Of course, the speed issue doesn't matter if you only ever do it for
release builds, and the shared library problem can be dealt with by
using the fourth approach when appropriate.
Right, but you want the error catching that you can only get with whole
program recompilation when you're doing development, and that isn't
feasible.
The
fourth approach, my personal favorite, basically involves splitting off
separate versions of the function automatically which are optimised for
1) built-in types matching the profile,
Code explosion imminent.
No more than equally flexible static code.
In the vast majority of cases, we don't want that flexibility. It is
too loose.
2) user defined types matching the profile in this module
Which is often not the case.
Maybe, maybe not.
Not only is it often not the case, you have needlessly hindered other,
more useful abstractions. For example, a compiler is likely to define
its expr type in a separate module so that it can be shared by the
parser and evaluator. With your approach, this will be crippled.
and 2) unknown user defined types, respectively.
Back to poor run-time performance.
Performance isn't top priority when you're developing an app. That's
for after you finish getting the thing to work.
Performance and error catching go hand in hand here. If you can't
optimise because you lack the static type information then you also
can't do the static type checks.
Of course, this has
the disadvantage that it's not always going to be as fast as the full
program compilation approach, but it provides a nice compromise.
From my point of view, this will most likely give slower compile times and
slower run times that static typing. That doesn't sound like a "nice
compromise". It sounds like a bad design decision.
Funny, I'd say the same thing about your premature optimisation
approach.
Despite the fact that there is a lot of evidence to back up what I am
saying (e.g. performance of compilers and compiled dynamically typed
languages, utility of static checking) and little to back you up (e.g.
no code duplication, no aggressive factoring into HOFs).
Of course it can. I could write a program to do such a source to source
translation of OCaml code. I'm not going to, because I'm not about to
waste my time automating a process that I have never needed to do by
hand...
At which point you've expended more time and effort than using one of
the methods above.
Nonsense. We're both talking about developing compilers. You can't disregard
the time required to write yours and then complain about the effort it
would take to write mine (not that anybody needs mine).
No, I was talking about compile time. Actually, I just realized your
approach is essentially an ad hoc full program optimizer--one that
probably runs slower than a specifically tailored one that fits into
the compiler.
On the contrary, I am saying that you can get the same functionality by
writing the opposite of an optimiser - a program that adds abstractions
to statically-typed programs. It would be very fast to execute (it is
much easier to add abstractions than it is to optimise them away) but I
don't think it would be useful because people simply don't need the
flexibility that you are advocating. Moreover, every time you abstract
your statically typed code it weakens the static checking and will let
more bugs through. The code will then require more unit testing and
debugging, slowing development.
Cheers,
Jon.
.
- Follow-Ups:
- Re: RAD vs. performance
- From: Curtis W
- Re: RAD vs. performance
- References:
- Re: RAD vs. performance
- From: Jon Harrop
- Re: RAD vs. performance
- From: Curtis W
- Re: RAD vs. performance
- From: Jon Harrop
- Re: RAD vs. performance
- From: Curtis W
- Re: RAD vs. performance
- From: Jon Harrop
- Re: RAD vs. performance
- From: Curtis W
- Re: RAD vs. performance
- Prev by Date: status (Re: lang effort: type conversions)
- Next by Date: Re: RAD vs. performance
- Previous by thread: Re: RAD vs. performance
- Next by thread: Re: RAD vs. performance
- Index(es):
Relevant Pages
|