LispDay 15

elispでgoroutine的なものを作ろうとした

More than 3 years have passed since last update.


el-routine.el の紹介

Emacsでgoのgroutineのようなものを作ってみました。

まだ実験段階ではありますが、以下のようなコード片と引数を渡すと、別プロセスを立ち上げて実行します。結果はdeferredで返ってきます。

(deferred:pp

(elcc:routine-d '(lambda (x y) (+ x y)) 1 2))

elcc:routine-d で渡したタスク(コード片と引数)は、キューに積まれて、予め決められたワーカープロセスの数で処理されます。(今のところ、 elcc:init-processを実行した時点のelcc:process-max-number で決まりますが、APIがダサいので今後変わると思います)

elcc:demoでは、以下のようにフィボナッチを並列に計算してみました。

(defun elcc:demo ()

(interactive)
(lexical-let
((code '(lambda (x)
(let* ((f (lambda (f xx)
(if (> 2 xx) 1
(+ (funcall f f (- xx 1)) (funcall f f (- xx 2)))))))
(funcall f f x))))
(begin-time (float-time)))
(deferred:nextc
(deferred:parallel-list
(loop for i from 1 below 30
collect
(elcc:routine-deferred-internal code (list 30))))
(lambda (xs)
(message "Result: (time %s) %S " (- (float-time) begin-time) xs)))))

手元の Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz の 4 core でいくつかワーカーの数を変えて動かしてみました。

worker num
time(second)

1
16.890

2
10.141

4
10.548

一応、1個よりは2個が速いのですが、2個で打ち止めになってしまいました。


考察など

まず、モチベーションとしては、Emacsでスレッドぽいものが出来ないかということからでした。

Emacsでの並行処理についてはEmacsWiki:ConcurrentEmacsにまとめられていますが、なかなかむつかしそうです。

半年に一度ぐらい、emacs-develで、Emacs上のthreadについての話題が出て盛り上がるのですが、個人的にはそんなに簡単にはうまく行かないのではないかと思っています。

Emacsでの別プロセスでの実行の簡単なパッケージとしては、emacs-asyncが有名です。

これは、別プロセスのEmacsを毎回立ち上げてS式を実行させるもので、独立した単純な処理ではそれなりにうまく動きます。しかしながら、複数の処理をつなげるような場合にはかなり厳しいです。

非同期処理だと、拙作の [deferred.el / concurrent.el があります。deferred.el でemacs-asyncと似たようなことが出来ます。また、epc.el というRPCスタックもあり、これを使えばプロセス間で自由に通信が出来ます。

今回は、これらを組み合わせて、タスクのキューを作り、とりあえず形だけ動かしてみました。

シングルスレッドのモデルの場合は、このようにプロセス間でメッセージを使った通信で並行処理を行うといいのではないかと思っています。

今後の方針として、requireや関数の定義を渡したり、goのようにchannelで通信したり、Emacsらしくバッファをプロセス間で共有する仕組みなどを作ると便利なのかなとか思っていますが、自分でもこんなものが本当に必要なのだろうかと疑問に思っているため、もしかしたら続かないかも知れません。

以上でした。