Euphoriaのパフォーマンスについての小技
あらゆるプログラミング言語において、特にEuphoiraでは、動作速度に関しての結論を出す前に実際に計測を行う必要があります。 Euphoriaでは実行回数プロファイルと時間プロファイル(DOS32専用)の両方が提供されています。詳しくはrefman.docを参照してください。これらのプロファイル結果に大抵は驚愕するでしょう。なお、プログラムで高い割合の合計時間を使用している箇所あるときは、その場所(または最も実行回数が多い箇所)に絞って改良を行ってください。合計時間の0.01%のみ使用するコード部分は書き直す箇所ではありません。通常はコードを一箇所、または数箇所に手を入れると意義深い差が生じことがあります。 time()関数を使用してコードの実行速度を計測することもできます。すなわち、 atom t t = time() for i = 1 to 10000 do -- ここに小さなコード片が入ります。 end for ? time() - t どちらの方法が高速か見るためにコード片を別のものに書き直すことがあります。
プロファイルによりプログラムの動作速度上の問題点を提示できます。これらは通常ループの内側にあります。ループ内側の計算を見て、ループを毎回実行する必要は本当にあるか、ループは以前にも一度実行されたか、あなた自身に問い合わせます。
加算は乗算より高速です。場合によりループ変数を乗算から加算へ置き換えることができます。これは: for i = 0 to 199 do poke(screen_memory+i*320, 0) end forこうなります: x = screen_memory for i = 0 to 199 do poke(x, 0) x = x + 320 end for 変数に対する結果の保存
非常に頻繁に呼び出されている高速な小規模ルーチンがあるとき、それはルーチンとして呼ぶのではなく、インライン展開((Inline expansion)を行って処理したほうが処理時間を節約できます。ただしコードの可読性が落ちることがあるため、ルーチンの呼び出しが頻繁に発生する箇所のみインライン展開にしたほうが良いでしょう(訳注:外部プリプロセッサと併用することで、この問題を解決できることがあります)。
Euphoriaでは巨大なシーケンスのデータの操作は命令文を一度使用するだけで処理できます。これは1要素ごとに毎回ループを回して処理をする手間を省きます。すなわち、 x = {1,3,5,7,9} y = {2,4,6,8,10} z = x + yループ版: z = repeat(0, 5) -- 必要であれば。 for i = 1 to 5 do z[i] = x[i] + y[i] end for ほとんどのインタプリタ言語では、ループ内でスカラー演算を処理するより、シーケンス(配列)全体をひとつの命令文を処理するほうが非常に高速です。これはインタプリタでは命令文を実行するごとに膨大なオーバーヘッドが生じるからです。Euphoriaでは違います。Euphoriaは非常に洗練されており、インタプリタ処理のオーバーヘッドが少ないのですが、シーケンス操作では常に優位とは限りません。唯一の解決方法は両方の時間を計測することです。通常は1つの命令文だけでシーケンスを処理するとき要素ごと対価は低いですが、シーケンスの確保と開放にはオーバヘッドが関連しますが他のシーケンス操作手法によりオーバヘッド規模が変動することがあります。
Euphoriaは特定の特別な場合に自動的に最適化を行います。変数xとyは後述の任意の式です。 x + 1 -- 一般的に x + y より高速です。 1 + x -- 一般的に y + x より高速です。 x * 2 -- 一般的に x * y より高速です。 2 * x -- 一般的に y * x より高速です。 x / 2 -- 一般的に x / y より高速です。 floor(x/y) -- x と y が整数ならば、than x/y より高速です。 floor(x/2) -- floor(x/y) より高速です。 上述のxは単純に編集であり、yは任意の変数または式です: x = append(x, y) -- 一般的に z = append(x, y) より高速です。 x = prepend(x, y) -- 一般的に z = prepend(x, y) より高速です。 x = x & y -- xがyより大きいときは -- 一般的に z = x & y より高速です。シーケンスが"増大する"ループを記述したときデータを追加するか連結することにより、一般に処理時間は追加する要素の数の(N)の2乗に比例して増大します。 しかし、特殊な最適化形式としてappend(), prepend()またはを上述のリストにある連結演算子を使用できますが、実際の処理時間はN(おおよそ)に比例して増大します。これにより極端に長いシーケンスを作成するとき膨大な処理時間を節約して保存することができます(最大サイズのシーケンスを作成するためにrepeat()を使用することもでき、さらに要素を埋めつくすための方法は後述されています)。
より高速に動作させるには、これを: 左側 = 左側 演算子 演算式このように変更します: 左側 演算子= 演算式常に左側には最低2つの添字、または最低1つの添字とスライスが含まれます。全て単純な形式であるときは2つ形式は同じ速度で動作します(または非常に似通っている)。
テキストをスクリーンに書き込むときに、puts()またはprintf()を使用すると描画速度は遅いです。DOS32では必要に応じて、ビデオメモリーに書き込むか、display_text_image()を使用すると高速に描画できます。puts()でスクリーンへの出力は非常にオーバヘッドがありますが、出力文字あたりでは比較的小さな対価が加算されます。exw (WIN32)ではオーバーヘッドは非常に高いです(Windows 95/98/ME以降では)。 LinuxおよびFreeBSDではDOS32とWIN32のテキスト出力速度の中間くらいです。したがってput()を文字ごとに呼び出すではなく、長い文字列を構成して呼び出したほうが意味があります。しかし欠点として1行より長い文字列を構成する方法がありません。 テキスト出力が遅くなる主な原因はオペレーティングシステム側のオーバヘッドです。
一般的なルーチンの数種類は非常に高速です。恐らくC言語またはアセンブリ言語を使用したり他の全ての方法を使用してたとしても、これより処理を高速にできないでしょう。これらは次の通りです:
他のルーチンも非常に高速ですが速度が重要なときは場合により、さら処理を高速にできることがあります。 x = repeat(0,100) for i = 1 to 100 do x[i] = i end forは次のものよりも多少高速です: x = {} for i = 1 to 100 do x = append(x, i) end forappend()はxの大きさが増えるたびに領域の確保と再確保を行います。repeat()は最初に一度だけ領域を確保します(不親切なことにappend()はxになにか追加するたびに十分な領域を確保しません。これはもっと領域が必要になったときに多少確保され、再確保の回数により縮小されます)。 これは: remainder(x, p)こう書き換えられます: and_bits(x, p-1)これによりpは整数の2の累乗が非常に高速に行えます。必ずxは非負数の32bit整数値である必要があります。 arctan()はarccos()またはarcsin()より高速です。
Euphoriafind() は50要素までシーケンスの値の検索方法としては非常に高速です。その他に、ハッシュテーブル (demo\hash.ex) または 二分木 (demo\tree.ex)の使用を検討できることがあります。
ほとんどの場合において、include\sort.eにあるシェルソートを使用できます。 大量のデータをソートするときは、demo\allsorts.eにあるソートのうち一つを試したほうが良いことがあります(すなわち、 素晴らしいソート)。データがメモリーに収めるには大きすぎるときであっても、Euphoriaの自動メモリースワップ機能に依存しないでください。その代わりに一度に2000から3000レコードをソートして、連続でテンポラリファイルに書き出してください。そして全てソートが終わった後にテンポラリファイルを1の巨大なソート済みファイルとして統合してください。 なお、データが整数値でのみ構成されており全て非常に狭い範囲にあるならば、demo\allsorts.eにあるバケツソート を試してみてください。
CPUの速度の高速化とともに、オンチップ・キャッシュ・メモリーとメイン・メモリーまたはDRAM(ダイナミック・ランダム・アクセス・メモリー)間の速度の差は非常に大きくなります。コンピュータには256MB以上のメモリーを搭載しているかもしれませんが、Pentiumでは8K(データ) + 8KB(インストラクション)のキャッシュか、MMX PentiumまたはPentium II/IIIでは、これに + 16KB(インストラクション)の一次キャッシュがあるだけです。ほとんどのマシンでは256KBまたは512MB以上の"二次"キャッシュを備えています(訳注:古い記述ですので必要であれば調べてください)。 初めから終わりまで、アルゴリズムは2000から3000以上の要素を持つ長いシーケンスの中をひとつずつ通過しながら、何度も要素ごとに小さな操作を実行しますが、オンチップデータキャッシュは効率よく使用されません。次の要素へ移動する前に、いくつかの処理が要素ごとに適用されますが、一気に通過したほうが良いことがあります。これと同じ引数を持つプログラムではデータが巨大なためスワップが開始されてしまい、最も最後に使用されたデータはディスクへスワップアウトされます。 これらのキャッシュの効果はEuphoriaでは低レベルコンパイル言語とは違い顕著な差は出ませんが、これらは測定可能です。
Euphoriaから32bit x86機械語で記述されたルーチンを呼び出すことができます。WIN32とLinuxおよびFreeBSDでは.dll または .soファイルにあるC言語ルーチンを呼び出すことができ、C言語ルーチンからEuphoriaルーチンを呼び出すことができます。Euphoriaでは直に処理できない何かをするために、または動作速度を改善するためにC言語コードまたは機械語を呼び出すことがあります。 動作速度を向上するには、機械語またはC言語ルーチンが呼び出されるごとに膨大な作業量を処理する必要があり、そうしなければ引数の準備や呼び出しで処理時間が独占されてオーバヘッドが発生してしまい、あまり動作速度が向上しないことがあります。 多くのプログラムではCPU時間のほとんどを複数の内部コアの処理で消費します。この部分のコードをC言語または機械語にできるならば、Euphoiraプログラムの大半を残しながら、Euphoriaの安全性と柔軟性を犠牲にすることなくC言語に匹敵する動作速度を達成できることがあります。
version 3.0から、完全なEuphoria → C言語トランスレータがインストールパッケージに含まれるようになりました。 これは任意のEuphoriaプログラムをC言語ソースファイルの一式へと翻訳してC言語コンパイラでコンパイルすることができるようになります。 トランスレータにより生成された実行可能ファイルはインタプリタと同様に実行できますがインタプリタを使用したときより高速です。動作速度は0.3倍から5倍数以上の高速化されます。
|