For a long time I’ve been writing Mandelbrot programs for fun and recently to act as a benchmark for some retro BASIC systems. The output has been variable from simple ASCII text to high resolution colour graphics.
For retro systems… Well, those high resolution graphics are somewhat problematic for a few reasons – one is that the old systems simply don’t have high resolution or multi colour graphics and another is that generating a Mandelbrot is a very compute intensive task. Would you want to wait 2 hours for an image?
So plain-text ASCII it is.
An issue with benchmarking is that unless you want to cheat (who, me?) then you need to be able to run the same code on every system you have and where you change the code, you need to make it absolutely clear where you change it and why…
So what does it actually benchmark?
Good question.. Traditionally, Mandelbrot does a lot of arithmetic operations – typically floating point in BASICs that support it, but even in integer only BASICs it’s still a lot of calculations. So many that the surrounding loops, etc. make little difference to the overall time – one run I did where I replaced the FOR loops with arithmetic, tests and GOTO was only 2 seconds quicker with FOR loop vs. without on an 84 second run, so if you need to change the FOR loops into tests and GOTOs because the TinyBasic you’re using doesn’t support it, then go ahead. Also this isn’t testing the efficiency of GOSUB nor GOTO which some benchmarks are better suited to.
With this in-mind, developing a “universal” benchmark program in BASIC is quite hard. Some of these old BASICs have strings, some don’t, some have FOR/NEXT loops, some (typically “Tiny” Basics) don’t. Some support 2 or more letter variable names, some don’t, and so on. So developing code that works everywhere with minimal changes is the goal here.
This was my criteria:
- Spaces in the source should not be important. Some BASICs tokenise the input and remove all spaces, so run-time is faster. Some Tiny Basics leave the spaces in and therefore take time to skip over them at run-time, so lets remove spaces where we can.
- Multiple statements per line. Try to combine lines where possible, but don’t go overboard.
- Integer range? Lets use an algorithm that will work in signed 16-bit integers used in some of the very early Tiny Basics as well as the floating point used in larger BASICs.
- Single letter variables.
- Easy to type in. So not pages and pages, but should we need to type it in, then you’re not going to resent it. Much.
Here is the code:
100PRINT "Mandelbrot - BBC Basic - FP + Strings" 110PRINT "Start" 120TIME=0:REM Initialise TIME 130Z$=".,'~=+:;*%&$OXB#@ " 140F=50 150FOR Y = -12 TO 12 160FOR X = -49 TO 29 170C=X*229/100 180D=Y*416/100 190A=C:B=D:I=0 200Q=B/F:S=B-(Q*F) 210T=((A*A)-(B*B))/F+C 220B=2*((A*Q)+(A*S/F))+D 230A=T: P=A/F:Q=B/F 240IF ((P*P)+(Q*Q))>=5 GOTO 280 250I=I+1:IF I<16 GOTO 200 260PRINT" "; 270GOTO 290 280PRINT MID$(Z$,I+1,1); 290NEXT X 300PRINT "" 310NEXT Y 320Q=TIME 330PRINT"Finished" 340PRINT"Time: "; Q/100; " secs."
Things to note about the code:
- Timing: BBC Basic supports a built-in TIME variable. It increments once every 100th of a second.
- The MID$() function starts with an index of 1.
- My own 6502 SBC has an operating system that lets BBC Basic run, but for other BASICs on the same system, then the 100Hz timer is in locations 160,161,162 and 163, so TIME=0 becomes POKE160,0:POKE161,0 and in my TinyBasic it’s !160=0
The differences are going to be with integer truncation but the important part for the benchmark is that the code is the same in both cases.
And last thing to note when running these on your own system: My system is a 16Mhz 65C02 which can run a variety of BASICs. If you’re running this on an older or classic 1 or 2Mhz system, then expect the timings to be much longer…