Skip to content

Commit

Permalink
Invert IsZeroOp test and add opMovN.
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanmorgan committed Jul 30, 2024
1 parent b09946a commit e3dde43
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 19 deletions.
10 changes: 8 additions & 2 deletions OPTIMISE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ running benchmarks on different version of the code I discovered areas of improv

## One more instruction

The last common pattern was identified as a fixed multiplication instruction. Where similar to the move instruction, for each decrement of the source cell another was incremented multiple times by a fixed amount `[->>++++<<]`. this too had an alternative where the decrement was at either the start or end `[>>++++<<-]`. This instruction required a second operand so I leveraged a following "noop" instruction to store it.
The last common pattern was identified as a fixed multiplication instruction. Where similar to the move instruction, for each decrement of the source cell another was incremented multiple times by a fixed amount `[->>++++<<]`. this too had an alternative where the decrement was at either the start or end `[>>++++<<-]`. This instruction required a second operand so I leveraged a following "noop" instruction to store it.

## And some more

With multiple operand instructions unlocked `[>+>>+<<<-]` detection could be added. This being a duplicate value instruction. It will take one cells value and adds it to two new cells. Ideally they should be empty but there's no guarantee that this is true. Also added a variation of the move instruction but with a negative accumulation `[<<->>-]`.

## Dead code removal

Probably without a real-world performance improvement is dropping dead code from the output of the instruction parsing stage. Often a loop block is used to store comments in BF knowing that any characters within will never be interpreted during runtime. Identifying these comment blocks isn't always trivial but if a block starts directly after a cell being zeroed out then they could be skipped. To validate all the optimisations I kept a branch called no_optimisation updated to test a performance comparison and the correct operation of the optimised version.
Probably without a real-world performance improvement is dropping dead code from the output of the instruction parsing stage. Often a loop block is used to store comments in BF knowing that any characters within will never be interpreted during runtime. Identifying these comment blocks isn't always trivial but if a block starts directly after a cell being zeroed out then they could be skipped.

