[Return to top]

Enhanced BASIC, advanced examples by Lee Davison
[Back]


Creating buffer space.

Sometimes there is a need for a byte oriented buffer space. This can be achieved by lowering the top of BASIC memory and using the "protected" space created thus. The main problem with this is that there may not be the same RAM configuration in all the systems this code is to run on.

One way round this is to allocate the space from BASIC's array memory by dimensioning an array big enough to hold your data. As arrays always start from zero then to work out the array size needed you do ..

Array dimension = (bytes needed/4)-1.

E.g.


10 DIM b1(19) : REM need 80 bytes for input buffer
20 DIM b2($FF) : REM need $0400 bytes for a screen buffer
So you've allocated the buffer but where is it? This is one use of the VARPTR function, it is used in this case to return the start of the array's data space.

E.g.


100 a1 = VARPTR(b1(0)) : REM get the address of the buffer space
But wait, there is another problem here. Because variables are created when they are first assigned a value any new variable created after the array is dimensioned will move the array in memory. So the following will not work..

10 DIM b1(19) : REM 80 bytes for buffer
20 a1 = VARPTR(b1(0)) : REM get the address of the buffer space
40 FOR x = 0 to 79
50 POKE a1+x,ASC(" ")
60 NEXT
.
.
When we get to line 40, a1, the pointer to the array data space, is wrong because the variable x has been created and moved all the arrays up by six bytes. The way round this is to ensure that all variables that you will use have been created prior to getting the pointer. This also means you start with known values in all your variables.

10 DIM b1(19) : REM 80 bytes for buffer
20 x = 0 : REM loop counter
30 a1 = VARPTR(b1(0)) : REM get the address of the buffer space
40 FOR x = 0 to 79
50 POKE a1+x,ASC(" ")
60 NEXT
.
.
Another way is to get the pointer every time you use it. This has the advantage of always being correct but is somewhat slower.

10 DIM b1(19) : REM 80 bytes for buffer
40 FOR x = 0 to 79
50 POKE VARPTR(b1(0))+x,ASC(" ")
60 NEXT
.
.
One thing to remember, never try to use a string array as a buffer. Everything will seem to work until you run out of string space and the garbage collection routine is called. Once this happens it's likely that your buffer will get trashed and you may even find that the program freezes because the garbage collection routine now thinks that there are more string bytes than there are memory.

Creating short code space.
While the techniques explained above can also be used to create space for machine code routines there is a simpler way for position independent routines up to 65535 bytes long to be held in memory.

Assemble the code and use the hex output from your assembler to create a set of BASIC data statements.
E.g.


1000 DATA $B1,$F9,$00,$04,$04,$4A,$65,$0C
1010 DATA $61,$00,$15,$36,$B1,$F9,$00,$04
1020 DATA $04,$4A,$63,$3E,$4E,$75
1030 DATA -1
Now we just use a loop like this to load this hex code into a string.

10 RESTORE 1000
20 READ by : REM assume at least one byte
30 DO
40 co$ = co$+CHR$(by)
50 READ by
60 LOOP UNTIL by=-1
The code can now be called by doing ..

140 CALL(SADD(co$))
Note that you must always use the SADD() function to get the address for the CALL as the garbage collection routine may move the string in memory and this is the best way to ensure that the address is always correct.

Coding for speed
Spaces
Remove spaces from your code. Spaces, while they don't affect the program flow, do take a finite time to skip over. The only space you don't need to worry about is the one between the line number and the code as this is stripped during input parsing and the apparent space is generated by the LIST command output.

E.g. the following ..


10 REM line 10
20   REM line 20
30     REM line 30
.. reads as follows when LISTed

10 REM line 10
20 REM line 20
30 REM line 30
Removing REM.
Remove remarks from your code. Remarks like spaces don't do anything, program wise, but take time to skip. Removing remarks, especially from time critical code, can make a big difference.
Variables.
Use variables. One place where time is wasted, especially in loops, is repeatedly interpreting numeric values or unchanging functions.
E.g.

.
140 FOR x = 0 to 79
150 POKE $F400+x,ASC(" ")
160 NEXT
.
This loop can be improved in a number of ways. First assign a variable the value $F400 and use that. Doing this is faster after only three uses.
E.g.

10 a1 = $F400
.
140 FOR x = 0 to 79
150 POKE a1+x,ASC(" ")
160 NEXT
.
The other way to make this loop faster is to assign the value of the (unchanging) function to a variable, then move the function outside the loop.
E.g.

10 a1 = $F400
.
130 sp = ASC(" ")
140 FOR x = 0 to 79
150 POKE a1+x,sp
160 NEXT
.
Now the ASC(" ") is only evaluated once and the loop is executed faster.
GOTO and GOSUB
When EhBASIC encounters a GOTO or GOSUB it has to search through memory for the target line. If the target line follows the command then it searches from the next line, if the target line precedes the command then the search starts from the beginning of program memory. So keeping this distance, in lines, as short as possible will make the program run faster.

One place that this is difficult is in a conditional loop. In calculating points in the Mandelbrot set, for example, code like this is used ..


