こんなコード。
main = print (tarai 20 10 5)
tarai :: Int -> Int -> Int -> Int
tarai x y z = if x <= y then y
else tarai (tarai (x-1) y z)
(tarai (y-1) z x)
(tarai (z-1) x y)
で実行時間は、Java コードでは約13秒かかっているのに対し、遅延評価によって必要最小限の計算しかやらない Haskell では 0.002 秒と、6千倍以上速くなっている。今回は、これを Java 8で試してみたい。
まず、上記 Haskell コードをそっくり置き換えた、オーソドックスなスタイルのタライ回し。
static int tarai(int x, int y, int z) {
return x <= y ? y : tarai(tarai(x - 1, y, z),
tarai(y - 1, z, x),
tarai(z - 1, x, y));
}
Haskell と違って、不要な計算も全部評価してしまう。うちのマシンでは、だいたい4秒かかった。以下が遅延評価的な手法を取り入れたバージョン。
static IntSupplier taraiLazy(int x, int y, int z) {
return tarai(() -> x, () -> y, () -> z);
}
static IntSupplier tarai(
IntSupplier x, IntSupplier y, IntSupplier z) {
return () -> {
int yv = y.getAsInt();
return x.getAsInt() <= yv
? yv
: tarai(tarai(() -> x.getAsInt() - 1, y, z),
tarai(() -> y.getAsInt() - 1, z, x),
tarai(() -> z.getAsInt() - 1, x, y)).getAsInt();
};
}
ここでは Java 8 の IntSupplier を使ってみた。これは、保持している値がプリミティブの int である、Supplier の特殊な形のクラス。
こんな感じで比較してみた。
public static void main(final String[] args) {
measureTimeElapsed(() -> tarai(20, 10, 5));
measureTimeElapsed(taraiLazy(20, 10, 5));
}
public static void measureTimeElapsed(IntSupplier s) {
long start = System.currentTimeMillis();
System.out.printf("計算結果:%d, 経過時間:%.3f秒%n",
s.getAsInt(),
(System.currentTimeMillis() - start) / 1000.0);
}
結果は以下
[java] 計算結果:20, 経過時間:4.389秒 [java] 計算結果:20, 経過時間:0.004秒6千倍とまでは行かないが、千倍くらいにはなっている。
まあ言語レベルで遅延評価になったわけでもなく、やろうと思えば Java 7 以前でもできた事ではあるけど、こういう遅延評価っぽいアプローチが少しだけ簡単になったのは、まあ良いことだとは思う。
0 件のコメント:
コメントを投稿