Optimizing Gerbil Programs
Once you have mastered Gerbil, you will be able to write complex systems programs with a performance envelope similar to equivalent complexity C programs. Alas, mastery comes through practice, so it might take you some time to fully understand the performance properties of your code.
Here are some tips that can significantly improve performance.
Profile your program first
This should be self-evident, but you shouldn't be trying to optimize without profiling first. Once you have identified the hotspots at module top level procedure granularity, you can focus your efforts on optimizing the critical performance section.
Always compile with -O
The optimizer does a lot of transforms that make you code go
significantly faster. Forgetting -O
is like forgetting to drink
water.
Use specialized arithmetic operations
If you are working with fixnums, use the fx
prefixed operators. If
you are working with flonums, use the fl
prefixed operators. You
can also scope this with (declare (fixnum))
or (declare (flonum))
where appropriate.
(not safe)
Surgically use Adding a (declare (not safe))
in performance critical modules or
functions can help eliminate all overhead from dynamic type checking.
Use judiciously however, as it really is unsafe; if you have a bug
it will be memory corruption and most likely a segfault.
Limit use of apply
Use case-lambdas and opt-lambdas where applicable; apply
allocates a
list for the arguments, which takes time, creates garbage, and
stresses the garbage collector.
Make direct calls
The compiler arity checks and optimizes direct calls to known procedures.
Avoid internal optional, keyword, and case lambdas
They are great at the module interface barrier as they offer great flexibility and the compiler optimizes direct dispatch. They are a performance liability however as first class procedures as they have to pack arguments and use apply for dispatch.
Use unchecked accessors and mutators internally
Use the unchecked: #t
option for generating unchcked accessors and
mutators for your structs and classes, which evade type checks (and
obviously are unsafe for export). Once you have checked the type of an
object at the interface barrier, you can avoid extraneous checks.
Use interfaces
It goes without saying, but interface dispatch is a lot faster than dynamic method dispatch. Interfaces provide the ideal vehicle for building facades to complex functionality, where implementation details are hidden and certain assumptions can be made.
Benchmark Mode
If you want to see how fast your program can go at its current form,
then you can compile with -full-program-optimization
and -prelude '(declare (not safe))'
. This will remove all implicit runtime
checking and show you your performance envelope.
Note
We do not advocate shipping programs compiled like this in production servers, unless you want your devops to be debugging segfaults. These programs are also nearly impossible to debug because everything is lumped in a single host function and you might not even get a stack trace with gdb.