With a little bit of interest in the Bubble Universe demo recently I thought I’d go back and look at mine. It’s slow … however given the limitations I don’t think it’s that bad.
Here is the output of the first frame:
The number at the top, 1396 is the milliseconds to generate, so just under 1.4 seconds per frame. It increases to about 1450mS after about 10 iterations which is explainable when angles exceed the +/- Pi value.
Initially I was just over 2 second per frame, so even without hardware FP, I did feel there was some work I could do. The first thing was optimising the reduction in angles that are outside the +/- Pi range as my sin and cos functions are only valid in that range. After I improved these, still looking for more tweaks I looked at the number of terms in my sin/cos code (Taylor/Maclaurin series) and reduced it by one step - which kept the same precision but was again, only fractionally faster. (Saved 2 FP Multiplies per evaluation) The final step was replacing my FP multiply by the one written by Luke Wren, creator of the Hazard3 RISC-V cores in the RP2350 and that gave me another small increase (at the expense of the code being Hazard3 only).
But these were small steps. I did expect better, so it’s still quite possible I’m missing something here.
The algorithm I’m using is essentially the same published by Paul Dunn on Facebook some time back - it has “n = 200”, so 200 steps of outer loop and 200 inner loop. That’s 40,000 pixels per frame and each pixel requires 2 x sine() and 2 x cosine () calls, so 160,000 trig calls. (The code for sin & cos is virtually identical FWIW) It still seems slow.
(I did notice some demos have n=150 - which is obviously much faster at the expense of pixel “density” and colour - running it with n=255 is very nice and colourful, but also even slower)
I don’t think the overhead of the actual screen plot is the issue - running it without the call to vduPlot does improve speed, but not be that much.
It could be that it’s the nature of BCPL - The compiler I’m using compiles to a bytecode and this bytecode is interpreted by a hand-coded interpreter written (by me) in RISC-V assembler. I really don’t think I’m missing any tricks here though. I’m fairly sure the bulk of the time is inside the math functions (Also all hand coded in RISC-V assembler)
As an experiment, I bludgeoned a C version of the same code into the PicoCalc (internally my little bios/framework that supports the BCPL OS while not using the SDK is mostly ABI compliant) and it is running 300mS per frame faster. So it seems the bulk of the time really is being spent inside those maths functions.
Now… I have the best part of 8MB of PSRAM just sitting there, so of-course I could “cheat” and fill it with a big lookup table, but really I can’t be bothered right now. I’m actually quite happy I did manage to save a tiny bit of time, but slightly disappointed it wasn’t as much as I’d hoped.
The laws of premature optimisation and all that!
Here is my BCPL code for interest:
GET "libhdr.h"
GET "sys.h"
GET "math.h"
GET "vdu.h"
MANIFEST
{
n = 200
ti = 1.0 #/ FLOAT n
tau = M_PI #* 2.0
r = tau #/ 235.0
sz = 75.0 //100.0
sw = 320 / 2 // Width
sh = 320 / 2 // Height
}
LET start () = VALOF
{
LET x, u, v = ?,?,?
LET t = 0.0
LET tStart = ?
vduOrigin (sw, sh)
u := 0.0
v := 0.0
x := 0.0
{
vduCls ()
tStart := sys (Sys_cputime)
FOR i = 0 TO n DO
{
LET fi = FLOAT i
FOR j = 0 TO n DO
{
LET rix = r #* fi #+ x
LET iv = fi #+ v
u := sys (Sys_flt, fl_sin, iv) #+ sys (Sys_flt, fl_sin, rix)
v := sys (Sys_flt, fl_cos, iv) #+ sys (Sys_flt, fl_cos, rix)
x := u #+ t
vduGColRGB (i,j,99)
vduPoint (FIX (u #* sz), FIX (v #* sz))
}
}
t := t #+ ti //0.025
vduXY (0,0)
sawritef (" %4d*n", sys (Sys_cputime) - tStart)
} REPEATUNTIL sapollrdch () > 0
vduOrigin (0,0)
RESULTIS 0
}
-Gordon
