Profiling a Lisp application
Profiling is one of the most important steps in writing software. Once a package is written, profiling greatly helps to identify bottlenecks and inefficiencies by showing how an application spends its time. How to profile a lisp application is a common question among those new to lisp. Luckily, profiling lisp is simple with emacs and slime.
There are several slime profiling functions, but the simplest is slime-profile-package
(M-x slime-profile-package
), which profiles all functions in a package. You will be asked for the package name to profile. All functions in the package will be profiled. slime-profile-functions
gives you more control over which functions to profile. The profiling of a single function can be controlled with slime-toggle-profile-fdefinition
.
After entering the package name, you are asked whether to record the most common callers and whether to profile methods as well as typical functions. To view the results, you use slime-profile-report
, which displays a table of the most commonly called functions, time spent in each, amount of consing in each, calls, total time, etc.
Before the next run, be sure to use M-x slime-profile-reset
to reset the profiling statistics so you get a fresh and accurate report.
Optimizing functions
Generally, you should attempt to write a more efficient algorithm before depending on compiler optimization declarations. The slime profiler will tell you which functions are taking the majority of your application’s time, and making these more efficient will serve you better than tuning the manner in which a function is compiled. However, once your function is written to your satisfaction, you may wish to further tune it by sacrificing speed for lower memory usage (or vice versa) or trading debug information for extra speed.
Lisp compilers accept a few optimization declarations that can earn you significant speed-ups. The compiler generally optimizes code based on four variables: speed, safety, debug, and compilation-speed. These can be set between 0 and 3 with a declaration:
(defun foo () (declare (optimize (speed 2) (safety 3) (debug 3) (compilation-speed 0))) (format t "~&Hello world~%"))
Generally, lowering the debug value of a function makes trouble-shooting your application extremely difficult. For example, the name of the function may disappear from backtraces. Therefore, lowering this value is usually only worthwhile once the program has been suitably tested and deployed. Use the profiler to determine which portions of your code will provide the most benefit.
Macros and optimization
Macros are sometimes touted as a way to optimize code. In a compiled lisp program, they can cause some calculations and expansions to be performed at compile time, saving run-time. In situations where much of the information is known ahead of time, this can be an effective technique, although overuse can be dangerous. For example, rewriting a function as a macro will make debugging more difficult, since the macro is expanded in the compiled code. A problem in a macro may only be shown as a problem in the function that utilizes the macro.
Note that in interpreted lisps, especially those without any form of compilation (such as newLISP), macros provide no speed-up. In fact, in newLISP, macros are just functions that do not evaluate their arguments.