[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


The Language Compiled

Calls to load or require occurring at the top level of a file being compiled are ignored. Calls to load or require within a procedure are compiled to call (interpreted) load or require as appropriate.

Several SCM and SLIB extensions to the Scheme report are recognized by hobbit as Scheme primitives.

Macros

The Common-lisp style defmacro implemented in SCM is recognized and procedures defined by defmacro are expanded during compilation.

Note Bene: any macro used in a compiled file must be also defined in one of the compiled files.

`#.<expression>' is read as the object resulting from the evaluation of <expression>. The calculation is performed during compile time. Thus <expression> must not contain variables defined or set! in the compiled file.

SCM Primitive Procedures

Real-only versions of transcedental procedures (warning: these procedures are not compiled directly into the corresponding C library procedures, but a combination of internal SCM procedures, guaranteeing exact correspondence with the SCM interpreter while hindering the speed):

real-sqrt real-exp real-ln real-expt real-sin real-cos real-tan
real-asin real-acos real-atan real-sinh real-cosh real-tanh real-asinh
real-acosh real-atanh

Note Bene: These procedures are compiled to faster code than the corresponding generic versions sqrt, abs, ... expt.

A selection of other extra primitives in SCM is also recognized as primitives. eg. get-internal-run-time, quit, abort, restart, chdir, delete-file, rename-file.

SLIB Logical Procedures

The following bitwise procedures in the scheme library file `logical.scm' are compiled directly to fast C operations on immediate integers (small 30-bit integers) (Scheme library funs in the upper row, C ops below):

   logand logior logxor lognot logsleft logsright
      &      |     ^      ~       <<        >>

The following alternative names logical:logand, logical:logior, logical:logxor, logical:lognot, and ash are compiled for the generic case, not immediate-integers-only and are thus much slower.

Notice that the procedures logsleft, logsright are NOT in the the library file `logical.scm:' the universal procedure ash is instead. Procedures ash, logcount, integer-length, integer-expt, bit-extract, ipow-by-squaring, in `logical.scm' are not primtives and they are all compiled into calls to interpreted code.

logsleft and logsright are defined for non-compiled use in the file `scmhob.scm' included in the SCM distribution.

Fast Integer Calculations

The following primitives are for immediate (30-bit) integer-only arithmetics. The are compiled directly into the corresponding C operations plus some bitshifts if necessary. They are good for speed in case the compiled program uses BOTH generic arithmetics (reals, bignums) and immediate (30-bit) integer arithmetics. These procedures are much faster than corresponding generic procedures taking also reals and bignums. There is no point in using these unless the program as a whole is compiled using generic arithmetics, since otherwise all the arithmetics procedures are compiled directly into corresponding C operations anyway.

Note Bene: These primitives are NOT defined in SCM or its libraries. For non-compiled use they are defined in the file `scmhob.scm' included in the SCM distribution.

%negative?  %number?    %>      %>=     %=      %<=     %<
%positive?  %zero?      %eqv?   %+      %-      %*      %/

Force and Delay

The nonessential procedure force and syntax delay are implemented exactly as suggested in the report 4. This implementation deviates internally from the implementation of force and delay in the SCM interpeter, thus it is incorrect to pass a promise created by delay in the compiled code to the force used by interpreter, and vice-versa for the promises created by the interpreter.

Suggestions for writing fast code

The following suggestions may help you to write well-optimizable and fast code for the hobbit-scm combination. Roughly speaking, the main points are:

Here come the details.

  1. Immediate arithmetics (ie using small, up to 30 bits integers) is much faster than generic (reals and bignums) arithmetics. If you have to use generic arithmetic in your program, then try to use special immediate arithmetics operations %=, %<=, %+, %*, ... for speed-critical parts of the program whenever possible. Also, if you use bitwise logical operations, try to use the immediate-integer-only versions
    logand logior logxor lognot logsleft logsright
    
    and not logical:logand or ash, for example.
  2. Due to its inner stack-based architecture, the generic (not escape-only) continuations are very slow in SCM. Thus they are also slow in compiled code. Try to avoid continuations (calls to the procedure call-with-current-continuation and calls to the continuations it produces) in speed-critical parts.
  3. In speed-critical parts of your program try to avoid using procedures which are redefined or defined by set!:
    (set! bar +)
    (set! f (lambda (x) (if (zero? x) 1 (* x (f (- x 1))))))
    
    anywhere in the compiled program. Avoid using compiler flags (see section Hobbit Options):
    (define compile-all-proc-redefined t)
    (define compile-new-proc-redefined t)
    
  4. Do not use complicated higher-order procedures in speed-critical parts. By complicated we mean not clonable, where clonability is defined in the following way (Note Bene: the primitives `map' and `for-each' are considered clonable and do not inflict a speed penalty). A higher-order procedure (HOP for short) is defined as a procedure with some of its formal arguments occuring in the procedure body in a function position, that is, as a first element of a list. Such an argument is called a higher-order argument. A HOP `bar' is clonable iff it satisfies the following four conditions:
    1. `bar' is defined as
      (define bar (lambda ...))
      
      or
      (define (bar ...) ...)
      
      on top level and bar is not redefined anywhere.
    2. the name `bar' occurs inside the body of bar only in a function position and not inside an internal lambda-term.
    3. Let f be a higher-order argument of bar. Any occurrence of f in bar has one of the following two forms:
      • f occurs in a function position,
      • f is passed as an argument to bar and in the call it occurs in the same position as in the argument list.
    4. Let f be a higher-order argument of bar. f does not occur inside a lambda-term occurring in bar. Examples: If `member-if' is defined on top level and is not redefined anywhere, then `member-if' is a clonable HOP:
      (define (member-if fn lst)
         (if (fn (car lst))
             lst
             (member-if fn (cdr lst)) ))
      
      member-if-not is not a clonable HOP (fn occurs in a lambdaterm):
      (define (member-if-not fn lst)
        (member (lambda (x) (not (fn x))) lst) )
      
      show-f is not a clonable HOP (fn occurs in a non-function position in (display fn)):
      (define (show-f fn x)
          (set! x (fn x))
          (display fn)
          x)
      
    5. In speed-critical parts avoid using procedures which return procedures. Eg, a procedure
      (define plus
        (lambda (x)
           (lambda (y) (+ y x)) ))
      
      returns a procedure.
    6. A generalisation of the previous case 5: In speed-critical parts avoid using lambda-terms except in non-set! function definitions like
      (define foo (lambda ...)),
      (let ((x 1) (f (lambda ...))) ...)
      (let* ((x 1) (f (lambda ...))) ...)
      (let name ((x 1) (f (lambda ...))) ...)
      (letrec ((f (lambda ...)) (g (lambda ...))) ...)
      
      or as arguments to clonable HOP-s or primitives map and for-each, like
      (let ((x 0)) (map (lambda (y) (set! x (+ 1 x)) (cons x y)) list))
      (member-if (lambda (x) (< x 0)) list)
      
      where member-if is a clonable HOP. Also, avoid using variables with a procedural value anywhere except in a function position (first element of a list) or as an argument to a clonable HOP, map or for-each. Lambda-terms conforming to the current point are said to be liftable. Examples:
      (define (bar x) (let ((f car)) (f (f x))))
      
      has `car' in a non-function and non-HOP-argument position in (f car), thus it is slower than
      (define (bar x) (let ((f 1)) (car (car x))))
      
      Similarly,
      (define (bar y z w)
        (let ((f (lambda (x) (+ x y))))
           (set! w f)
           (cons (f (car z))
                 (map f z) )))
      
      has `f' occurring in a non-function position in (set! w f), thus the lambda-term (lambda (x) (+ x y)) is not liftable and the upper `bar' is thus slower than the following equivalent `bar' with a liftable inner lambda-term:
      (define (bar y z w)
        (let ((f (lambda (x) (+ x y))))
           (set! w 0)
           (cons (f (car z))
                 (map f z) )))
      
      Using a procedure bar defined as
      (define bar (let ((x 1)) (lambda (y) (set! x y) (+ x y))))
      
      is slower than using a procedure bar defined as
      (define *bar-x* 1)
      (define bar (lambda (y) (set! *bar-x* y) (+ *bar-x* y)))
      
      since the former definition contains a non-liftable lambda-term.
    7. Try to minimize the amount of consing in the speed-critical program fragments, that is, a number of applications of cons, list, map, quasiquote (`) and vector->list during the time program is running. `cons' (called also by `list', `map' and `quasiquote') is translated into a C call to an internal cons procedure of the SCM interpreter. Excessive consing also means that the garbage collection happens more often. Do (verbose 3) to see the amount of time used by garbage collection while your program is running. Try to minimize the amount of creating new vectors, strings and symbols in the speed-critical program frgaments, that is, a number of applications of make-vector, vector, list->vector, make-string, string-append, *->string, string->symbol. Creating such objects takes typically much more time than consing.
    8. The Scheme iteration construction `do' is compiled directly into the C iteration construction `for'. We can expect that the C compiler has some knowledge about `for' in the optimization stage, thus it is probably faster to use `do' for iteration than non-mutual tailrecursion (which is recognized by hobbit as such and is compiled into a jump to a beginning of a procedure) and certainly much faster than non-tail-recursion or mutual tailrecursion (the latter is not recognized by hobbit as such).
    9. Declare small nonrecursive programs which do not contain let-s or lambdaterms as being inlinable. Declare globally defined variables which are never set! or redefined and whose value is a small integer, character or a boolean, as being inlinable. See section Hobbit Options.
    10. If possible, declare vectors as being stable. See section Hobbit Options. This gives a minor improvement in speed.
    11. If possible, declare critical global vars as being uninterned. See section Hobbit Options. This gives a minor improvement in speed. Declare the global variables which are never set! and have an (unchanged) numeric or boolean value as being inlined. See section Hobbit Options.
    In addition, take the following into account:


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]