🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Thread View: comp.lang.functional
2 messages
2 total messages Started by Jan Roland Olsson Mon, 14 Dec 1992 15:27
Run-Time Code Generation ?
#945
Author: Jan Roland Olsson
Date: Mon, 14 Dec 1992 15:27
81 lines
3068 bytes
Functions that return other functions are common in
functional programs. The function returned is created
at run-time and is in strict languages most often 
represented using closures. 

In my view, a closure consists of 

1. A pointer to the machine code for a function.
2. One or more pointers to "arguments" of the function.
   These arguments may be other closures.

It sometimes happens that the function returned is
represented by 10 or more closures pointing to each
other. If the function is not recursive, these closures
normally form a tree that is isomorphic to the
expression tree for the right hand side of the
function definition.

A recursive function may be represented by closures 
that form a directed cyclic graph, where the cycle
can be introduced with a pointer i.e. a value of
ref type in ML.

I have a program in Standard ML of New Jersey that
synthesizes, i.e. writes, function definitions and
translates them to closures in order to execute them.

This works fine except for the fact that the resulting
functions execute about twice as slowly as functions
that are defined by ML source code and compiled.
Since the functions created at run-time are needed
over and over again, perhaps even millions of times
including recursive calls, this is a significant
performance problem for me.
Here is a small trivial example that illustrates
the problem.


fun compose 0 F = ( fn X => X )
  | compose N F = 
      let val g = compose (N-1) F
      in
       fn X => F ( g X )
      end;


val f1 = compose 5 tl;

val f2 = fn X => tl(tl(tl(tl(tl(X)))));


The functions f1 and f2 are the same, but calls
to f1 execute much slower than calls to f2.

What I really would like to have is a built-in
function 

compile : ( 'a -> 'b ) -> ( 'a -> 'b )

which has the same semantics as the identity 
function, but changes the representation of the
definition of its argument from lots of inter-
linked closures to pure machine code.
Since Standard ML of New Jersey stores machine
code on the heap as garbage collectable byte
sequences and since the compiler is part of a
running ML program this seems possible.

Am I the only ML programmer on this planet that
finds such a function useful ?

Or maybe there are some drawbacks or solutions that
I have overlooked ?

Or maybe I should use Common Lisp instead, which after
all has compile built in ? Needing to switch to
Lisp just for a performance problem like this would
really be a pity, since ML in my view is a much
better language and since Standard ML of New Jersey 
is an excellent implementation.
Re: Run-Time Code Generation ?
#951
Author: Torben AEgidius Mogensen
Date: Tue, 15 Dec 1992 15:24
64 lines
2546 bytes
olsson@ifi.uio.no (Jan Roland Olsson) writes:

>I have a program in Standard ML of New Jersey that
>synthesizes, i.e. writes, function definitions and
>translates them to closures in order to execute them.

>This works fine except for the fact that the resulting
>functions execute about twice as slowly as functions
>that are defined by ML source code and compiled.
>Since the functions created at run-time are needed
>over and over again, perhaps even millions of times
>including recursive calls, this is a significant
>performance problem for me.
>Here is a small trivial example that illustrates
>the problem.


>fun compose 0 F = ( fn X => X )
>  | compose N F = 
>      let val g = compose (N-1) F
>      in
>       fn X => F ( g X )
>      end;


>val f1 = compose 5 tl;

>val f2 = fn X => tl(tl(tl(tl(tl(X)))));


>The functions f1 and f2 are the same, but calls
>to f1 execute much slower than calls to f2.

>What I really would like to have is a built-in
>function 

>compile : ( 'a -> 'b ) -> ( 'a -> 'b )

>which has the same semantics as the identity 
>function, but changes the representation of the
>definition of its argument from lots of inter-
>linked closures to pure machine code.

You could try to rewrite compose into a somewhat more efficient form:

fun compose N F =
   let fun g 0 = (fn X => X)
         | g M = let val g1 = (g  N-1) in (fn X => F (g1 X)) end;
      in
       g N
      end;

This version does not carry F as a parameter to the recursive call.

f1 will still be slower than f2 when using this definition, but not by
the same extent. What is needed for this is that the application (g1
X) is unfolded so (fn X => F ((fn X' => F (g1' X')) X)) is reduced to
(fn X => F (F (g1' X))). This does not require compilation of the code,
but rather reduction to more than just WHNF. Of course, if this is to
be represented by a single closure, the code for it will not exist as
a single piece of the original code. But another representation in
between closures and straightline code would be feasible.

	Torben Mogensen (torbenm@diku.dk)
Thread Navigation

This is a paginated view of messages in the thread with full content displayed inline.

Messages are displayed in chronological order, with the original post highlighted in green.

Use pagination controls to navigate through all messages in large threads.

Back to All Threads