Posted 2016-08-10 15:50:11 GMT

前回は、Zebraパズルをお題に各種埋め込みPrologがどんなものかを試してみましたが、今回は、たらいまわしの竹内関数で有名なtakです。

どうしてtakかというとAZ-Prologのベンチにあったので、AZ-Prologと比較するには都合が良いという理由です。

Takとは

正式な竹内関数はtakとは微妙に違いますが、Lispのベンチで有名なGabrielベンチで広まってしまったのは、マッカーシー先生の憶え違いのtakの方でした。

以来ベンチではこちらが良く使われます。

比較する埋め込みPrologについて

今回も比較する埋め込みPrologは、

Allegro Prolog

PAIPROLOG

Uranus

です。

ベンチ

CL処理系: Allegro CL 8.2 Linux 64bit版

CPU: Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz

メモリ32GiB

AZ-Prolog

タイム: 1.050 sec

今回は、AZ-Prologを基準にしてみたいと思うので、まずAZ-Prologからですが、 takを100回繰り返しています。

takの引数は、tak(18,12,6)です。iが付いていますがintegerのiでしょうか。

※interpreterのiでした。やたらcallが付いているのはそのためだったんですね。 callなしのtakもベンチにあったのを見逃していましたので、追記し、AZ-Prologとの比較表もtakを基準としたものに修正します。

tak(100) : 0.140 Sec 0.370 Sec 1.250 Sec

最速のCへの変換版で0.140です。

参考インタプリタ版:

---Module Name(Iterate)---- C-Code ByteCode Interpreter -----------------------------+----------+----------+----------- itak(100) : 1.050 Sec 1.310 Sec 1.500 Sec

Uranus

タイム: 26.5536 sec

define tak ( ( *x *y *z *a ) ( <= *x *y ) ( cut ) ( = *z *a ) ) ( *x *y *z *a ) 1- *x *x1 ( tak *x1 *y *z *a1 ) 1- *y *y1 ( tak *y1 *z *x *a2 ) 1- *z *z1 ( tak *z1 *x *y *a3 ) ( tak *a1 *a2 *a3 *a ) tak

Uranusですが、上記のような定義です。

(dotimes (i 100) (result '(tak 18 12 6 *ans))) ;=> nil #|------------------------------------------------------------| ; cpu time (non-gc) 22.020000 sec user, 0.000000 sec system ; cpu time (gc) 4.530000 sec user, 0.000000 sec system ; cpu time (total) 26.550000 sec user, 0.000000 sec system ; real time 26.553695 sec ; space allocation: ; 354,620,051 cons cells, 2,900,592,192 other bytes, 0 static bytes ; x86_64 |------------------------------------------------------------|#

Allegro Prolog

タイム: N/A

( <-- ( tak ?x ?y ?z ?a ) ( lispp* ( <= ?x ?y ) ) ! ( = ?z ?a ) ) - ( tak ?x ?y ?z ?a ) 1- ?x is ?x1 ( tak ?x1 ?y ?z ?a1 ) 1- ?y is ?y1 ( tak ?y1 ?z ?x ?a2 ) 1- ?z is ?z1 ( tak ?z1 ?x ?y ?a3 ) ( tak ?a1 ?a2 ?a3 ?a )

こんな感じに直訳で書きましたが、スタックオーバーフローで完走できません。

Prologのスタックを伸ばす方法もありますが、ちょっと伸ばしてもLisp処理系のスタックオーバーフローになってしまいます。コンパイルしてみても駄目。

PAIProlog

タイム: N/A

Allegro Prologと同じ定義ですが、同じくスタックオーバーフロー

Allegro Prolog で Lisp側のtakを呼ぶ

タイム: 0.050 sec

なんとなく姑息な気がしますが、takみたいなものは、すべからくLisp側で定義すべし、ということなのかもしれないので、そういう定義を書いてみました。

( x y z ) if ( <= x y ) z 1- x taky z 1- y takz x 1- z takx y tak defun tak (<-- (tak ?x ?y ?z ?a) (is ?a (tak ?x ?y ?z))) (dotimes (i 100) (prolog (tak 18 12 6 ?a) (lisp (return-from prolog ?a))))

今度は、飛び抜けて速いですね。

PAIProlog で Lisp側のtakを呼ぶ

タイム: 0.140 sec

Allegro Prologと同じですが、ユーティリティ述語が少ない分、若干違っています。

( <-- ( tak ?x ?y ?z ?a ) ( lisp ?a ( tak ?x ?y ?z ) ) ) (dotimes (i 100) (let (ans) (prolog (tak 18 12 6 ?a) (lisp (setf ans ?a))) ans))

Allegro Prologより若干遅い位です。

といってもtakの速さは同じですが。

Uranus で Lisp側のtakを呼ぶ

タイム: 0.054 sec

dotimes ( i 100 ) ( result ' ( tak 18 12 6 *ans ) )

Uranusは、Lisp側で関数を定義してやれば、それに引数が増えた形式で、Uranus側から呼べるので、特に追加の定義は必要ありません。

順位とタイム

順位 処理系 タイム(秒) AZ-Prolog比 1 Allegro Prolog←Lisp 0.050 1/2.8 2 Uranus←Lisp 0.054 1/2.6 3 PAIProlog←Lisp 0.140 1 3 AZ-Prolog 0.140 1 5 SWI-Prolog 0.781 5.5 6 Uranus 26.5536 189 7 Allegro Prolog N/A N/A 8 PAIProlog N/A N/A

まとめ

埋め込み処理系は、それ自体、独立したものとは考えず、Prologが得意とするもの以外はLisp側に投げる、という使い方が吉なのでしょうか。

単体のProlog処理系であれば、Cを呼び出したりしそうなので、Lisp⇔Prologの行き来がシームレスで高速であれば、それで良いのかなあ、という気がしないでもないです。

しかし、Prologからコードを移植したりする場合を考えると、とりあえず愚直に書き直したものが動いてくれると嬉しいですねえ。

■

