A slightly faster MID$ using VARPTR

TL:DNR – This is stupid. Don’t bother trying it :)


Here is another stupid thing I had to try in Color BASIC. See also my Stupid VARPTR tricks post.

Suppose you want to horizontally scroll a string across the CoCo’s 32-column text screen. You can build a long string (up to 255 characters) and then use a FOR/NEXT loop and MID$ to print 32 characters from within that longer string.

20 CLS:CLEAR 510:DIM A$
30 A$="":FORA=32TO255:A$=A$+CHR$(A):NEXT:A$=A$+STRING$(31,128)
40 TIMER=0
50 FORA=1TO224:PRINT@256,MID$(A$,A,32);:NEXT
60 PRINTTIMER

On a CoCo 3, you get a timer result of around 330-335. On a CoCo 1/2, it is slightly faster showing around 295. (There are less BASIC keyword tokens to look for so Color BASIC is faster than Exended BASIC which is faster than Super BASIC.)

When I first started benchmarking BASIC here years ago, I was using an emulator that only ran CoCo 1/2, and it never occurred to me it would be different on the CoCo 3. #TheMoreYouKnow

As I have discussed in earlier string theory articles (quite a few relating to this topic), MID$, LEFT$ and RIGHT$ all need to create a new string containing data from the original. Suppose you have this:

A$="THIS IS A TEST"

If you trim the string to the first four characters, like this:

A$=LEFT$(A$,4)

…BASIC is basically doing this:

  1. Allocate new string space for a 4 character string.
  2. Copy the first four characters from A$ into the new string.
  3. Make A$ point to the new string, which leaves the original A$ string memory available for cleanup.

This is why you need extra string memory to use these functions. If you clear JUST enough memory to hold a ten character string, you can’t do any MID$, LEFT$ or RIGHT$ with that string.

In order to make that work, you need at least 4 extra bytes reserved for string memory so LEFT$ can make the new string before deallocating the original. And if you add 4 bytes but try to use 5, you get that ?OS ERROR (out of string space) still.

So if you have a 255 character string and you use MID$(A$,0,32) to get 32 characters from that string into a new string (or use it directly with PRINT, which will directly access that string memory without a variable needed), it has to allocate 32 bytes from the reserved string memory and copy those 32 bytes over, then you can use it (PRINT or assign to a variable or whatever).

As my sample program runs, it is allocating 32 byte strings and copying 32 bytes over each time through the loop.

VARPTR, can you help?

After recently learning a weird trick involving changing where a string variable points to for its string data, that made me wonder if you could just manipulate the string address using VARPTR and then PRINT it, and be faster than the overhead of MID$.

I just had to try this…

Here is my test program. It first builds A$ to contain character 32 (space) to character 255 (end of semigraphics characters) and then pads that with 31 black blocks to be the full 255 character string size.

Starting at line 40 is a loop that will PRINT a substring of 32 characters in a loop, starting at the first character and going to the end, making the line scroll to the left.

When done, it prints how many TIMER ticks it took to do this.

Next, starting in line 70, it makes a new B$ that is a clone of A$. (No, it doesn’t need to clone it. I could have just manipulated A$ and saved a few lines.)

It then gets the address of that string descriptor using VARPTR, and saves the size (at the first byte) and the address of the string data (third and fourth byte). This allows restoring the string after the test, since manipulating strings this way could cause problems with strings and garbage collection.

For this specific example I know that the MSB and LSB of the new string are 126 and 1 on my emulated CoCo. That means I could make the string’s starting address be one higher in that memory by incrementing the second value. Had that second value been 250, once I incremented it 5 times to 255, I’d have to increment the first one and reset the second one to 0. (Clear as mud?)

Let’s just say I got lucky enough for this test, but even if this approach DID work, it would not work on all strings based on where they are in memory. It would need extra code for that, which would slow it down even further.

I POKE to change the size of B$ to 32. Then I can go in a loop POKEing the second byte of the start address up by one each time, and simply PRINTing the string. It will print 32 bytes from wherever it thinks the string data begins. It has no idea those values are being manipulated.

When done, I print the time it took and restore the original B$ values back.

10 'VARMID.BAS
20 CLS:CLEAR 510:DIM A$
30 A$="":FORA=32TO255:A$=A$+CHR$(A):NEXT:A$=A$+STRING$(31,128)
40 TIMER=0
50 FORA=1TO224:PRINT@256,MID$(A$,A,32);:NEXT
60 PRINTTIMER
70 DIM B$,V,O2,O3,S1,S2,SS
80 ' CLONE A$. GET VARPTR OF B$.
90 ' V POINTS TO SIZE
100 ' O2-O3 POINTS TO START
110 B$=A$:V=VARPTR(B$):O2=V+2:O3=V+3
120 ' SS - SAVE SIZE
130 ' S1-S2 - SAVE START
140 SS=PEEK(V):S1=PEEK(O2):S2=PEEK(O3)
150 ' CHANGE SIZE TO 32
160 POKE V,32
170 ' CHANGE START ADDR THEN
180 ' PRINT.
190 TIMER=0
200 FORA=0TO223:POKEO3,S2+A:PRINT@256,B$;:NEXT
210 PRINTTIMER
220 ' RESTORE ORIGINAL STRING
230 POKE V,SS:POKE O2,S1:POKE O3,S2

Sadly, it is only a tiny bit faster, and if I had to take care of both address bytes in the VARPTR memory, it would be slower and, without testing this, I assume slower than just using MID$.

It was a fun experiment, but “sometimes the juice is not worth the squeeze.”

Until next time…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.