A few things I have learned about building binaries with Chicken Scheme

The binaries

The binaries generated by the compiler have a hidden set of parameters that start with -:. They can be used to enable debugging, perform statistical profiling or control the garbage collector’s behavior:

$ csc gen.scm
$ ./gen -:?
[Runtime options]

 -:?              display this text
 -:c              always treat stdin as console
 -:d              enable debug output
 -:D              enable more debug output
 -:g              show GC information
 -:o              disable stack overflow checks
 -:hiSIZE         set initial heap size
 -:hmSIZE         set maximal heap size
 -:hgPERCENTAGE   set heap growth percentage
 -:hsPERCENTAGE   set heap shrink percentage
 -:hSIZE          set fixed heap size
 -:r              write trace output to stderr
 -:p              collect statistical profile and write to file at exit
 -:PFREQUENCY     like -:p, specifying sampling frequency in us (default: 10000)
 -:sSIZE          set nursery (stack) size
 -:tSIZE          set symbol-table size
 -:fSIZE          set maximal number of pending finalizers
 -:x              deliver uncaught exceptions of other threads to primordial one
 -:b              enter REPL on error
 -:B              sound bell on major GC
 -:G              force GUI mode
 -:aSIZE          set trace-buffer/call-chain size
 -:ASIZE          set fixed temporary stack size
 -:H              dump heap state on exit
 -:S              do not handle segfaults or other serious conditions

  SIZE may have a `k' (`K'), `m' (`M') or `g' (`G') suffix, meaning size
  times 1024, 1048576, and 1073741824, respectively.

Modules

The modules are declared inside a module. As stated in the module documentation, import scheme is necessary inside the module. That’s easily ovelooked.

The parameters are: (module MODULE_NAME (EXPORTED_NAME_1 EXPORTED_NAME_2 ...) BODY):

(module lib (exported-proc exported-const)
    (import scheme)

    (define (exported-proc x) (* x 2))

    (define not-exported-const 128)

    (define exported-const (* 2 not-exported-const))
)

In this example, exported-proc can be used by the code that import this library, but not-exported-const will not.

This module cannot be directly imported, you will need to emit the import file for it:

csc -library -emit-import-library lib lib.scm

It will generate a shared library as well as lib.import.scm which will allow you to import it from another file:

$ cat test.scm 
(import lib)

(display (exported-proc exported-const))
(display "\n")
$ csi -script test.scm
512
$ csc test.scm && ./test 
512

Profiling

Let’s try to profile this simple file:

# test.scm
(define big-list
  (let proc ((size 1000000))
       (if (= size 0) '() (cons size (proc (- size 1))))))

(define (my-sum l)
  (if (null? l)
    0
    (+ (car l) (my-sum (cdr l)))))


(define (my-product l)
  (if (null? l)
    0
    (* (car l) (my-product (cdr l)))))

(display (my-sum big-list))
(display (my-product big-list))

There are 2 different profilers built-in chicken scheme. This page gives some very interesting insights on how they work.

Both methods will generate a file that you can parse with chicken-profile, which is included with chicken scheme.

statistical profiler

Use -:p to sample every 10000us or :-P NUM to sample every NUM us. Every NUM, the profiler will stop the execution and write down what is the procedure currently executed.

$ csc test.scm 
$ ./test -:P100 > /dev/null
$ chicken-profile PROFILE.26626
reading `PROFILE.26626' ...

procedure                calls  seconds  average  percent
---------------------------------------------------------
test.scm:3: proc             1    0.007    0.007   49.315
test.scm:8: my-sum           1    0.005    0.005   36.986
test.scm:14: my-product      1    0.002    0.002   13.698

Instrumentation profiling

Add -profile during compilation to instruct the compiler to add extra code for profiling. Only the top-level procedures will be instrumented, which will limit the amount of details available.

$ csc -profile test.scm 
$ ./test  > /dev/null
$ chicken-profile PROFILE.26036
reading `PROFILE.26036' ...

procedure     calls  seconds  average  percent
----------------------------------------------
my-sum      1000001    1.757    0.000  100.000
my-product  1000001    0.899    0.000   51.166

This result is very likely due to the fact that each recursive call will trigger the instrumentation code, which will then be included into the cost. Without the profiling code, the execution time is much lower.

If your script end quickly and you want to aggregate the data gathered over several runs, -profile-name FILE will write the profiling information into FILE and -accumulate-profile will append to this file after each run. chicken-profile will then merge the data when printing the result.

csc -profile gen.scm -profile-name gen.profile -accumulate-profile