In part 1, I looked at a reference implementation for Brainf*** and a first optimization.

The execution time was reduced from 2 minutes, 16 seconds to 1 minute, 6 seconds.

Now we'll look at another optimization, one that actually didn't work very well.

Optimization 2: Combine instructions

In the code there are a lot of things like this +++++++++++++. This increments the value at the pointer by 1, thirteen times.

So the following code is executed 13 times:


Not much, but it would make sense to call the following statement instead:

memory[pointer] += 13;

However, to know the value that must be added, we must know how many "+" instructions there are.

So whenever I encountered a "+" I would go through the code further and count how many consequitive "+" instructions follow:

memory[pointer] += GetSumFromSeries(cr, '+');


In trying to be clever, I also made sure that for one position in the source code, I would only count this once and store it in a cache.

So that if the interpreter reaches the same code twice, it doesn't have to count the + instructions again.


The code for the GetSumFromSeries ended up looking like this:

    public byte GetSumFromSeries(CharReader cr, Char lookupChar)
        var startPosition = cr.Position;
        var cachedCount = _seriesCache[startPosition];

        if (cachedCount != 0)
            cr.Position += cachedCount - 1;
            return cachedCount;

        byte count = 1;

        while (cr.GetChar() == lookupChar) { count += 1; cr.Forward(); }

        _seriesCache[startPosition] = count;

        return count;

What happened?

This added a few seconds to the run-time.

The counting of the "+" instructions was more expensive than the simple increments by 1.

It requires keeping a cache and navigating through the code, which added up to costing us more time.


Not every optimization that seems reasonable actually results in a win.

We'll see later how this strategy of combining instructions to one more complex instruction can help though.