.
230 INC it
235 tp = mx*mx-my*my+x
240 my = 2*mx*my+y
245 mx = tp
250 co = (mx*mx + my*my)
255 IF (it<128) AND (co<4.0) THEN 230
. 
Each time the condition in line 255 is met the interpreter has to search from the start of memory for line 230. While this may not take long if the program is short it can slow longer programs considerably.

This can easily be resolved though by using a DO .. LOOP instead. So our example code becomes..


.
220 DO
230 INC it
235 tp = mx*mx-my*my+x
240 my = 2*mx*my+y
245 mx = tp
250 co = (mx*mx + my*my)
255 LOOP WHILE (it<128) AND (co<4.0)
.
This is quicker because the location of the start of the loop, the DO, is placed on the stack and the interpreter doesn't have to search for it.
Packing them in.
Another way to speed up time critical code is to place as many commands as possible on each line, this can make a noticeable speed gain.
E.g.

.
220 DO:INCit:tp=mx*mx-my*my+x
230 my=2*mx*my+y:mx=tp:co=(mx*mx + my*my)
240 LOOP WHILE (it<128) AND (co<4.0)
.
INC and DEC.
INCrement and DECrement are quick and clear ways of altering a numeric value by plus or minus one and are faster than using add or subtract.
E.g.

100 INC a
.. is quicker than ..

100 a = a+1
.. and ..

100 INC a,a
.. is still quicker than ..

100 a = a+2
Also combine increments or decrements if you can.
E.g.

100 INC so,d
.. is quicker than ..

100 INC so : INC de
>> and <<
Using >> and << can be quicker than using / or * where integer math and a power of two is involved.
E.g. you want to find the byte that holds the pixel at x,y in a 256 x 32 display

100 ad = y*32 + INT(x/8) : REM pixel address
.. is done quicker with.

100 ad = y<<5 + x>>3 : REM pixel address

Coding for space
Most of the techniques used to improve the speed of a program can also reduce the number of bytes used by that program.

Spaces.

Remove spaces from your code. The only space you don't need to worry about is the one between the line number and the code as this is stripped during input parsing and the apparent space is generated by the LIST command output.
Removing REM.
Remove remarks from your code. Remarks like spaces don't do anything, removing remarks, can save a lot of space.
Variables.
Use variables. Often you will find yourself using the same numeric value again and again. If this value has many digits, such as the value for e (2.718282), then assigning that value at the beginning of the program can start to save space with the third use.

Re-use variables. Every time you assign a new variable a value it takes up eight more bytes (ten for strings) of the available memory. If you have a variable that is only used as a loop counter then try to use it for temporary values or GET values elsewhere in the program.

Constants.
There are two constants defined in EhBASIC, PI and TWOPI. They are the closest floating values to pi and 2*pi and will save memory and improve accuracy (they have 32 bit mantissas) each time you can use them.
Packing them in.
Another way to save space is to place as many commands as possible on each line, this will save you five bytes every time you put another command on an existing line compared to using a new line.
INC and DEC.
INCrement and DECrement also save space. Either will save you three bytes for each variable INCremented or DECremented.

Derived functions
The following functions, while not part of BASIC, can be calculated using the existing BASIC functions.

SecantSEC(X)=1/COS(X)
CosecantCSC(X)=1/SIN(X)
CotangentCOT(X)=1/TAN(X)
Inverse sineARCSIN(X)=ATN(X/SQR(X*X+1))
Inverse cosineARCCOS(X)=-ATN(X/SQR(X*X+1))+PI/2
Inverse secantARCSEC(X)=ATN(SQR(X*X-1))+(SGN(X)-1)*PI/2
Inverse cosecantARCCSC(X)=ATN(1/SQR(X*X-1))+(SGN(X)-1)*PI/2
Inverse cotangentARCCOT(X)=-ATN(X)+PI/2
Hyperbolic sineSINH(X)=(EXP(X)-EXP(-X))/2
Hyperbolic cosineCOSH(X)=(EXP(X)+EXP(-X))/2
Hyperbolic tangentTANH(X)=-EXP(-X)/(EXP(X)+EXP(-X))*2+1
Hyperbolic secantSECH(X)=2/(EXP(X)+EXP(-X))
Hyperbolic cosecantCSCH(X)=2/(EXP(X)-EXP(-X))
Hyperbolic cotangentCOTH(X)=EXP(-X)/(EXP(X)-EXP(-X))*2+1
Inverse hyperbolic sineARGSINH(X)=LOG(X+SQR(X*X+1))
Inverse hyperbolic cosineARGCOSH(X)=LOG(X+SQR(X*X-1))
Inverse hyperbolic tangentARGTANH(X)=LOG((1+X)/(X))/2
Inverse hyperbolic secantARGSECH(X)=LOG((SQR(X*X+1)+1)/X)
Inverse hyperbolic cosecantARGCSCH(X)=LOG((SGN(X)*SQR(X*X+1)+1)/X)
Inverse hyperbolic cotangentARGCOTH(X)=LOG((X+1)/(X-1))/2

e-mail me [e-mail]
Last page update: 16th April, 2003.