Optimised Code Patterns


This chapter documents all the automatic Lisp code transformations, done by the Lisp Optimiser, during Lisp file load-time.
The tables show the original Lisp code on left side, and the optimised code on right side.

Additionally, some explanations on the reasons and benefits, and also some insights to the BricsCAD Lisp engine behaviour and special conditions.

All the effects of code pattern optimisations can be verified with dedicated Lisp Benchmarks by every developer.


1. empty arguments and local variables list

(defun func ( x y z / )

=>

(defun func ( x y z )

(defun func ( / )

=>

(defun func ( )

as the / character indicates a list of local variables, the BricsCAD Lisp engine needs to parse the local variables identifiers, because the internal structure of the defun block depends both on arguments and local variables;
thus, when no arguments or no local variables are effectively present, the defun code block uses a lightweight internal structure, which saves Lisp memory and improves runtime performance


2. single statement inside (progn ...)

(progn (setq var 123) )

=>

(setq var 123)

the (progn ...) statement is not a lightweight operation, triggering some significant internal operations, in our Lisp engine (might be different in other AutoLISP implementations); in combination with a single expression the (progn ..:) does not make real sense, so eliminating this provides a better performance and reduced memory load


3. comparison with 'nil'

(if (= var nil) ...)

=>

(if (not var) ...)

(if (eq var nil) ...)

=>

(if (not var) ...)

(if (equal var nil) ...)

=>

(if (not var) ...)

(if (/= var nil) ...)

=>

(if (boundp var) ...)

using the "=", "eq", "equal" and "/=" functions always do a comparison "by value", so the "var" variable is evaluated for its content, and depending on the type of content (number, string, ename etc.) compared with the 'nil' argument;
because comparing with 'nil' is basically a "logical" (boolean) operation whether "var" has *any* kind of content, the (not) resp. (boundp) function do a much easier comparison like "has any content", the type of content does not matter and is not analysed;
another advantage :
both (not) and (boundp) functions take only 1 argument, this means, less operations to push/pop arguments to/from the Lisp stack, which also increases performance


4. unnecessary (progn ...)

(while (expr) (progn ...))

=>

(while (expr) ...)

(repeat (expr) (progn ...))

=>

(repeat (expr) ...)

(foreach item lst (progn ...))

=>

(foreach item lst ...)

the (progn ...) which encloses the entire code of "while", "repeat", "foreach" is not necessary at all - but causes significant overhead for our Lisp engine (see above); so eliminating that (progn ...) improves performance and reduces memory load


5. COM property and method names as strings

(vlax-get-property  object "property")

=>

(vlax-get-property  object 'property)

(vlax-put-property  object "property" ...)

=>

(vlax-put-property  object 'property ...)

(vlax-invoke-method object "method" ...)

=>

(vlax-invoke-method object 'method ...)

using symbol name instead of string name provides some internal advantages (less string copy operations, no uppercase conversion necessary)


6. list member access by index

(nth 0 lst) ... (nth 9 lst)

=>

(vle-nth0 lst) ... (vle-nth9 lst)

using the (nth) function is very common code - especially (nth 0 lst), (nth 1 lst) is widely used, though the (car lst), (cadr) etc. functions are more performant; the Optimiser uses the (vle-nth) function here, for indices 0...9. Especially when (nth 0 lst) etc. is used inside loops, the performance gain is surprisingly : reason is, that (vle-nth) function uses only 1 argument rather than 2, which reduces the amount of push/pop operations in Lisp stack.


7. (cdr (assoc ...))

(cdr (assoc item lst))

=>

(vle-cdrassoc item lst)

the (cdr (assoc ...)) is one of the most fundamental code patterns found in any Lisp dialect ... but any modern Lisp dialect has a dedicated function cassoc (or similar) for this elementary operation; our Lisp engine provides (vle-cdrassoc) for this purpose, which provides significant performance gain, reduced memory load and less GarbageCollections :
1. only 1 lisp instruction instead of 2, which gives >= 50% performance advantage
2. there is no temporary, intermediate Lisp result from (assoc) operation, which needs to be created and stored, and after (cdr) it is garbage


8. (reverse (cdr (reverse ..)))

(reverse (cdr (reverse lst))

=>

(vle-remove-last lst)

another most fundamental operation is to remove the last list item ... unfortunately, AutoLISP does not provide a related function (as any modern Lisp does) - developers have no other choice than to use this highly ineffective code;
BricsCAD Lisp offers the (vle-remove-last) function here - the performance improvement is huge :
1. only 1 operation instead of 3
2. the 2 temporary, intermediate lists are not created; original code creates the 2 lists, which is costly and causes useless memory load, and more GarbageCollections;


9. (cdr (assoc dxf (entget ...)))

(cdr (assoc dxf (entget ename)))

=>

(vle-entget dxf ename)

in the CAD environment, this code pattern is also very basic and most often used; to improve performance and efficiency here, the Lisp engine provides the (vle-entget) function :
1. only 1 operation instead of 3
2, creating temporary data by (entget) and (assoc) is completely bypassed; as entity definition data could be rather large, creating those data is very costly, and the memory used is immediately garbage afterwards


10. boolean usage of (tblsearch)

(not (tblsearch table item))
(null (tblsearch table item))
(if (tblsearch table item) ...)
(and (tblsearch table item) ...)
(or (tblsearch table item) ...)
(while (tblsearch table item) ...)

=>

(not (vle-tblsearch table item))
(null (vle-tblsearch table item))
(if (vle-tblsearch table item)) ...
(and (vle-tblsearch table item) ...)
(or (vle-tblsearch table item) ...)
(while (vle-tblsearch table item) ...)

this code pattern is also very basic and most often used to verify whether a particular table item is present; to improve performance and efficiency here, the Lisp engine provides the (vle-tblsearch) function :
as only the "boolean" information is required, there is no need to generate the (entget)-like data list for the table item; this optimisationsignificantly saves execution time and reduces memory workload (thus triggering less GarbageCollections).
Note : there is also the native (tblobjname) in all AutoLISP-compatible systems, which can be used here as well.


11. boolean usage of (dictsearch)

(not (dictsearch dict item))
(null (dictsearch dict item))
(if (dictsearch dict item) ...)
(and (dictsearch dict item) ...)
(or (dictsearch dict item) ...)
(while (dictsearch dict item) ...)

=>

(not (vle-dictsearch dict item))
(null (vle-dictsearch dict item))
(if (vle-dictsearch dict item) ...)
(and (vle-dictsearch dict item) ...)
(or (vle-dictsearch dict item) ...)
(while (vle-dictsearch dict item) ...)

this code pattern is also very basic and most often used to verify whether a particular dictionary item is present; to improve performance and efficiency here, the Lisp engine provides the (vle-dictsearch) function :
as only the "boolean" information is required, there is no need to generate the (entget)-like data list for the dictionary item; this optimisation significantly saves execution time and reduces memory workload (thus triggering less GarbageCollections).
Note : there is also the (vle-dictobjname), which can be used here as well (and is available for other CAD systems by emulation).


12. boolean usage of (entget)

(not (entget ename))
(null (entget ename))
(if (entget ename) ...)
(and (entget ename) ...)
(or (entget ename) ...)
(while (entget ename) ...)

=>

(not (vle-ename-valid ename))
(null (vle-ename-valid ename))
(if (vle-ename-valid ename) ...)
(and (vle-ename-valid ename) ...)
(or (vle-ename-valid ename) ...)
(while (vle-ename-valid ename) ...)

this code pattern is also very basic and most often used to verify whether a given entity is valid and not erased; to improve performance and efficiency here, the Lisp engine provides the (vle-ename-valid) function :
as only the "boolean" information is required, there is no need to generate the (entget) data list for the entity, which is not a lightweight operation; this optimisation significantly saves execution time and reduces memory workload (thus triggering less GarbageCollections).



The BricsCAD test system covers all these cases to ensure proper operation.


©  Bricsys NV. All rights reserved.