最適化されたコードパターン-BricsCAD
最適化されたコードパターンの内容
この記事は、Lispファイルのロード時にLispオプティマイザが行う自動的なLispコードの変換についての説明。
左側が元のLispコード、右側が最適化されたコードになっている。 さらに、その理由と効果、BricsCAD Lispエンジンの動作と特殊な条件についての説明もある。
コードパターンの最適化の効果は、専用のLispベンチマークで検証することができる。
1. 空の引数とローカル変数のリスト
(defun func ( x y z / ) => (defun func ( x y z ) ) => (defun func ( x y z )) (defun func ( / ) => (defun func ( )) => (defun func ( ))
文字がローカル変数のリストを示すように、defunブロックの内部構造は引数とローカル変数の両方に依存するので、BricsCAD Lispエンジンはローカル変数の識別子をパースする必要がある。 したがって、引数やローカル変数が実質的に存在しない場合、defunコードブロックは軽量な内部構造を使用し、Lispメモリを節約し、実行時のパフォーマンスを向上させることができる。
2. (progn ...) 内の単一ステートメント
(progn (setq var 123) ) => (setq var 123)
(progn ...) は軽量な操作ではなく、Lispエンジンでは重要な内部操作を引き起こす(他のAutoLISP実装では異なるかもしれない);
単一の式との組み合わせでは (progn ...) は本当の意味を持たないので、これを削除すると性能が向上しメモリ負荷が軽減される。
3. '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) ...)
「=」、「eq」、「equal」、「/=」関数を使うと、常に「値による」比較を行うので、「var」変数の内容が評価され、内容の種類(数値、文字列、enameなど)によっては、引数の「nil」と比較される。 nil' との比較は基本的に "var" が *どんな* 内容を持っているかという "論理" (boolean) 操作なので、 (not) resp. (boundp) 関数は "has any content" のようにもっと簡単に比較を行い、内容の種類は重要ではなく、分析もされない。
もう一つの利点
- (not) と (boundp) 関数は共に1つの引数しか取らないので、Lispスタックへの引数のプッシュ/ポップ操作が少なくなり、パフォーマンスも向上する。
4. 不要な (progn ...)
(while (expr) (progn ...)) => (while (expr) ...) (repeat (expr) (progn ...)) => (repeat (expr) ...) (foreach item lst (progn ...)) => (foreach item lst ...)
while", "repeat", "foreach "のコード全体を囲む(progn ...)は全く必要ありませんが、Lispエンジンに大きなオーバーヘッドを与える(上記参照)ので、この(progn ...)を取り除くことでパフォーマンスが向上し、メモリの負荷も軽減される。
5. COM プロパティ名およびメソッド名を文字列で指定
(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 ...)
文字列名の代わりにシンボル名を使用することで、内部的な利点がある(文字列のコピー操作が少ない、大文字変換が不要)。
6. インデックスによるリストメンバーアクセス
(nth 0 lst) ... (nth 9 lst) => (vle-nth0 lst) ... (vle-nth9 lst)
(nth) 関数の使用は非常に一般的なコードで、特に (nth 0 lst), (nth 1 lst) は広く使われているが、(car lst), (cadr) などの関数はより性能が高い。
オプティマイザーはここで、インデックス 0...9 に対して (vle-nth) 関数を使用する。
特にループの中で(nth 0 lst)などを使うと、驚くほど性能が向上する。これは、(vle-nth)関数の引数が2つでなく1つだけなので、Lispスタックのpush/pop操作が少なくなるためである。
7. (cdr (assoc ...))
(cdr (assoc item lst)) => (vle-cdrassoc item lst)
(cdr (assoc ...)) はどのLisp言語でも見られる最も基本的なコードパターンです。しかし、最近のLisp言語はこの基本的な操作のために専用の関数 cassoc(または同様のもの)を持っている。
- 2つのLisp命令ではなく、1つのLisp命令で済むので、50%以上の性能向上が期待できる。
- (assoc)処理による一時的なLisp結果がなく、(cdr)処理後はゴミとなる。
8. (reverse (cdr (reverse ..)))
(reverse (cdr (reverse lst)) => (vle-remove-last lst)
もう一つの最も基本的な操作は、リストの最後の項目を削除することである。残念ながら、AutoLISPは(最近のLispのように)関連する関数を提供していないため、開発者はこの非常に非効率なコードを使用する以外に選択肢がないのである。 BricsCAD Lispでは( vle-remove-last )関数を提供しており、その性能向上は非常に大きい。
- 3つの操作の代わりに、1つの操作のみ
- 一時的な中間リストが2つ作成されない。元のコードでは2つのリストが作成され、コストがかかり、無駄なメモリ負荷とガベージコレクションが増加する。
9. (cdr (assoc dxf (entget ...)))
(cdr (assoc dxf (entget ename))) => (vle-entget dxf ename)
CAD環境では、このコードパターンも非常に基本的で、最もよく使われる。ここで、性能と効率を向上させるために、Lispエンジンは、(vle-entget)関数を提供する。
- 3つの操作の代わりに1つの操作のみ
- (entget)と(assoc)による一時データの作成が完全に回避される。エンティティ定義データはかなり大きいので、これらのデータの作成は非常にコストがかかり、使用したメモリはその後すぐにゴミとなる。
10. (tblsearch) のブーリアン使用
(not (tblsearch table item)) => (not (vle-tblsearch table item)) (null (tblsearch table item)) => (null (vle-tblsearch table item)) (if (tblsearch table item) ...) => (if (vle-tblsearch table item)) ... (and (tblsearch table item) ...) => (and (vle-tblsearch table item) ...) (or (tblsearch table item) ...) => (or (vle-tblsearch table item) ...) (while (tblsearch table item) ...) => (while (vle-tblsearch table item) ...)
このコードパターンも非常に基本的で、特定のテーブル項目が存在するかどうかを確認するために最もよく使用される。 この部分のパフォーマンスと効率を改善するために、Lispエンジンは(vle-tblsearch)関数を提供している。 この最適化により、実行時間が大幅に短縮され、メモリ使用量も削減される(したがって、ガベージコレクションの発生も少なくなる)。 注: すべてのAutoLISP互換システムには、ネイティブの(tblobjname)もあり、ここでも使用できる。
11. (dictsearch) のブーリアン使用
(not (dictsearch dict item)) => (not (vle-dictsearch dict item)) (null (dictsearch dict item)) => (null (vle-dictsearch dict item)) (if (dictsearch dict item) ...) => (if (vle-dictsearch dict item) ...) (and (dictsearch dict item) ...) => (and (vle-dictsearch dict item) ...) (or (dictsearch dict item) ...) => (or (vle-dictsearch dict item) ...) (while (dictsearch dict item) ...) => (while (vle-dictsearch dict item) ...)
このコードパターンも非常に基本的で、特定の辞書項目が存在するかどうかを確認するために最もよく使用される。 ここでの性能と効率を改善するために、Lispエンジンは(vle-dictsearch)関数を提供している。 この最適化により、実行時間が大幅に短縮され、メモリ使用量も削減される(したがって、ガベージコレクションの発生も少なくなる)。 注: (vle-dictobjname)もあり、こちらも同様に使用できる(他のCADシステムでもエミュレーションで使用できる)。
12. (entget) のブーリアン使用
(not (entget ename)) => (not (vle-ename-valid ename)) (null (entget ename)) => (null (vle-ename-valid ename)) (if (entget ename) ...) => (if (vle-ename-valid ename) ...) (and (entget ename) ...) => (and (vle-ename-valid ename) ...) (or (entget ename) ...) => (or (vle-ename-valid ename) ...) (while (entget ename) ...) => (while (vle-ename-valid ename) ...)
このコードパターンも非常に基本的で、与えられたエンティティが有効で消去されていないかどうかを確認するために最もよく使われます。この部分のパフォーマンスと効率を改善するために、Lispエンジンは (vle-ename-valid) 関数を提供します。 この最適化により、実行時間の大幅な短縮とメモリ使用量の削減(ガベージコレクションの発生を抑える)が可能となる。
"""BricsCADのテストシステムは、これらのすべてのケースをカバーし、正常な動作を保証している。"""