First Catch your Function: the STSC Compiler
Vector 7:3, page 65
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 65
Let me state from the outset, I am, as the title suggests, a newcomer to the STSC APL compiler. I have compiled about 6 functions so I’m no expert on the subject. However, only about half a dozen people in the UK have used the compiler so, "In the land of the blind, the one-eyed man is king".
The compiler turns APL functions into machine-code functions which one uses like a locked function within the workspace. The compiler does NOT compile whole workspaces. In theory, you compile the slower functions in the workspace. In practice, it’s not that easy. For a start, you have to add declarations. Then, the compiler only handles old style (non-nested) APL. Lastly, if it is too complex, the compiled function itself "calls the interpreter".
How to Compile a Function.¶
"First, catch your function."
Since compiling 10% of the code in the average workspace can often save 80% of the CPU time, there is little point in compiling the whole workspace. So the first step is to discover which function or functions it is worth while compiling.
The best tool for this job is the STSC ⎕MF (Monitor Function); this allows one to locate where all the CPU is being used at run time. (For more information on ⎕MF and efficiency in general, may I refer you to Dave Ziemann’s excellent article "Efficiency in APL". Having applied the points learnt from that article, you may find you don’t need the compiler at all!)
OK, you have written efficient code, and ⎕MF still says that 80% of the CPU is being taken up by 1 function AND the system still runs too slow. Now you know which function to work on. The next stage is to check where the CPU is being used within that function. If only a few lines use most of the CPU, then it may well be worth putting those lines into a sub-function and just compiling that rather than the whole thing.
"Next, add the seasoning."
Before a function can be compiled, some declarations may need to be added. Declarations are required for:
- User-defined functions
- Global variables
- Shared variables
To aid the compiler, other declarations are recommended for:
- Arguments to the function
- Results returned by subroutines
- Variables assigned atypical values (integer or reals assigned Boolean 0 or 1)
- Local variables being used internally (under some conditions).
Getting the right level of declarations is important, and studying the supplied "Compiler reference manual" will help here. Too few declarations will not allow the compiler to produce the best code. Too many or too detailed will burden the compiler and may lead to run time errors due to errors in the declarations.
"Then, place in a pre-heated compiler for 2-3 minutes."
Cooking your function is easy, simply copy it into the compiler workspace and run the compiler function with the name of your function as its right hand argument. The compiler produces lots of screen output, so it’s worth having a session manager turned on (this is also written to a special file from where it can be retrieved) as you may need to examine it rather closely.
The resulting output (assuming the compilation worked) is the "object code". This is stored in a character vector and when fixed via ⎕FX or ⎕DEF produces the compiled function in the active workspace. (Note that ⎕CR or ⎕VR on the compiled function return the source code NOT the object code.)
"The test of the pudding is in the eating"
Having fixed the compiled function in the workspace, you can now rerun the original test you did with ⎕MF to see how successful you were. Speed-ups of between 2 and 200 times are achievable, but some functions even run slower!
If you are happy with the results "bon appetit", if not refer back to the compiler listing to find out why the compiler is unable to speed up the execution. The reference manual gives a lot of help in this area, telling you how to rewrite your function in a way the compiler can take an advantage of. (It might be useful if you can still remember how to write Fortran!)
"How fast is fast food?"
As an example, may I present some relative timings produced from a test on a real problem. It involved a looping numerical (non-integer) calculations on arrays. The original un-compiled code ran in 8.54 CPU time units. On the same problem the modified and compiled code ran in 1.51 CPU time units. The overall effect was to reduce the total CPU used in the application by 50%.
"Here is one STSC prepared earlier"
STSC are now providing many "fast functions" with their interpreter, so you may not need the compiler at all!
"Health warning"
The compiler is unable to deal with:
- Nested array features or syntax
- Error trapping via
⎕ELXor⎕ALX - Large functions. (Large being defined in terms of the number of "tokens" used, rather than number of lines of code.)
- Pregnant women, young children or APL programmers of a nervous disposition.
Final Thoughts¶
My overall impression is that you end up rewriting the function in pseudo-APL that looks (and runs) like Fortran. There are times however, that it pays off.
References¶
Efficiency in APL. Dave Ziemann. Vector Vol.4 No.4. pp.111-144 (April 1988).
APL*PLUS Compiler Reference Manual. Jim Welgang. (June 1986).