At SourceProbe, we are building fast and powerful code search tools. Our query engine uses a sort of mini-language to express the query. We are planning to add another internal language to our system, to provide even more precise results for your searches. Both of these rely on fast and efficient interpreters.
Interpreters are very common in software. In fact, large systems tend to develop interpreter like functionality, whether intentional or not (Greenspun’s rule). Outside of programming languages, they are also typically found in system emulators and any system which wants to add programmability.
So lets see what it takes to build an interpreter, and make it fast. In this first post, we’ll focus primarily on the languages we’ll use in future posts. Next we’ll build a few simple interpreters, and then explore techniques and tricks to make them fly.
For this investigation, we have selected a few programming languages to explore.
If you would like more information on the tools used in this series, you can expand this section.
Show / Hide$ g++ --version
g++ (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ python3 --version
Python 3.11.2
$ sbcl --version
SBCL 2.4.0.40-016fa7e01
$ csc -version
CHICKEN
(c) 2008-2021, The CHICKEN Team
(c) 2000-2007, Felix L. Winkelmann
Version 5.3.0 (rev e31bbee5)
linux-unix-gnu-x86-64 [ 64bit dload ptables ]
To start off our investigation, we will implement a Mandelbrot renderer in each of these languages. We have selected this as it uses a good mix of language features, particularly branching and looping. Yet it is also simple enough that a few implementations can be completed in an afternoon.
The Mandelbrot renderer performs some calculation for each x/y cell in the image. At each point, the formula is iterated. The value at each point is based on the number of steps before the formula diverges.
The renderer was initially written in Python, then translated by hand to each of the other languages. Care was taken to ensure that the implementation should be performing similar work, and no inefficiencies are introduced. The Scheme and CL implementations are both tail-recursive, and both platforms support the Tail-Call Optimization.
The table below captures the performance of each implementation. A few of these rows involve different settings which are worth mentioning:
Language & Link to code | Run time (s) | Multiple vs C (lower is better) |
---|---|---|
C, default | 0.002 | 1.0 |
C (script) | 0.348 | 173 |
C (O3) | 0.001 | 0.5 |
Python3 | 0.036 | 18 |
Scheme, Chicken, script | 0.191 | 93.5 |
Scheme, Chicken, compiled | 0.047 | 23.5 |
Common Lisp, SBCL, script | 0.037 | 18.5 |
Common Lisp, SBCL, compiled | 0.034 | 17.0 |
The total running time, even for the slowest setups, is quite low. Unfortunately, this means running time can be dominated by higher startup times or other one-time costs.
A longer running program would more accurately characterize the performance of these compilers. Next up we will be building a simple programming language interpretter, and run a familiar program in our resulting interpreters.
Join our newsletter to get notified of new articles and product updates. Unsubscribe at any time.