我在玩 Haskell 的过程中休息了一段时间,现在我开始重新投入使用。我肯定还在学习这门语言。我意识到,在编写 Haskell 时总是让我感到紧张/不舒服的一件事是,我对如何制作既惯用又高效的算法没有深入的了解。我意识到“过早的优化是万恶之源”,但同样缓慢的代码最终将不得不被处理,而且我无法摆脱我对高级语言超级慢的先入为主的观念。
因此,本着这种精神,我开始使用测试用例。我正在研究的其中一个是经典的 4 阶龙格-库塔方法的简单、直接的实现,应用于相当简单的 IVP dy/dt = -y; y(0) = 1
,它给出了y = e^-t
. 我用 Haskell 和 C 编写了一个完全直接的实现(稍后我会发布)。Haskell 版本非常简洁,当我看到它时,它的内部给了我温暖的模糊感,但是 C 版本(实际上解析起来并不可怕)快了一倍多。
我意识到比较两种不同语言的性能并不是 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 秒。
这些时间考虑了二进制文件的整个运行时间,包括输出到标准输出,这显然占了一些开销,但我确实通过重新编译和运行以及查看 GC对 GHC 二进制文件进行了一些分析统计数据。我并没有真正理解我所看到的所有内容,但似乎我的 GC 并没有失控,尽管可能会得到一点控制(5%,生产力在 ~93% 用户,~85 % total elapsed) 并且大部分生产时间都花在了 function上,当我写它的时候我知道这会很慢,但是对我来说如何去清理它并不是很明显。我意识到我在使用列表时可能会受到惩罚,无论是在常量-prof -auto-all
+RTS -p
+RTS -s
iterateRK
cons
ing 以及将结果转储到标准输出的懒惰。
我到底做错了什么?我悲惨地不知道我可以用来清理哪些库函数或 Monadic 魔法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;
}