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.
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.
10 DIM b1(19) : REM need 80 bytes for input buffer 20 DIM b2($FF) : REM need $0400 bytes for a screen bufferE.g.
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..
100 a1 = VARPTR(b1(0)) : REM get the address of the buffer spaceWhen 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 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 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 . .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.
10 DIM b1(19) : REM 80 bytes for buffer 40 FOR x = 0 to 79 50 POKE VARPTR(b1(0))+x,ASC(" ") 60 NEXT . .
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.Now we just use a loop like this to load this hex code into a string.
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 -1The code can now be called by doing ..
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=-1Note 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.
140 CALL(SADD(co$))
SpacesRemove 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.Removing REM.E.g. the following ..
.. reads as follows when LISTed
10 REM line 10 20 REM line 20 30 REM line 30
10 REM line 10 20 REM line 20 30 REM line 30Remove 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.GOTO and GOSUB
E.g.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.
. 140 FOR x = 0 to 79 150 POKE $F400+x,ASC(" ") 160 NEXT .
E.g.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.
10 a1 = $F400 . 140 FOR x = 0 to 79 150 POKE a1+x,ASC(" ") 160 NEXT .
E.g.Now the ASC(" ") is only evaluated once and the loop is executed faster.
10 a1 = $F400 . 130 sp = ASC(" ") 140 FOR x = 0 to 79 150 POKE a1+x,sp 160 NEXT .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.Packing them in.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 ..
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.
. 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 .This can easily be resolved though by using a DO .. LOOP instead. So our example code becomes..
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.
. 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) .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.INC and DEC.
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) .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.>> and <<
E.g... is quicker than ..
100 INC a.. and ..
100 a = a+1.. is still quicker than ..
100 INC a,aAlso combine increments or decrements if you can.
100 a = a+2
E.g... is quicker than ..
100 INC so,d
100 INC so : INC deUsing >> 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.. is done quicker with.
100 ad = y*32 + INT(x/8) : REM pixel address
100 ad = y<<5 + x>>3 : REM pixel address
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.Constants.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.
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.
The following functions, while not part of BASIC, can be calculated using the existing BASIC functions.
Secant SEC(X)=1/COS(X) Cosecant CSC(X)=1/SIN(X) Cotangent COT(X)=1/TAN(X) Inverse sine ARCSIN(X)=ATN(X/SQR(X*X+1)) Inverse cosine ARCCOS(X)=-ATN(X/SQR(X*X+1))+PI/2 Inverse secant ARCSEC(X)=ATN(SQR(X*X-1))+(SGN(X)-1)*PI/2 Inverse cosecant ARCCSC(X)=ATN(1/SQR(X*X-1))+(SGN(X)-1)*PI/2 Inverse cotangent ARCCOT(X)=-ATN(X)+PI/2 Hyperbolic sine SINH(X)=(EXP(X)-EXP(-X))/2 Hyperbolic cosine COSH(X)=(EXP(X)+EXP(-X))/2 Hyperbolic tangent TANH(X)=-EXP(-X)/(EXP(X)+EXP(-X))*2+1 Hyperbolic secant SECH(X)=2/(EXP(X)+EXP(-X)) Hyperbolic cosecant CSCH(X)=2/(EXP(X)-EXP(-X)) Hyperbolic cotangent COTH(X)=EXP(-X)/(EXP(X)-EXP(-X))*2+1 Inverse hyperbolic sine ARGSINH(X)=LOG(X+SQR(X*X+1)) Inverse hyperbolic cosine ARGCOSH(X)=LOG(X+SQR(X*X-1)) Inverse hyperbolic tangent ARGTANH(X)=LOG((1+X)/(X))/2 Inverse hyperbolic secant ARGSECH(X)=LOG((SQR(X*X+1)+1)/X) Inverse hyperbolic cosecant ARGCSCH(X)=LOG((SGN(X)*SQR(X*X+1)+1)/X) Inverse hyperbolic cotangent ARGCOTH(X)=LOG((X+1)/(X-1))/2