Always use VAR1 += VAR2
in place of VAR1 = VAR1 + VAR2
: it is appreciably faster. Favour -=
and :=
similarly.
Don't use expressions as terminating values in FOR/NEXT
loops: use only constants or variables. For instance, rewrite:
FOR RECORD.NUMBER = 1 TO DCOUNT(RECORD.LIST, @FM) * Do something NEXT RECORD.NUMBER
...as...
NUMBER.OF.RECORDS = DCOUNT(RECORD.LIST, @FM) FOR RECORD.NUMBER = 1 TO NUMBER.OF.RECORDS * Do something NEXT RECORD.NUMBER
This prevents UniVerse recalculating the terminating value every time round the loop. If you cannot restructure your FOR
/NEXT
loop because you are relying on the recalculation, replace the FOR
/NEXT
loop with a LOOP
/REPEAT
loop. It may be faster, but it will definitely be more readable.
If your CASE
conditions are mutually exclusive, their order will make no difference to the behaviour of your code, but may make a difference to its performance. Even where the conditions are not mutually exclusive, they can sometimes be rearranged without affecting the logic of the program, to increase its speed.
This is because UniVerse evaluates each condition in turn until it encounters one which is true: the more often it finds that the first CASE
is true, the fewer comparisons it need make in total. (See Basic Conditions for a detailed example).
The quickest way to test a record for existence (assuming you don't need to read any of the information it contains) is to use READV
to read field 0
thus:
READV DUMMY FROM VOC.FILE, 'RECORD.BEING.TESTED', 0 ELSE PRINT "It's missing!"
Never loop through a dynamic array using a subscript to address each field. Addressing individual fields by subscript within a dynamic array takes time: the higher the subscript, the more time. Use LOOP
/REPEAT
around a REMOVE
statement instead. REMOVE
is specifically designed to remove consecutive dynamic array fields without searching from the beginning of an array to resolve a subscript each time.
Use the UniVerse Basic SELECT
statement instead of EXECUTE 'SELECT...'
wherever possible. If the reason you did not use it originally is because you wanted a sorted list, ask yourself if your program really needs to sort the list: quite often batch programs which need not sort are given sorted lists merely because it makes the display of keys 'currently being processed' neater: this is an expensive luxury.
If you already have a dynamic array, and you wish to turn it into a select list, you need NOT write it to &SAVEDLISTS&
and then issue an EXECUTE 'GET.LIST...'
. You may sometimes find this approach taken in early UniVerse Basic (or PICK or INFO Basic) because it was at one time the only available method. You may now, though, simply say SELECT DYNAMIC.ARRAY
: the select statement, when followed by a dynamic array, will turn the array into a select list, and save a great deal of time.
Use dimensioned rather than dynamic arrays unless there is a good reason not to: the elements of a dimensioned array are far quicker to address than the fields of a dynamic array.
If you need a dynamic array, keep it one-dimensional by using only field marks: not value or subvalue marks. Most of the things which dynamic arrays are good at (being searched, sorted, grown, shrunk and traversed) are made difficult or possible once it contains mixed markers. Think of dynamic arrays as a 'list' type.
For some reason...
DYNAMIC.ARRAY := @FM : 'NEWFIELD'
...is generally considerably faster than...
DYNAMIC.ARRAY<-1> = 'NEWFIELD'
...despite the fact that they are nearly identical. Both add a field to a dynamic array. The <-1>
syntax is slightly neater, as if DYNAMIC.ARRAY is empty above, the := @FM
syntax will add an empty field before the new field. However, this can easily be dealt with by writing...
DYNAMIC.ARRAY = DYNAMIC.ARRAY[2, LEN(DYNAMIC.ARRAY) - 1]
The time this takes to execute is usually far less than the time gained by using := @FM
, paricularly when building a long dynamic array in a loop. You need only trim the first character once.
The CONVERT
statement changes all the occurences of a character in a string to another character. If the 'from' and 'to' strings contain more than one character, the first character of the first string is converted to the first character of the second, and so on. The TRIM
function not only trims leading and trailing spaces from a string, but replaces all multiply occurring spaces within the string with single spaces. You can use CONVERT
and TRIM
together to remove empty fields from a dynamic array as follows:
CONVERT @FM TO ' ' IN DYNAMIC.ARRAY DYNAMIC.ARRAY = TRIM(DYNAMIC.ARRAY) CONVERT ' ' TO @FM IN DYNAMIC.ARRAY
This relies on the fact that an empty field is represented by two field marks next to each other. When they are converted to spaces and trimmed, they are replaced with a single space.
Note that this code assumes that your fields do not already contain spaces. If they do, you must find at least one character which your string could not contain (which we'll represent by the variable SAFE.CHARACTER) and safe gaurd the 'real' spaces as follows:
CONVERT ' ' : @FM TO SAFE.CHARACTER : ' ' IN DYNAMIC.ARRAY DYNAMIC.ARRAY = TRIM(DYNAMIC.ARRAY) CONVERT SAFE.CHARACTER : ' ' TO ' ' : @FM IN DYNAMIC.ARRAY
To 'clean' a string of any characters which do not want, first define a variable which contains all the characters which you do want. For instance, if the surnames in your system must be purely alphabetic, try the following:
LEGAL.CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' JUNK.CHARACTERS = SURNAME CONVERT LEGAL.CHARACTERS TO '' IN JUNK.CHARACTERS ; * So anything left is junk... CONVERT JUNK.CHARACTERS TO '' IN SURNAME ; * ...which can be got rid of.
Never rely on the value of a counter variable outside a FOR
/NEXT
loop. It is undefined. Even if you find that it reliably takes a particular and useful value, that could change with the next release of the UniVerse Basic compiler.
Don't use subroutine parameters as 'working variables' within your subroutine. Assign them new values only if it is clearly the purpose of the subroutine to do so.
When calling a subroutine, put brackets around naked variables if you do not wish them to be changed by the subroutine you are calling: this converts them to 'expressions' and is sufficient to prevent them being changed even if the subroutine you are calling attempts to change them.
1. Never create, update, or delete a record without first obtaining an update lock with READU
, MATREADU
or READVU
.
2. Never assume that a record has remained unchanged since you read it unless you obtained an update lock (READU
) or shared lock (READL
) which you still hold.
3. Never keep records locked longer than you have to.