Factor/最適化

データ構造

データ構造を適切に選択することは、非常に重要です。

たとえば、ループ内でシーケンスを構築する場合、suffixappend を使うよりも、pushpush-all で要素をプッシュする方がベターです。前者ではシーケンスの先頭部分がコピーされるからです。

複数のシーケンスを分離するときは、サブシーケンス操作ではなくスライスを使うと、パフォーマンスを向上できます。たとえば、要素が百万個の配列に対して rest を呼び出すと、999,999 個の要素からなる新しい配列が割り当てられますが、rest-slice を呼び出せば、1 つのスライスが割り当てられるだけで、操作は非常に効率的になります。

静的スタック効果

Factor コードを最適化するための第一歩は、コードが最適化コンパイラによってコンパイルされるようにすることです。ワードが最適化されるのは、そのワードが静的スタック効果を持つ場合かつその場合に限られます。ワードが静的スタック効果を持つかどうかをチェックするには、次のようにします。

[ my-word ] infer.

これが真であることをアサートするユニットテストは、次のように定義できます。

\ my-word must-infer

高水準オプティマイザ

次のツールを使うと、ワードが高水準オプティマイザによってどのように処理されるか調べることができます。

USE: compiler.tree.debugger
\ foo optimized.

これで、ジェネリックディスパッチが除去されるかどうか、汎用算術演算がマシン語演算に変換されるかどうか、どのワードがインライン化されるのかを知ることができます。

クォーテーションも optimized. に渡すことができます。

[ 100 [ ] times ] optimized.

高水準コンパイラに対しては、ヒントと、上の出力に影響する inline 宣言を指定することができます。

低水準オプティマイザ

ワードが低水準オプティマイザによってどのように処理されるかを調べるには、次のようにします。

USE: compiler.cfg.debugger
\ foo test-mr mr.

ディスアセンブリ

最終的にどのようなコードが生成されるかを見るには、disassemble ワードを使います。

ワードのディスアセンブルは、x86 では libudis86、PowerPC では gdb を使って行われます。

udis--enable-shared オプションを指定してコンパイルする必要があることに注意してください。

その他のアドバイス

  • extra/benchmark/ ディレクトリにあるコードに目を通してください。これらのプログラムは非常に最適化されていますが、高水準スタイルで記述されています。
  • タイトループ内での動的変数の使用は避けてください。代わりに、locals またはスタックを使ってください。
  • イミュータブルな locals とスタック操作はセマンティクス上は同じで、同一のコードにコンパイルされます。コードが明解になる方を使用してください。
  • 一方、ミュータブルな locals は、ミュータブルな値を保持するためのヒープ割り当てコンテナが作成されるために、パフォーマンス上のペナルティを課せられます。
  • 総称ワード、case コンビネータといったコアな言語機能は高度に最適化されており、プログラマが自分で作った同種のものよりおそらく高速です。たとえば、整数のキーが指定された case はジャンプテーブルに変換され、リテラルシーケンスを指定された member? はハッシュドディスパッチまたはビット配列参照になるほか、総称ワードのディスパッチは多数のメソッドにもきわめて良好に対応できるなど、かなり最適化が図られています。
  • fixnum+ などの低水準ワードは使わないでください。インライン化宣言とヒントを適切に組み合わせれば、コンパイラは汎用算術演算を除去できるので、これらの変換をプログラマが手作業で行うより安全で読みやすいコードになります。
  • なお、タイトループ内でシーケンスにアクセスする場合には、時としてアンセーフなシーケンス操作が有益なことがあります。ただし、mapreduce、または同種のものを使ってコードを表現するようにしてください。これらは、水面下ではすでにアンセーフな操作を使用しているからです。

This revision created on Tue, 1 Sep 2009 20:49:06 by slava