Re: RAD vs. performance
- From: "Curtis W" <cwarren89@xxxxxxxxx>
- Date: 3 Jul 2006 09:33:56 -0700
Dr Jon D Harrop wrote:
Curtis W wrote:The other examples you gave were almost identical in functionality to
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.
mine. I thought you didn't like my approach?
Of course, yours would actually be slower than mine, since yours hasIf you want it to handle something
else, you have to /rewrite/ it.
No, and I just gave you the code.
abstractions that will be there, whereas my compiler can optimize them
away most of the time.
This has absolutely nothing to do with type checking. Mine will catchActually, 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.
just as many errors as yours.
Again, compile time doesn't matter so much when you only do it once perand 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.
release.
You mean, besides the fact that your standard library is so poorlyML 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.
written because of this fact? Take a look at the array and list
functions--half of them are identical.
In many, many pieces of OCaml code.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?
It doesn't particularly matter to me if I'm currently writing the code,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?
no.
Profiling is?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.
I thought I mentioned it when I was talking about operators.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.
Doesn't matter to me, so long as it's fast.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?
What are you talking about? The type checker doesn't require wholeThis 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.
program compiling--the only reason the optimiser needs that is because
/it's changing a function/, whereas a type checker simply verifies the
statements against publicly available type information from that
function.
How can you be "too loose"? I've already shown you that performanceThe
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.
would rarely be an issue and that type checking isn't harmed.
The evaluator would be bundled with the types as a member function. The2) 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.
parser doesn't even do anything other than instantiate types, so there
isn't even a problem there.
No. See above.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.
What kind of response is that? I say premature optimisation is a badOf 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).
design decision and you throw me statistics on the performance of
compilers?
That may be a fault of /your/ type checker, but mine handlesOf 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.
abstraction fine. It seems to me you're just throwing stuff out there,
now, since that doesn't even really make any sense.
.
- Follow-Ups:
- Re: RAD vs. performance
- From: Dr Jon D Harrop
- 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
- From: Dr Jon D Harrop
- Re: RAD vs. performance
- Prev by Date: Re: RAD vs. performance
- 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
|
Loading