LoginSignup
21
21

More than 5 years have passed since last update.

Clojure で書いたアプリケーションを速く実行する

Last updated at Posted at 2015-11-09

これを読んでたら 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 くらいのサイズになると少し差が出てきます。

通常コンパイル時間に問題が解決出来ることはあまり多くないですが、実行時間で計算する必要がないものはコンパイル時間で計算しておくことによって最終的なアプリケーションの実行時間を短くすることが出来ます。

という当たり前の話を書いてみました。

宣伝

【参加者募集】 Clojure ワークショップ(仮)

21
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
21