To validate all the optimisations I kept a branch called no_optimisation updated to test a performance comparison and the correct operation of the optimised version.
4 changes: 4 additions & 0 deletions parser/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func Execute[T Number](data []T, program []Instruction, reader io.ByteReader, wr
destPtr := (operand + dataPtr) & DataMask
data[destPtr] += data[dataPtr]
data[dataPtr] = 0
case opMovN:
destPtr := (operand + dataPtr) & DataMask
data[destPtr] -= data[dataPtr]
data[dataPtr] = 0
case opMulVal:
destPtr := (operand + dataPtr) & DataMask
factor := program[pc+1].operand
Expand Down
3 changes: 2 additions & 1 deletion parser/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ func TestExecuteSmall(t *testing.T) {
{opSetVal, 32},
{opOut, 1},
{opIn, 1},
{opMovN, 2},
}
startdata := make([]int, 65536)
outputBuf := bufio.NewWriter(&bufferWriter{})
inputBuf := bufio.NewReader(strings.NewReader("no input."))
data := Execute(startdata, program, inputBuf, outputBuf)[:10]
want := []int{0, 0, 0, 110, 10, 10, 0, 0, 0, 0}
want := []int{0, 0, 0, 0, 10, -100, 0, 0, 0, 0}

if !reflect.DeepEqual(data, want) {
t.Errorf("got %v want %v", data, want)
Expand Down
12 changes: 7 additions & 5 deletions parser/instruction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
opJmpZ
opJmpNz
opMove
opMovN
opSkip
opMulVal
opDupVal
Expand Down Expand Up @@ -52,9 +53,10 @@ func (inst Instruction) Complement(instTwo Instruction) bool {

// IsZeroOp returns true for ops that have left the pointer on a zero
func (inst Instruction) IsZeroOp() bool {
return inst.operator == opJmpNz ||
inst.operator == opNoop ||
inst.operator == opMove ||
inst.operator == opSkip ||
(inst.operator == opSetVal && inst.operand == 0)
return !(inst.operator == opAddDp ||
inst.operator == opAddVal ||
inst.operator == opJmpZ ||
inst.operator == opOut ||
inst.operator == opIn ||
(inst.operator == opSetVal && inst.operand != 0))
}
18 changes: 14 additions & 4 deletions parser/instruction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var opName = map[Opcode]string{
opJmpZ: "jmp",
opJmpNz: "jnz",
opMove: "mov",
opMovN: "nmv",
opSkip: "skp",
opMulVal: "mul",
opDupVal: "dup",
Expand Down Expand Up @@ -72,24 +73,32 @@ func TestIsZeroOp(t *testing.T) {
{opNoop, 0},
{opAddDp, 1},
{opAddVal, 1},
{opSetVal, -1},
{opSetVal, 0},
{opSetVal, 1},
{opOut, 1},
{opSkip, 1},
{opIn, 1},
{opJmpZ, 0},
{opJmpNz, 0},
{opMove, 1},
{opSkip, 1},
{opMovN, 1},
{opMulVal, 1},
{opDupVal, 1},
}
want := []bool{
true,
false,
false,
false,
true,
false,
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
}

Expand All @@ -110,6 +119,7 @@ func TestSameOp(t *testing.T) {
opJmpZ,
opJmpNz,
opMove,
opMovN,
opSkip,
opMulVal,
opDupVal,
Expand Down
6 changes: 2 additions & 4 deletions parser/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,10 @@ func instPrint(inst, lastInst Instruction) string {
return "]"
case opMove:
return "[-" + repeatDirection("<", ">", inst.operand) + "+" + repeatDirection(">", "<", inst.operand) + "]"
case opMovN:
return "[-" + repeatDirection("<", ">", inst.operand) + "-" + repeatDirection(">", "<", inst.operand) + "]"
case opSkip:
return "[" + repeatDirection("<", ">", inst.operand) + "]"
case opMulVal:
return ""
case opDupVal:
return ""
case opNoop:
if lastInst.operator == opMulVal {
multiplier := repeatDirection("-", "+", inst.operand)
Expand Down
3 changes: 2 additions & 1 deletion parser/print_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ func TestPrint(t *testing.T) {
{opJmpNz, 5},
{opAddDp, 2},
{opOut, 1},
{opMovN, 2},
}
var buf bytes.Buffer
outputBuf := bufio.NewWriter(&buf)
Print(program, outputBuf)
got := buf.String()
want := " \n >>>>>\n [-]\n +++++\n [->>+<<]\n [\n\t ,\n\t [<<<]\n\t ++\n\t [->>++<<]\n\t [->+>+<<]\n ]\n >>\n .\n"
want := " \n >>>>>\n [-]\n +++++\n [->>+<<]\n [\n\t ,\n\t [<<<]\n\t ++\n\t [->>++<<]\n\t [->+>+<<]\n ]\n >>\n .\n [->>-<<]\n"

if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
Expand Down
4 changes: 4 additions & 0 deletions parser/tokenise.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ func Tokenise(input io.ByteReader) (program []Instruction, err error) {
pc = jmpPc
program = program[:pc]
program = append(program, Instruction{opMove, pointers[0]})
} else if ok && factors[0] == -1 {
pc = jmpPc
program = program[:pc]
program = append(program, Instruction{opMovN, pointers[0]})
} else if ok && factors[0] != 0 {
pc = jmpPc
program = program[:pc]
Expand Down
4 changes: 2 additions & 2 deletions parser/tokenise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestTokenise(t *testing.T) {
}{
{
"small_prog",
">>>>>++[-]zero+++++>+++++[->>+<<]move>>[>>+<<-]move",
">>>>>++[-]zero+++++>+++++[->>+<<]move>>[>>-<<-]movn",
[]Instruction{
{opNoop, 0},
{opAddDp, 5},
Expand All @@ -26,7 +26,7 @@ func TestTokenise(t *testing.T) {
{opAddVal, 5},
{opMove, 2},
{opAddDp, 2},
{opMove, 2},
{opMovN, 2},
},
},
{
Expand Down

0 comments on commit e3dde43

Please sign in to comment.