Re: RAD vs. performance



Curtis W wrote:
Dr Jon D Harrop wrote:
You're looking at the wrong example.

The other examples you gave were almost identical in functionality to
mine. I thought you didn't like my approach?

That's the point. You can write in your abstracted style easily in ML
but people do not do this because it weakens the typing, slows the code
and is rarely used in practice (in the context of numeric types, at
least).

If you want it to handle something
else, you have to /rewrite/ it.

No, and I just gave you the code.

Of course, yours would actually be slower than mine, since yours has
abstractions that will be there, whereas my compiler can optimize them
away most of the time.

No, inlining the function arguments to the HOF recovers the original.
The only difference between the specialised and generic implementations
in OCaml is the passing of operators as function arguments.

No, I'm emphasising static checking to catch errors. The fact that my
code will also be faster than yours is incidental.

This has absolutely nothing to do with type checking. Mine will catch
just as many errors as yours.

No. This has everything to do with type checking. Both optimisation and
type checking require the same static type information. You cannot
optimise away your abstraction because you have thrown away the type
information (the best you can do is try to cover all possibilities and
use heuristics to keep compile time under control) so you cannot type
check as effectively because you do not have the type information.

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.

Again, compile time doesn't matter so much when you only do it once per
release.

During development, you do it all the time.

It is empirical evidence that you are solving a problem that doesn't
need solving.

You mean, besides the fact that your standard library is so poorly
written because of this fact? Take a look at the array and list
functions--half of them are identical.

Again, can you provide any examples? I'm looking and I don't see what
you're claiming. For example, here is Array.map:

let map f a =
let l = length a in
if l = 0 then [||] else begin
let r = create l (f(unsafe_get a 0)) in
for i = 1 to l - 1 do
unsafe_set r i (f(unsafe_get a i))
done;
r
end

and here is List.map:

let rec map f = function
[] -> []
| a::l -> let r = f a in r :: map f l

where is all of this commonality and code duplication?!

Again, where is all of this code duplication?

In many, many pieces of OCaml code.

Give some examples, please.

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.

Profiling is?

Reading a profile is time consuming, even if we assume that your
profile will contain all of the type information that is required.

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.

What are you talking about? The type checker doesn't require whole
program compiling--the only reason the optimiser needs that is because
/it's changing a function/,

.... according to its type.

whereas a type checker simply verifies the
statements against publicly available type information from that
function.

This is a question of specificity. In ML, the factorial function has
the type int -> int and the type checker will unify this type with any
uses of this factorial function in order to type check the rest of the
program. A type-specific version in your language will have the same
type checking done. A type-generic (e.g. int | float) version in your
language can only have the same type checking guaranteed if whole
program analysis is used to examine every call site (monomorphisation).
Alternatively, you can type check the union type int|float but this is
weaker type checking (the type is not as specific) so it will not catch
as many bugs.

This problem exists in OCaml, where polymorphic variants provide
inferred union types. The OCaml compiler cannot optimise these away
because it currently does not do whole program optimisation. If it did,
code using polymorphic variants could be made to run a lot faster. In
the mean time, polymorphic variants are comparatively rare for exactly
the reasons I've described (they are more weakly typed).

In the vast majority of cases, we don't want that flexibility. It is
too loose.

How can you be "too loose"? I've already shown you that performance
would rarely be an issue and that type checking isn't harmed.

Performance is an issue. This abstraction in OCaml costs about an order
of magnitude in performance when applied to int vs float, which is
equivalent to the case of your compiler being unable to optimise away
your abstraction, i.e. the worst-case scenario for dynamic typing.

Type checking is harmed. You have weakened it by abstracting int to
int|float|... which is a less specific type and, consequently, conveys
less information.

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.

The evaluator would be bundled with the types as a member function.

You're talking about OOP? Ok.

The
parser doesn't even do anything other than instantiate types, so there
isn't even a problem there.

But the parser and evaluator might be mutually recursive (e.g. having
"load" function) unless the expr type is declared somewhere shared by
the two.

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).

What kind of response is that? I say premature optimisation is a bad
design decision and you throw me statistics on the performance of
compilers?

As I've said, we don't do this to optimise our code, we do it to
statically check our code to remove more bugs. Over-abstracting is
every bit as bad as premature optimisation. People just haven't
realised it yet...

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.

That may be a fault of /your/ type checker, but mine handles
abstraction fine.

No, it doesn't. This is why dynamically typed languages offer weaker
type checking and either slower run-time performance or slower compile
time.

It seems to me you're just throwing stuff out there,
now, since that doesn't even really make any sense.

What doesn't make sense?

Cheers,
Jon.

.



Relevant Pages

  • Re: RAD vs. performance
    ... be no slower than the specialized version, simply because the compiler ... so your compile times must go through the roof in order to optimise ... but they don't because you rarely need that abstraction. ... I'd have an int argument and generalise the result using ...
    (comp.lang.misc)
  • Re: RAD vs. performance
    ... underused and undersupported (by the compiler). ... away the unwanted abstraction that you have imposed upon us. ... the type int -> int and the type checker will unify this type with any ... equivalent to the case of your compiler being unable to optimise away ...
    (comp.lang.misc)
  • Re: object system...
    ... what the C compiler typically produces... ... C++ is a versatile language, as one can use it in some places mostly like it ... in general, this compiler provided abstraction, will somewhat beat out the ... but I still don't see how this relates to "programming based on ...
    (comp.object)
  • Re: RAD vs. performance
    ... compilers do nothing to optimise such code. ... maybe the OCaml people should improve their compiler:) ... OCaml code is not usually written in that form, ... It is a union type (an expression is ...
    (comp.lang.misc)
  • Re: AVR Beginner Questions - Ports and Speed
    ... On bigger processors, a good compiler will ... while the assembly programmer might use a full-blown divide ... > 3) One of the most productive strategies is to code in C, but optimise ... Chosing the optimal form for loops, ...
    (comp.arch.embedded)