Hat tip to Erico Monteiro for sending me down another quick benchmarking rabbit hole…
NOTE: This technique will work poorly for ASCII TEXT characters, since the PEEK value is not the same as the PRINT CHR$ value for some characters. It works fine with the graphics blocks (128-255). See the example at the end.
In general, I expect PEEK to be faster than looking up a variable. PEEK only has to process whatever is in the parentheses:
V=PEEK(1024)
Parsing the decimal 1024 can be slow. Using hex is faster (&H400). Using a variable can be even faster (unless there are a ton of variables BASIC has to scan to before finding the target one):
V=PEEK(L)
Erico just showed me technique using an array to store all the characters on the CoCo’s 32 column screen. PRINT@ can be used to put characters on the screen quickly, and when you want to PRINT@ the character somewhere else, you can PRINT@ whatever character used to be there by taking it from the array.
I expected PEEK would be faster than accessing elements of an array so I did a test where I looped through 512 characters using PEEK versus an array:
0' peek-vs-array.bas
10 TIMER=0:FORA=1024 TO 1536
20 Z=PEEK(A)
30 NEXT:PRINT TIMER
40 DIMB(511):TIMER=0:FOR A=0 TO 511
50 Z=B(A)
60 NEXT:PRINT TIMER
At line 10, I loop through all the locations of the 32×16 screen. One by one, Z is set to the value of that location. The value of the loop (1024-1536) matches the POKE/PEEK memory location of the screen.
At line 40, I have an array B() that would be loaded with all the bytes in the screen. The elements of the array (0-511) match the PRINT@ location of the screen.
My results:
123
127
Very close, though the array access is slightly slower. I confirmed PEEK is indeed faster … in this example.
Let’s pretend the loop is a “background” screen and we would PEEK and POKE to restore it versus PRINT@ from the array. (In this example, I am just getting what is there and printing that same thing there again, just for timing.)
0' peek-vs-array2.bas
10 TIMER=0:FOR A=1024 TO 1536
20 Z=PEEK(A):POKEA,Z
30 NEXT:PRINT TIMER
40 DIMB(511):TIMER=0:FOR A=0 TO 511
50 Z=B(A):PRINT@A,CHR$(Z);
60 NEXT:PRINT TIMER
And my results:
210
258
PEEK is faster here, too.
But I have now seen “real code” using this to put a CHR$() player graphic on the screen, and erase it as it moved across the screen (restoring the background as it goes) and the array was faster.
This is another example of why benchmarking a specific item is not always useful. For example, if using PRINT to put things on the screen, you are using the 0-511 location which matches the 0-511 array. If using PEEK, you have one location that is the display screen and another that would be the “saved” background screen. Each time you want to update something, you have to take the location (offset) and add it to the background (to get that one) and the foreground (to get that one). That doubling of math could make it slower versus PRINT@ using 0-511 and CHR$(B(x)) using the same 0-511.
So while PEEK is faster by itself, if you do that and need more math to use it, ARRAYs could win.
0' peek-vs-array3.bas
10 'FOREGROUND 0400-05FF (512)
11 'BACKGROUND 3E00-3FFF (512)
20 TIMER=0:FOR A=0 TO 511
30 Z=PEEK(&H3E00+A):POKE&H400+A,Z
40 NEXT:PRINT TIMER
50 DIM B(511):TIMER=0:FOR A=0 TO 511
60 Z=B(A):PRINT@A,CHR$(Z);
70 NEXT:PRINT TIMER
The first test loops 0-511 then uses “math” to calculate the background memory location, and more “math” to calculate the foreground memory location. Twice the math, twice the slowness.
The second does not match because the array and PRINT@ location.
333
259
Array is faster than two maths.
But, we can cut that math in half but have the FOR loop go through screen memory, then only add to that to get the background. &H3E00 (15872) background minus &H400 (1024) foreground is &H3A00 (14848). I am using hex because it is quicker for BASIC to parse an &H value than a decimal value.
0' peek-vs-array4.bas
10 'FOREGROUND 0400-05FF (512)
11 'BACKGROUND 3E00-3FFF (512)
20 TIMER=0:FOR A=1024 TO 1536
30 Z=PEEK(&H3A00+A):POKEA,Z
40 NEXT:PRINT TIMER
50 DIM B(511):TIMER=0:FOR A=0 TO 511
60 Z=B(A):PRINT@A,CHR$(Z);
70 NEXT:PRINT TIMER
Now that the first version only maths one time, it gets faster:
267
259
…but the array still beats it. The slower array/PRINT without math is faster than the faster PEEK/POKE with math.
How would you optimize this further? Comments if you got ’em…
Bonus Code
Use line 110 for keyboard control, or line 115 for random movement.
10 'ARRAYBAK.BAS
20 DIMB(511)
30 'KALEIDOSCOPE
40 CLS0:FORA=0TO42:X=RND(32)-1:Y=RND(16)-1:C=RND(8):SET(X,Y,C):SET(63-X,Y,C):SET(X,31-Y,C):SET(63-X,31-Y,C):NEXT
50 'SAVE SCREEN DATA TO ARRAY
60 FORA=0TO511:B(A)=PEEK(&H400+A):NEXT
70 'L=PRINT@ LOCATION
80 L=111
90 'MAIN LOOP
100 PRINT@L,"<O>";
110 'ONINSTR(" WASD",INKEY$)GOTO110,120,130,140,150
115 ONRND(4)GOTO120,130,140,150
120 NL=L-&H20:GOTO160
130 NL=L-1:GOTO160
140 NL=L+&H20:GOTO160
150 NL=L+1
160 IFNL<.THEN110
170 IFNL>&H1FC THEN110
180 PRINT@L,CHR$(B(L))CHR$(B(L+1))CHR$(B(L+2));:L=NL:GOTO100
Double Bonus
10 'ARRAYBAK.BAS
20 'L=NUMBER OF BLOCKS (0-7)
30 N=7
40 DIMB(511):DIML(N),C(N)
50 'KALEIDOSCOPE
60 CLS0:FORA=0TO42:X=RND(32)-1:Y=RND(16)-1:C=RND(8):SET(X,Y,C):SET(63-X,Y,C):SET(X,31-Y,C):SET(63-X,31-Y,C):NEXT
70 'SAVE SCREEN DATA TO ARRAY
80 FORA=0TO511:B(A)=PEEK(&H400+A):NEXT
90 'RANDOMIZE SHIP POSITIONS
100 FORA=0TON:L(A)=RND(510)-1:C(A)=143+16*A:NEXT
110 'MAIN LOOP
120 FORA=0TON
130 ONRND(4)GOTO140,150,160,170
140 NL=L(A)-&H20:GOTO180
150 NL=L(A)-1:GOTO180
160 NL=L(A)+&H20:GOTO180
170 NL=L(A)+1
180 IFNL<.THENNL=NL+&H1FC:GOTO210
190 IFNL>&H1FC THENNL=NL-&H1FC
200 L=L(A):PRINT@L,CHR$(B(L));:PRINT@NL,CHR$(C(A));:L(A)=NL
210 NEXT:GOTO120
