Optimizing your code

Internally, the DOS and 32-bit Windows operating systems are very different.  DOS applications run in 16-bit "Real Mode", which means that the largest single data object is 64 Kilobytes (the largest 16-bit value is 65535).  And because of the way "memory segmentation" works, the total addressing space available in "Real Mode" is a little over 1 Megabyte.  Since the CPU is running in 16-bit mode (Real mode), numeric operations are fastest when variables are 16-bits (Integers and Words).

In contrast, 32-bit Windows runs in "Protected Mode", and the largest single data object is two Gigabytes (the largest 32-bit value is actually four Gigabytes, but the operating system reserves half of that for itself).  Because the CPU is running in 32-bit mode (Protected mode), numeric operations are fastest when variables are 32-bits (Long-integers and Double-words).

Use 32-bit Variables  

As you move your DOS code into PowerBASIC, you should replace all "Integer" and "Word" variable types with Long-integers and Double-words respectively - particularly in FOR/NEXT loops and integral-class math calculations.  It actually takes the CPU longer to perform a calculation on a 2-byte Integer than it does with a 4-byte Long-integer, and it takes even longer with single byte variables.

Use Register Variables  

Register variables are variables that are stored directly in specific CPU registers, rather than in application memory.  Since data in a CPU register can be accessed much faster, and with less code, Register variables are valuable optimization tools.

Register variables are always local to the Sub or Function where they appear.  In the current version of PowerBASIC, there may be up to two integral-class Register variables (Word/Dword/Integer/Long), and up to four Extended-precision floats.  It is possible that future versions of the compiler will change these limits, so you may declare an unlimited number of them.  Any "extra" Register variables are automatically reclassified as locals.

The REGISTER statement allows you to choose which variables will be classified as Register variables.  If you do not make the choice in a particular Sub/Function, the compiler will attempt to choose for you.  By default, the compiler will always assign the first two integral-class local variables available.  Extended-precision float variables will be automatically assigned only in functions that contain no external function calls.

integral class Register variables are most efficient for variables that are updated or used often, such as For/Next loop counter variables, and variables that are used repeatedly as array indexes.  Floating-point Register variables should generally be chosen with a bit more caution, since the compiler must generate code to save and restore them to conventional memory around each call to a Sub or Function.  In some rather rare cases, it is possible that floating-point Register variables could actually reduce execution speed.  However, they are extremely valuable with intensive floating-point calculations and in functions that have few references to other Subs and Functions.

Due to the design of FPUs (floating point units), and the instruction sets available, the first float register variable declared in your program has far more optimization possibilities than the others do.  Use care in choosing the variable which is used most within floating-point expressions (that is, on the right side of the '=' assignment operator), in order to gain the greatest advantage in execution speed.  Also, remember it is typically valuable to assign floating-point numeric constants to Register variables when they are used in repetitive or intensive calculations.

You must use care with Inline Assembler floating-point opcodes in functions that enable Register variables.  Floating-point Register variables may occupy up to four of the FPU registers, so you must limit your use of the x87 registers to the remaining four.  Further, floating-point Register variables may never be referenced by name from Inline Assembler code, as the compiler cannot always track the register locations with absolute certainty.

Register variables are preserved when a call to an external DLL or API function is made.  Register variables are automatically thread-safe too.

Because Register variables are stored within the CPU, it is not possible to use VARPTR on a register variable.  When passing a register variable to a Sub/Function BYREF, the compiler temporarily converts the register variable into a memory variable, and reloads the register variable upon return from the Sub/Function call.  The overhead that this adds is insignificant.


See Also

The Inline Assembler