これを読んでたら Clojure でも同じこと書けるなーって思ったんだけど、全く同じことを書いても面白くないのでちょっと色々書いてみる。
こんな感じの素朴なコードを書く。
(ns fib.core
(:gen-class))
(defn fib [n]
(if (<= n 1)
n
(+ (fib (- n 1))
(fib (- n 2)))))
(defn -main []
(println (fib 40)))
project.clj はこんな感じ。
(defproject fib "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.7.0"]]
:main fib.core)
さて、 lein run
で実行してみましょう。
$ time lein run
102334155
lein run 33.51s user 3.17s system 118% cpu 30.965 total
34 秒くらいかかってます。遅いです。とはいえ、 lein run
で実行することなんて普通あり得ないので uberjar しましょう。
(defproject fib "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.7.0"]]
:main fib.core
:aot :all)
:aot
をたして lein uberjar
します。
$ time java -jar target/fib-0.1.0-SNAPSHOT-standalone.jar
102334155
java -jar target/fib-0.1.0-SNAPSHOT-standalone.jar 3.04s user 0.41s system 127% cpu 2.709 total
3 秒くらいになりましたが、まだ遅いですね。折角 Clojure 使ってるのに素朴な実装しているのが良くないので Clojure らしくよしなに書きましょう。
(ns fib.core
(:gen-class))
(defn l-fib* [a b]
(cons a (lazy-seq (l-fib* b (+' a b)))))
(defn l-fib [n]
(first (drop n (l-fib* 0 1))))
(defn -main []
(println (l-fib 40)))
$ time java -jar target/fib-0.1.0-SNAPSHOT-standalone.jar
102334155
java -jar target/fib-0.1.0-SNAPSHOT-standalone.jar 1.08s user 0.43s system 174% cpu 0.865 total
これでもまだ 1 秒かかりますが、これはもうほとんど JVM の起動時間みたいなもんなので drip を使って JVM の起動時間をカットしましょう。
$ time drip -jar target/fib-0.1.0-SNAPSHOT-standalone.jar
102334155
drip -jar target/fib-0.1.0-SNAPSHOT-standalone.jar 0.05s user 0.14s system 85% cpu 0.212 total
とても速くなりました。
もう一歩踏み込む
今回書いたプログラムは引数を実行時に受け取るわけではないので、実は実行時間に計算をする必要がありません。 Clojure にはマクロがあって、このようなプログラムであればコンパイル時間で解決できます。
やってみます。
(defmacro m-fib [n]
(l-fib n))
(m-fib 40)
これだけです。これをマクロ展開すると 102334155
という結果が出てきます。引数が充分にちいさいので実行時、コンパイル時のどちらで実行してもあまり時間差はありませんが、これが 1e5
くらいのサイズになると少し差が出てきます。
通常コンパイル時間に問題が解決出来ることはあまり多くないですが、実行時間で計算する必要がないものはコンパイル時間で計算しておくことによって最終的なアプリケーションの実行時間を短くすることが出来ます。
という当たり前の話を書いてみました。