powerbf
A superb brainfuck interpreter.
powerbf is an experiment in making the ultimate brainfuck interpreter, featuring all the things you'd ever want to make programming in brainfuck less of a brainfuck. It's a successor to microbf, which was my first ever bytecode VM, and it improves on many things the original failed to do well.
Building
To build powerbf, you'll need git and meson. If you have them installed, execute the following commands in your shell of choice.
git clone https://github.com/liquid600pgm/powerbf
cd powerbf
meson setup build
ninja -C build
It should compile cleanly as C99.
Running
Run
pbf -h
to get some help.
Benchmarks
Benchmarks were conducted using the same method as in microbf (cycle.h), with computed gotos enabled. Here are the results:
ticks | improvement | |
---|---|---|
microbf | 20414860928 | — |
powerbf | 5942684576 | 3.44x |
To perform the benchmark yourself, uncomment #define BENCHMARK
in
src/cli/pbf_main.c
.
But how is it so much better?
First of all, powerbf does more optimization than microbf. While it does not do it in the same way (optimizing during compilation in microbf vs after compilation in powerbf), it can still yield quite an improvement in all sorts if brainfuck programs, especially badly-written ones.
powerbf does two kinds of optimization:
- "streak", which folds series of bytecode like
INC 1; INC 1; INC 1
into a single opcode (in this caseINC 3
) - "balance", which remove any codes that cancel each other out
(eg.
INC 5; DEC 3
becomesINC 2
) It runs these optimizations exactly in the order presented.
Apart from optimizing the bytecode, powerbf also uses a much more efficient
memory representation: instead of using a linked list of memory cells, powerbf
uses a dynamically expanding array of int8_t
. These two changes are enough
to seriously impact the performance of the VM, as shown by the above benchmark.
The memory change also allows powerbf to run programs that microbf simply wasn't
capable of executing (because it ran out of memory before the program could
finish). An example of such program is mandelbrot.b
.
Another thing powerbf does better is the bytecode representation. While microbf
used variable length uint8_t
-based bytecode, powerbf uses fixed-length
uint32_t
-based codes. This makes the bytecode larger, but also allows for an
important loop optimization: microbf used an offset table in the chunk, which
could fit 256 offsets at most. powerbf on the other hand, can fit as many as
you'd like, as long as your program's bytecode doesn't exceed 268435456
instructions (which is unlikely). The opcode occupies the 4 most significant
bits of the integer, and the rest is the operand. Because the operand is so big,
the jump destination can be stored right within the remaining 28 bits of the
instruction, thus making the jump more immediate, because a table lookup isn't
needed.