1
0
mirror of https://github.com/tilleul/apple2.git synced 2024-11-29 15:50:11 +00:00
bitmap-editor/applesoft/nfs/README.md
2022-06-30 23:17:25 +02:00

6.0 KiB

Applesoft: Need For Speed

So you like Applesoft ? And you think you can write an action game with it ? Or maybe a science program ? Yes, you can ... will it be fast ? ... Probably, no ...

BUT ! ... Where there's light, there's hope !

Here are several tricks you can use to optimize your Applesoft code for SPEED !

Summary

Methodology

Not only am I going to show you that some code is faster than other, I'm going to prove it !

In order to do that, I'm using AppleWin, an Apple II emulator that has a cycle counting/difference feature. What I do is set a breakpoint on the Applesoft "RUN" statement in $D912 and another on the "END" statement in $D870. So everything between "RUN" (as detected by Applesoft) and "END" in the program is considered for cycles count.

To make sure that code snippets compare, I initialize some variables with the same content, even if they're not used in the snippet, this is only to be fair for the snippet where it will be actually used. Doing so allows me to determine by difference the fastest snippet.

For example, this snippet:

10 B=17: C=2
20 A=B/C
30 END

is compared with

10 B=17: C=2
20 A=B/2
30 END

Having the same line 10 for both snippets allows me to compare the speed of line 20. If line 10 did not include C=12, then it would not be a fair comparison between the two snippets.

Snippet #1 takes 8821 cycles from "RUN" to "END", while snippet #2 takes 9184 cycles. It means that the difference between A=B/C and A=B/2 is 363 cycles. It does not mean that using a variable instead of a actual number in the code is 363 cycles faster, it just means it IS faster. The exact cycle difference depends on several other factors, the first one, although it's probably not the most important one, being the order in which your variables are declared.

All the examples here will specify cycles count, those cycles are ALWAYS from "RUN" to "END".

Declare your most used variables first

Applesoft variables are stored in three separate areas: one for the numeric variables, one for the string variables and one for the arrays variables.

You don't need to declare a variable to use it. As soon as a variable name is discovered by the Applesoft parser, the following happens:

  • the type of the variable is determined so Applesoft knows where to look for the variable
  • Applesoft scans the memory repository for the specified variable type and looks for an existing variable of the same name
  • if it's not found, it means it's a new variable and the appropriate space (variable name, type, array indices, value) is reserved at the top of the memory pile where all variables of the same type reside.
  • if it's found or if the variable has just been created, its value is used/replaced/computed/etc (depending on the actual code)

As you see, numeric (float/real and integer) variables, string variables and arrays are stored in several different ways but they all share one thing in common: once a variable is encountered and once its type has been determined, the Applesoft parser will search for the variable in one of the three memory locations in the same way: from the bottom to the top of memory.

This means that variables are not "ordered" by their names ... It means that, in memory, variable Z might be before variable A... It also means that the time spent to look for a variable depends on how soon it was found in the code. How much time ? Let's find out.

Let's create a variable A and another named Z with equal values, then let's print the value of variable A and then in a second snippet, the value of variable Z.

10 A=123: Z=A
20 PRINT A
30 END

This takes 32389 cycles. Second snippet just prints variable Z instead of A.

10 A=123: Z=A
20 PRINT Z
30 END

This takes 32423 cycles. That's a difference of 34 cycles. It looks insignificant and it is ... but it has an impact on all the other techniques I'm gonna teach you.

Let's have another example. Now this time, we will declare 26 different variables named from A to Z and see the cycles count difference when accessing the first one or the last one declared.

10 A=123: B=A: C=A: D=A: E=A: F=A: G=A: H=A: I=A: J=A: K=A: L=A: M=A: N=A: O=A: P=A: Q=A: R=A: S=A: T=A: U=A: V=A: W=A: X=A: Y=A: Z=A
20 PRINT A
30 END

This took 73775 cycles. Second snippet is identical except we access variable Z

10 A=123: B=A: C=A: D=A: E=A: F=A: G=A: H=A: I=A: J=A: K=A: L=A: M=A: N=A: O=A: P=A: Q=A: R=A: S=A: T=A: U=A: V=A: W=A: X=A: Y=A: Z=A
20 PRINT Z
30 END

This took 74560 cycles. The difference is 785 cycles. Again, it's not gigantic. But ! Wait ! Remember that snippet in the first section (Methodology) ? It had a difference of 363 cycles just by replacing a harcoded value of 2 with a variable name. It would mean that if we're not careful, we might lose the advantage we took for granted.

Let me rephrase this: imagine if Z was holding a value you need to use OFTEN ... myself I like to put zero in Z because it's obviously a good variable name for such a value ...

Let's see that with two other snippets. Snippet #1 will declare Z first, snippet #2 will declare Z last and snippet #3 will not use Z but hardcoded value of 0

10 Z=0: A=123: B=A: C=A: D=A: E=A: F=A: G=A: H=A: I=A: J=A: K=A: L=A: M=A: N=A: O=A: P=A: Q=A: R=A: S=A: T=A: U=A: V=A: W=A: X=A: Y=A
20 PRINT Z
30 END

This took 67920 cycles (it is faster than the previous example because it prints a single number instead of 3 with 123)

Snippet #2:

10 A=123: B=A: C=A: D=A: E=A: F=A: G=A: H=A: I=A: J=A: K=A: L=A: M=A: N=A: O=A: P=A: Q=A: R=A: S=A: T=A: U=A: V=A: W=A: X=A: Y=A: Z=0
20 PRINT Z
30 END

Snippet #3

10 Z=0: A=123: B=A: C=A: D=A: E=A: F=A: G=A: H=A: I=A: J=A: K=A: L=A: M=A: N=A: O=A: P=A: Q=A: R=A: S=A: T=A: U=A: V=A: W=A: X=A: Y=A
20 PRINT 0
30 END