私は Haskell で遊ぶことから少し長い休憩を取っていましたが、それに戻り始めています。私は間違いなくまだ言語を学んでいます。Haskell を書いているときに常に緊張したり不快に感じたりする原因の 1 つは、慣用的でパフォーマンスの高いアルゴリズムを作成する方法をしっかりと把握していないことです。「時期尚早の最適化はすべての悪の根源である」ことは理解していますが、同様に遅いコードは最終的に処理する必要があり、非常に高レベルな言語は非常に遅いという先入観を取り除くことはできません。
そういうわけで、私はテストケースをいじり始めました。dy/dt = -y; y(0) = 1
私が取り組んでいたものの 1 つは、かなり自明な IVPに適用された、古典的な 4 次ルンゲクッタ法のナイーブで単純な実装でしたy = e^-t
。Haskell と C の両方で完全に単純な実装を作成しました (これについては、すぐに投稿します)。Haskell バージョンは信じられないほど簡潔で、見たときに内部がぼやけていましたが、C バージョン (実際には解析するのはまったくひどいものではありませんでした)は 2 倍以上高速でした。
2 つの異なる言語のパフォーマンスを比較するのは 100% 公平ではないことは承知しています。そして、私たち全員が死ぬ日まで、C は、特に手動で最適化された C コードのパフォーマンスの王としての王冠を保持する可能性が最も高いでしょう。Haskell の実装を C の実装と同じくらい速く実行しようとしているわけではありません。しかし、自分が何をしているのかをもっと認識していれば、この特定の Haskell 実装からもう少しスピードを求めることができると確信しています。
Haskell バージョンは-02
OS X 10.8.4 上の GHC 7.6.3 でコンパイルされ、C バージョンは Clang でコンパイルされ、フラグは付けませんでした。で追跡した場合、Haskell バージョンは平均で約 0.016 秒time
、C バージョンは約 0.006 秒でした。
これらのタイミングは、stdout への出力を含むバイナリの実行時間全体を考慮に入れており、明らかにオーバーヘッドの一部を占めていますが、-prof -auto-all
GC で再コンパイルして実行し+RTS -p
、GC を調べることによって、GHC バイナリのプロファイリングを行いました。との統計+RTS -s
。私が見たもののすべてを本当に理解していたわけではありませんでしたが、GC が制御不能ではないように見えましたが、おそらく少しは抑制できたようです (5%、~93% のユーザーでの生産性、~85総経過時間の割合) であり、生産的な時間のほとんどが function に費やされました。関数iterateRK
を書いたときに遅いことはわかっていましたが、それをクリーンアップする方法がすぐにはわかりませんでした。リストの使用でおそらくペナルティが発生していることに気づきました。cons
ing と、結果を stdout にダンプする際の怠惰。
私は正確に何を間違っていますか?クリーンアップに使用できることを悲劇的に認識していないライブラリ関数またはモナドの魔法は何iterateRK
ですか? GHCプロファイリングのロックスターになる方法を学ぶための良いリソースは何ですか?
RK.hs
rk4 :: (Double -> Double -> Double) -> Double -> Double -> Double -> Double
rk4 y' h t y = y + (h/6) * (k1 + 2*k2 + 2*k3 + k4)
where k1 = y' t y
k2 = y' (t + h/2) (y + ((h/2) * k1))
k3 = y' (t + h/2) (y + ((h/2) * k2))
k4 = y' (t + h) (y + (h * k3))
iterateRK y' h t0 y0 = y0:(iterateRK y' h t1 y1)
where t1 = t0 + h
y1 = rk4 y' h t0 y0
main = do
let y' t y = -y
let h = 1e-3
let y0 = 1.0
let t0 = 0
let results = iterateRK y' h t0 y0
(putStrLn . show) (take 1000 results)
RK.c
#include<stdio.h>
#define ITERATIONS 1000
double rk4(double f(double t, double x), double h, double tn, double yn)
{
double k1, k2, k3, k4;
k1 = f(tn, yn);
k2 = f((tn + h/2), yn + (h/2 * k1));
k3 = f((tn + h/2), yn + (h/2 * k2));
k4 = f(tn + h, yn + h * k3);
return yn + (h/6) * (k1 + 2*k2 + 2*k3 + k4);
}
double expDot(double t, double x)
{
return -x;
}
int main()
{
double t0, y0, tn, yn, h, results[ITERATIONS];
int i;
h = 1e-3;
y0 = 1.0;
t0 = 0.0;
yn = y0;
for(i = 0; i < ITERATIONS; i++)
{
results[i] = yn;
yn = rk4(expDot, h, tn, yn);
tn += h;
}
for(i = 0; i < ITERATIONS; i++)
{
printf("%.10lf", results[i]);
if(i != ITERATIONS - 1)
printf(", ");
else
printf("\n");
}
return 0;
}