| This test was
conducted to determine the relative efficiency and code
size of hand-coded Lisp, O'Caml and Qi Turbo-E using the
benchmark challenge provided by Jon Harrop. Dr
Harrop also provided the Lisp source written by Andre
Thieme, Nathan Forster and Pascal Constanza. Thanks to many people who corrected these
tests and made them possible.
Turbo-E is an unreleased version
of Qi which factorises its pattern-matching.
Jon Harrop Challenge Problem
Posted by Dr Harrop
http://groups.google.co.uk/group/comp.lang.lisp/browse_thread/thread/...
The problem is to simplify
symbolic expressions by applying the following rewrite
rules from the leaves up:
rational n + rational m ->
rational(n + m)
rational n * rational m -> rational(n * m)
symbol x -> symbol x
0+f -> f
f+0 -> f
0*f -> 0
f*0 -> 0
1*f -> f
f*1 -> f
a+(b+c) -> (a+b)+c
a*(b*c) -> (a*b)*c
Thanks to
Nathan Froyd
Andre Thieme
Pascal Constanza
Dan Bensen
for providing code
Jon Harrop (for both providing code and providing
motivation to develop Turbo-E)
Marcus Breing (for correcting my first run)
The Results
The benchmark was to simplify [*
x [+ [+ [* 12 0] [+ 23 8]] y]]. 10^7 iterations were used
as the benchmark. A 2.6GHz, 500Mb RAM machine was used to
drive the test. Windows XP was the OS. Times measure only
processor time (not real time) and are rounded to 1/10th
of a second.
A 2.6GHz, 500Mb RAM machine was
used to drive the test. Windows XP was the OS. Times
measure only processor time (not real time) and are
rounded to 1/10th of a second.
Key:
| SBCL
1.0 |
|
| Qi
Turbo-E under SBCL 1.0 |
|
| O'Caml
3.09.3 |
|
|
|
Mark
|
Nathan
|
Andre
|
Pascal
|
Dan
|
Jon
|
time
|
3.6s
|
3.4s
|
15s
|
8.2s
|
5.1s
|
2.0s
|
loc
|
15
|
39
|
23
|
24
|
34
|
15
|
| time vs OCaml |
1.8x |
1.7x |
7.5x |
4.1x |
2.6x |
1x |
The timer used in
Lisp was
(defun tt (N) (gc) (time (test
N)))
(defun test (N)
(cond ((zerop N) 0)
(t (simplify *expr*) (test (1- N)))))
took .2 s for 10^7 iterations
with (simplify *expr*) removed. All timings were adjusted
by deducting .2s from each.
I could run Jon 's solution in
interpreted mode, but could not get my O'Caml compiler to
work. Based on the ratio of interpreted time to his
compiled time, I could construct a projected time.
The Solutions
Here are the solutions; you can
download the code here.
Language: Qi
Author: Mark Tarver
Length: 15 lines
(define simplify
[Op A B] -> (s Op (simplify A) (simplify B))
A -> A)
(define s
+ M N -> (+ M N) where (and
(number? M) (number? N))
+ 0 F -> F
+ F 0 -> F
+ A [+ B C] -> (simplify [+ [+ A B] C])
* M N -> (* M N) where (and
(number? M) (number? N))
* 0 F -> 0
* F 0 -> 0
* F 1 -> F
* 1 F -> F
* A [* B C] -> (simplify [* [* A B] C])
Op A B -> [Op A B])
* Note I have used prefix
notation for this problem though Dr Harrop's formulation
uses infix. The first Lisp solutions followed prefix and
so I chose to make my program comparable.*
The generated code is
(DEFUN simplify (V148)
(BLOCK NIL
(TAGBODY
(IF (CONSP V148)
(LET ((Cdr159 (CDR V148)))
(IF (CONSP Cdr159)
(LET ((Cdr158 (CDR Cdr159)))
(IF (CONSP Cdr158)
(IF (NULL (CDR Cdr158))
(RETURN
(s (CAR V148) (simplify (CAR Cdr159))
(simplify (CAR Cdr158))))
(GO tag154))
(GO tag154)))
(GO tag154))))
tag154
(RETURN V148))))
(DEFUN s (V149 V150 V151)
(BLOCK NIL
(TAGBODY
(IF (EQ '+ V149)
(IF (AND (NUMBERP V150) (NUMBERP V151))
(RETURN (THE NUMBER (+ V150 V151)))
(IF (EQL 0 V150) (RETURN V151)
(IF (EQL 0 V151) (RETURN V150)
(IF (CONSP V151)
(LET ((Cdr170 (CDR V151)))
(IF (EQ '+ (CAR V151))
(IF (CONSP Cdr170)
(LET ((Cdr169 (CDR Cdr170)))
(IF (CONSP Cdr169)
(IF (NULL (CDR Cdr169))
(RETURN
(simplify
(CONS '+
(CONS
(LIST '+ V150
(CAR Cdr170))
Cdr169))))
(GO tag160))
(GO tag160)))
(GO tag160))
(GO tag160)))
(GO tag160))))))
tag160
(TAGBODY
(IF (EQ '* V149)
(IF (AND (NUMBERP V150) (NUMBERP V151))
(RETURN (THE NUMBER (* V150 V151)))
(IF (EQL 0 V150) (RETURN 0)
(IF (EQL 0 V151) (RETURN 0)
(IF (EQL 1 V151) (RETURN V150)
(IF (EQL 1 V150) (RETURN V151)
(IF (CONSP V151)
(LET ((Cdr183 (CDR V151)))
(IF (EQ '* (CAR V151))
(IF (CONSP Cdr183)
(LET ((Cdr182 (CDR Cdr183)))
(IF (CONSP Cdr182)
(IF (NULL (CDR Cdr182))
(RETURN
(simplify
(CONS '*
(CONS
(LIST '* V150
(CAR
Cdr183))
Cdr182))))
(GO tag171))
(GO tag171)))
(GO tag171))
(GO tag171)))
(GO tag171))))))))
tag171
(RETURN (LIST V149 V150 V151))))))
Language: OCaml
Author: Jon Harrop
Length: 15 lines
let rec ( +: ) f g = match f, g
with
| `Int n, `Int m -> `Int (n +/ m)
| `Int (Int 0), e | e, `Int (Int 0) -> e
| f, `Add(g, h) -> f +: g +: h
| f, g -> `Add(f, g)
let rec ( *: ) f g = match f, g
with
| `Int n, `Int m -> `Int (n */ m)
| `Int (Int 0), e | e, `Int (Int 0) -> `Int
(Int 0)
| `Int (Int 1), e | e, `Int (Int 1) -> e
| f, `Mul(g, h) -> f *: g *: h
| f, g -> `Mul(f, g)
let rec simplify = function
| `Int _ | `Var _ as f -> f
| `Add (f, g) -> simplify f +: simplify g
| `Mul (f, g) -> simplify f *: simplify g
Language: Lisp
Author: Andre Thieme
Length: 23 lines
(defun simplify (a)
(if (atom a)
a
(destructuring-bind (op x y) a
(let* ((f (simplify x))
(g
(simplify y))
(nf (numberp f))
(ng (numberp g))
(+? (eq '+ op))
(*? (eq '* op)))
(cond
((and +? nf ng)
(+ f g))
((and +? nf
(zerop f)) g)
((and +? ng
(zerop g)) f)
((and (listp g)
(eq op (first g)))
(destructuring-bind (op2 u v) g
(simplify `(,op (,op ,f ,u) ,v))))
((and *? nf ng)
(* f g))
((and *? (or
(and nf (zerop f))
(and ng (zerop g)))) 0)
((and *? nf (=
1 f)) g)
((and *? ng (=
1 g)) f)
(t
`(,op ,f
,g)))))))
Language: Lisp
Author: Nathan Froyd
Length: 39 lines
(defun
simplify-no-redundant-checks (xexpr)
(if (atom xexpr)
xexpr
(let ((op (first xexpr))
(z (second
xexpr))
(y (third
xexpr)))
(let* ((f
(simplify-no-redundant-checks z))
(g
(simplify-no-redundant-checks y))
(nf (numberp f))
(ng (numberp g)))
(tagbody
START
(if (eq
'+ op) (go OPTIMIZE-PLUS) (go TEST-MULTIPLY))
OPTIMIZE-PLUS
(when
(and nf ng) (return-from simplify-no-redundant-checks (+
f g)))
TEST-PLUS-ZEROS
(when
(eql f 0) (return-from simplify-no-redundant-checks g))
(when
(eql g 0) (return-from simplify-no-redundant-checks f))
(go
REARRANGE-EXPR)
TEST-MULTIPLY
(unless
(eq '* op) (go REARRANGE-EXPR))
OPTIMIZE-MULTIPLY
(when
(and nf ng) (return-from simplify-no-redundant-checks (*
f g)))
TEST-MULTIPLY-ZEROS-AND-ONES
(when (or
(eql f 0) (eql g 0)) (return-from
simplify-no-redundant-checks 0))
(when
(eql f 1) (return-from simplify-no-redundant-checks g))
(when
(eql g 1) (return-from simplify-no-redundant-checks f))
REARRANGE-EXPR
(when
(and (listp g) (eq op (first g)))
(let ((op2 (first g))
(u (second g))
(v (third g)))
(declare (ignore op2))
(return-from simplify-no-redundant-checks
(simplify-no-redundant-checks (list op (list
op f u) v)))))
MAYBE-CONS-EXPR
(if (and
(eq f z) (eq g y))
(return-from simplify-no-redundant-checks xexpr)
(return-from simplify-no-redundant-checks (list op
f g))))))))
Language: Lisp
Author: Pascal Constanza
Length: 25 lines
(defstruct add x y)
(defstruct mul x y)
(defgeneric simplify-add (x y)
(:method ((x number) (y number)) (+ x y))
(:method ((x (eql 0)) y) y)
(:method (x (y (eql 0))) x)
(:method (x (y add))
(simplify-add (simplify-add x (add-x y))
(add-y y)))
(:method (x y) (make-add :x x :y y)))
(defgeneric simplify-mul (x y)
(:method ((x number) (y number)) (* x
y))
(:method ((x (eql 0)) y) 0)
(:method (x (y (eql 0))) 0)
(:method ((x (eql 1)) y) y)
(:method (x (y (eql 1))) x)
(:method (x (y mul))
(simplify-mul (simplify-mul x (mul-x y))
(mul-y y)))
(:method (x y) (make-mul :x x :y y)))
(defgeneric simplify (exp)
(:method (exp) exp)
(:method ((exp add))
(simplify-add (simplify (add-x exp))
(simplify (add-y exp))))
(:method ((exp mul))
(simplify-mul (simplify (mul-x exp))
(simplify (mul-y exp)))))
Language: Lisp
Author: Dan Bensen
Length: 34 lines
(defmacro defop (func op
op-symbl ident zero-case)
(let ((e1 (gensym "E1"))
(e2 (gensym
"E2")))
`(defun ,func (,e1 ,e2)
(declare (optimize (speed 3)))
,(delete :no-case
`(case ,e1
,zero-case
(,ident ,e2)
(t ,(delete
:no-case
`(case ,e2
,zero-case
(,ident ,e1)
(t (cond
((and (rationalp ,e1)
(rationalp ,e2))
(,op ,e1 ,e2))
((atom ,e2)
(list ,e1 ,op-symbl ,e2))
(t (case
(cadr ,e2)
(,op-symbl (,func (,func ,e1 (car
,e2))
(caddr ,e2)))
(t (list ,e1 ,op-symbl
,e2))))))))))))))
(defop apply+ + '+ 0 :no-case)
(defop apply* * '* 1 ( 0 0 ))
(defun simplify (expr)
(if (atom expr)
expr
(let ((e1 (simplify (car
expr)))
(e2 (simplify
(caddr expr))))
(case (cadr expr)
('+
(apply+ e1 e2))
('*
(apply* e1 e2))))))
Mark
Copyright
(c) 2007, Mark Tarver
|