Accessing+the+fast+SYS+interface

//by Jon Ripley, July 2006//

The SYS statement allows you to call functions in the following DLLs //(Dynamic Link Libraries)// by name. Functions in all other DLLs must be called by their address in memory.
 * ADVAPI32.DLL
 * COMCTL32.DLL
 * COMDLG32.DLL
 * GDI32.DLL
 * KERNEL32.DLL
 * SHELL32.DLL
 * USER32.DLL
 * WINMM.DLL

The price for the convenience of calling functions in the above DLLs by name is speed. Calling a function by name is approximately 12 times slower than calling it by address. Where a function is called only a few times in a program this may not have much of an impact on the overall speed of a program. However, where a program makes extensive use of functions in the above DLLs, especially in tight loops, calling the functions by name instead of by address can have a significant performance cost.

Finding the address
To find the address of any function that can be called by name use the following routine: code format="bb4w" DEF FNSYS_NameToAddress(f$) LOCAL P%     DIM P% LOCAL 5 [OPT 0 call f$     ] =P%!-4+P% code Here **f$** is the name of the function. **FNSYS_NameToAddress** returns the address of the function. You should read the addresses of functions you will use in the initialisation routine of your program.

The following example demonstrates using **FNSYS_NameToAddress**: code format="bb4w" REM Program initialisation _sleep% = FNSYS_NameToAddress("Sleep")

REM Main loop REPEAT SYS _sleep%, 10 UNTIL FALSE: REM Loop forever code

Do not do the following, this is slower than calling a function by name: code format="bb4w" SYS FNSYS_NameToAddress("Sleep"), 10 code

Finding the address of BASIC I/O routines

 * FNSYS_NameToAddress** can be used to read the address of the BASIC I/O routines as listed in the [|Using BASIC input/output] section of the manual. To read the address of a BASIC I/O routine use code similar to the following:

code format="bb4w" oswrch = FNSYS_NameToAddress("oswrch") code Here we read the address of the **"oswrch"** routine and store it in the variable **oswrch**. Limited practical uses of this technique do exist.

Proof
To demonstrate that calling a function by pointer is significantly faster than calling the same function by name the following proof is included.

code format="bb4w" REM SYS Timing Test C%=1000000 REPEAT code Here **C%** is set to the number of iterations for the timing loops.

code format="bb4w" REM How long does an empty loop take? T%=TIME FOR I%=1 TO C%         : NEXT O%=TIME - T%       PRINT "Empty loop    : " ;(TIME - T%)/100"s" code Here we time how long an empty loop counting to one million takes to execute and store the result in **O%**. This value is used later to offset the time measurements made later. The displayed time is in seconds.

code format="bb4w" REM How long does calling a SYS by name take? T%=TIME+O% FOR I%=1 TO C%         SYS "GetSystemMetrics",0 TO X%        NEXT PRINT "SYS by name  : ";(TIME - T%)/100"s"' code Here we call by name one million times timing how long it takes and display the result in seconds. code format="bb4w" REM How long does calling a SYS by pointer take? T%=TIME+O% SYS "GetModuleHandle","User32.DLL" TO _user32% SYS "GetProcAddress",_user32%,"GetSystemMetrics" TO GetSystemMetrics% FOR I%=1 TO C%         SYS GetSystemMetrics%,0 TO X%        NEXT PRINT "SYS by pointer: ";(TIME - T%)/100"s" UNTIL 1=0 code Here we read the function pointer for, call the function by pointer one million times timing how long it takes and display the result in seconds.

If the above code exits with a **Division by zero** error you should increase the value of **C%** by a factor of ten; that is, add an extra zero to **C%**.