読者です 読者をやめる 読者になる 読者になる

(-> % read write unlearn)

All opinions expressed are solely my own and do not express the views or opinions of my employer.

Clojureをスクリプトとして使う:inlein

clojure leiningen

inleinClojureスクリプトとして使用することを可能にしてくれるツールです。leiningen経由でClojureのコードを実行すると起動が遅いという問題があります。inleineはこの問題を解決(緩和)してくれます。 ちなみに、「スクリプト」と言っているのは「ShellScript」とか「インストール・スクリプト」とか言う時のスクリプトの意味で、ClojureScriptのことではないです。

以降は、inleinのインストールと使い方を簡単に説明し、leiningenleinで速度を簡単に比べます。インストールや使い方については、良ければREADMEの和訳もあわせてご参照ください。

Install

前提

  • JavaJDKversion 7以上が入っていること。java -versionとかで確認してください。

手順

  1. ここから、リリースされている最新のinleinプログラムをDLします。
  2. DLしたファイルを$PATH上に配置。(例えば、~/binとか)
  3. chmod 755 ~/bin/inleinのような感じで実行権限を与えればOKです。

より詳しくは、リポジトリのREADMEを参照してください。

How to use

inleinコマンドは、第一引数で指定されたClojureスクリプトを実行します。

inlein foo.clj

実際には、スクリプトの実行処理は別のデーモン・プロセスに移譲されます。inleinを最初に実行するときには自動でデーモンの起動も行われ、2回目以降はデーモンの起動処理はスキップされます。*1

リポジトリを見ると、inleinのエントリポイントとなるclientJavaコードと、デーモンのClojureコードにディレクトリが分かれていることが分かります。

以下のようにして明示的にデーモンを起動/停止することもできます。

  • inlein --start-daemon
  • inlein --shutdown-daemon
  • inlein --restart-daemon
  • inlein --ping: 起動していればPONGが返ってくるので、デーモンのステータス確認に使う用途だと思われます。

サブコマンド(Taskと呼ぶ)の一覧は引数なしのinleininlein --helpで見れます。

直接inleinコマンドを叩いてスクリプトを実行することもできますが、実際には、スクリプト先頭にシバン(#!/bin/shとかあれ)を書いてスクリプトを直接叩いて実行するほうが想定される使い方だと思います。

以下にinlein用のClojureスクリプトの例を書きます(READMEのまんまですが)。

$ cat ./primes.clj
#!/usr/bin/env inlein
; ↑ シバン行。
; *nix環境では、これが書いてあってスクリプトに実行権限が与えられていれば、そのファイルを直接実行できる。

; 依存ライブラリ一覧。クオートしてる以外はleiningenと同様の形式。
'{:dependencies [[org.clojure/clojure "1.8.0"]
                 [com.hypirion/primes "0.2.1"]]}

; ここからが処理内容。nsは無くてもよい。

(require '[com.hypirion.primes :as p])

; -main関数とかいらなくて、いきなり地の文に処理を書いていける。

(when-not (first *command-line-args*)
  (println "Usage:" (System/getProperty "$0") "prime-number")  ; $0でファイル名が参照できる
  (System/exit 1))

(-> (first *command-line-args*)
    (Long/parseLong)
    (p/get)
    println)

実際にどれくらい速くなるのか、timeコマンドで簡単に測って比較してみます。環境は MacBookPro の El Captain です。

まずは、inleinで実行してみます。

$ time ./primes.clj 3
7
./primes.clj 3  1.18s user 0.19s system 115% cpu 1.187 total
$ time ./primes.clj 3
7
./primes.clj 3  1.18s user 0.17s system 117% cpu 1.155 total
$ time ./primes.clj 3
7
./primes.clj 3  1.19s user 0.18s system 117% cpu 1.175 total

次に、leiningenでプロジェクトを作って実行速度を計測してみます。まず、コードを作ります。

$ lein new inlein-test
$ cd ./inlein-test
$ open ./src/inlein_test/core.clj
$ cat ./src/inlein_test/core.clj
(ns inlein-test.core
 (:gen-class))

(require '[com.hypirion.primes :as p])

(defn -main [& arg]
  (when-not (first *command-line-args*)
    (println "Usage:" (System/getProperty "$0") "prime-number")
    (System/exit 1))

  (-> (first *command-line-args*)
      (Long/parseLong)
      (p/get)
      println))
$ open ./project.clj 
$ cat ./project.clj
(defproject inlein-test "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.8.0"]
                 [com.hypirion/primes "0.2.1"]]
  :main inlein-test.core)

そして、同様に3回計測してみます。

$ time lein run 3
7
lein run 3  2.60s user 0.31s system 111% cpu 2.608 total
$ time lein run 3
7
lein run 3  2.62s user 0.31s system 110% cpu 2.638 total
$ time lein run 3
7
lein run 3  2.58s user 0.30s system 111% cpu 2.589 total

inleinのほうが倍以上速い、でいいのかな。体感的にあんまり変わらないんだが。。

ちなみに、inleinには直接javaから実行するコマンドを生成する機能があります。inlein --sh-cmd <script-name> <args>...です。

$ inlein --sh-cmd ./primes.clj 3
java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp /Users/xxxxx/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:/Users/xxxxx/.m2/repository/com/hypirion/primes/0.2.1/primes-0.2.1.jar '-D$0=./primes.clj' clojure.main ./primes.clj 3

また同様に3回計測してみます。

$ time java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp /Users/xxxxx/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:/Users/xxxxx/.m2/repository/com/hypirion/primes/0.2.1/primes-0.2.1.jar '-D$0=./primes.clj' clojure.main ./primes.clj 3
7
java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp  '-D$0=./primes.clj'   0.90s user 0.13s system 114% cpu 0.900 total
$ time java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp /Users/xxxxx/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:/Users/xxxxx/.m2/repository/com/hypirion/primes/0.2.1/primes-0.2.1.jar '-D$0=./primes.clj' clojure.main ./primes.clj 3
7
java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp  '-D$0=./primes.clj'   0.90s user 0.14s system 115% cpu 0.892 total
$ time java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp /Users/xxxxx/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:/Users/xxxxx/.m2/repository/com/hypirion/primes/0.2.1/primes-0.2.1.jar '-D$0=./primes.clj' clojure.main ./primes.clj 3
7
java -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -cp  '-D$0=./primes.clj'   0.90s user 0.13s system 115% cpu 0.898 total

生成したjavaコマンドで実行するとさらに速くなりました。inleinが噛まなくなるので当たり前かもしれないですね。

結果をまとめると、

コマンド 1回目 2回目 3回目 平均
lein run 2.60s 2.62s 2.58s 2.60s
inlein 1.18s 1.18s 1.19s 1.18s
java 0.90s 0.90s 0.90s 0.90s

という感じなんですが、inleinの良さは速さ以上に、スクリプト的に記述できるところだと思います。具体的には以下の特徴です。

  • -main関数とか作らなくて良い。
  • 依存ライブラリをスクリプト中に含められる。
    → つまり、依存ライブラリがあっても1ファイルで完結させられる。
  • 依存ライブラリの記述がleiningenのproject.cljとほぼ一緒。
  • 実行中のスクリプト・ファイル名を、システム・プロパティの $0 で参照できるようになる。

速度の比較の仕方について、おかしい所があったら教えて頂けると幸いです。

*1:停止していれば起動されます。