BASIC Efficiency Issues
Most efficiency issues are simply common sense. They are equally
applicable to Pick Basic and to all other programming languages.
In the absence of other information, the general rule is to keep the code
as clean and straight forward as possible. This will not only be
generally more efficient, but will help control the maintenance and support
problems of complex systems. Most of the time (99%+) it is much better
to spend your time to save a line of code altogether than to worry about
the fastest instruction to use.
Obvious issues are to avoid redundant opens, excessive variables in pass
lists, and large dynamic arrays. On most systems, the Pick Basic
compiler does not do as much to optimize code as some of the newer, more
sophisticated compilers. This means you need to pay more attention
to writing clean code, and in particular, to avoiding redundant sub-expressions.
Redundant dynamic array extracts are especially poor – see the dynamic
array bullet. Moving static expressions out of loops is an easily
done but often overlooked improvement.
Never create a hard loop. Its detrimental effects on the machine
and on other processes is hard to over estimate. For example, if
you use a LOCKED clause in record reads, the locked path MUST have an input
statement or a sleep command! Another area often creating hard loops
are background batch jobs. Ideally, they should use specific inter-process
scheduling mechanisms to prevent the need for hard loops. At the
very least, there should be a significant sleep in the loop. This
should be the largest value possible and at least the expected loop time.
Tests show most complex programs actually read and write the same records
several times! Saving these extra reads and writes saves group and
record lock issues, external bus traffic and map operations as well as
CPU cycles. Two READV's will ALWAYS be more expensive than reading
the record outright. The overall performance improvements from this type
of clean up can be dramatic.
Don't read a record if you don't need the results (yes – it really happens)!
Take advantage of naturally occurring ordering and don't read a record
if you already have it read (code tables, division records, regions, company
headers, etc.). Do not use READUs if the application can infer a
lock – if the system always reads an order header with a record lock before
reading the order details, NO LOCKS are needed on the details! Do
not use private lock structures – the system supported routines are more
robust and much more efficient.
Never use the form X=OCONV(ID,"Tfile;X;n;n). This is the equivalent
of a file OPEN and a READV every time its executed! It also hides
the data reference from the common file cross reference programs.
One of the single biggest improvements is to use static arrays in preference
to dynamic arrays. Handling strings in dynamic arrays cause the system
to pass characters from the start of the string to locate the required
attribute. Even half a dozen trips through a mod-erate size string
can be substantially more expensive than setting up a static array and
doing a MAT read. If the record is large or the strings are repetitively
handled in loops, the improvement in overall CPU time of the program can
often be more than one third!
Avoid EXECUTEs when simpler internal functions are available. Using
an execute "who" or "sleep" can be 20 times more expensive than the local
use of "OCONV('','U50BB') or a sleep statement. Where possible, send
more than one statement to an EXECUTE.
You certainly should use subroutines whenever it makes sense by simplifying
the design or testing, but avoid external subroutines if internal GOSUB's
would do as well. When calling an external subroutine, be sure you
have properly partitioned the design so you are able to do significant
work in the subroutine – the expense of a subroutine call with several
passed variables can be the equivalent of several dozen in-line statements.
If possible, use unnamed common to pass the variables to subroutines.
Use named common sparingly – accessing a named common variable is slightly
more expensive than accessing regular variables or unnamed common variables,
but is worth it if it saves multiple opens, reads or writes, or evaluations
of complex expressions.
An admittedly small, but nearly mechanical change involves being conscious
of the required type of a variable. Basic allows painless conversions
from numeric data to strings data, or visa versa. However, because
these conversions happen at run-time, they are not totally cost free.
Take the time to properly specify the type of a constant expression or
variable. For example, X<I> = 5 causes the system to do a numeric
to string conversion at runtime every time the expression is encountered.
Since the dynamic assignment always requires a string, the expression X<I>
= "5" is measurably cheaper.
In the same vein, building a print line as a single statement may be slightly
harder to edit than 10 or 15 lines of repetitive concatenates, but it is
many times cheaper.
Understanding a piece of code well enough to make a fix or enhancement
is a big investment of your time and energy. Capitalize on this hard
won understanding by adding (or correcting) comments and making small cleanups
to some of the code. As a rule of thumb, if you had a problem understanding
the code, or your fix took longer than you thought it should, its important
to use your invested time to make the situation better for the next occasion.
I'm not suggesting you disrupt your company schedules, just spent a few
extra minutes to clean-up or document the worst of the situation.
Over time, you'll be amazed at how much even large systems improve.
Lastly, if the program is truly hopeless, please take the effort to document
the problem in writing to your company's management. It's likely
that a proper fix involves a broad redesign rather than a simple patch.
Changes of this magnitude should be decided on by MIS management so they
can be properly scheduled and perhaps coordinated with other program or
other system changes.