Table of Contents
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:
DEF FNSYS_NameToAddress(f$) LOCAL P% DIM P% LOCAL 5 [OPT 0 call f$ ] =P%!-4+P%
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:
REM Program initialisation _sleep% = FNSYS_NameToAddress("Sleep") REM Main loop REPEAT SYS _sleep%, 10 UNTIL FALSE: REM Loop forever
Do not do the following, this is slower than calling a function by name:
SYS FNSYS_NameToAddress("Sleep"), 10
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:
oswrch = FNSYS_NameToAddress("oswrch")
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.
REM SYS Timing Test C%=1000000 REPEAT 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"
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.
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"'
Here we call “GetSystemMetrics” by name one million times timing how long it takes and display the result in seconds.
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
Here we read the function pointer for “GetSystemMetrics”, 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%.