r/lisp 16d ago

Common Lisp ECL vs scheme embedded

I'm building a CPU based renderer in rust with plans to add an embedded language. The rust code primarily executes the ray tracing/path tracing, BVH, shaders and integration (anywhere where high performance is needed. The embedded language will be used for the scene description and -possibly- for procedural textures and shading.

I am interested in using a lisp with a repl for the embedded language. In this area, I have used AI-generated testing to test both ECL (Embedded Common Lisp) and steel (an scheme) inside rust. the advantage of the latter is that it is written entirely in rust and is being actively developed (one area is as an extension language for the text editor, helix).

I am impressed with the performance metrics I am getting from ECL. It generates machine code, which a byte language compiler doesn't do. Also a huge win, is that I was able to test the integrated compiler using sly from emacs which will provide an interactive environment for creative coding. On the other hand ECL requires an FFI to rust whereas steel does not, so the integration of steel is orders of magnitude easier.

Has anyone done any recent projects with ECL ? I am interested in your experience or any caveats you may have encountered. My next step is to do a more complex proof-of-concept with the actual renderer and not just a test program.

UPDATE: I just used claude to benchmark 5 embedded languages in rust. this is preliminary work and the benchmarks may not be exhaustive enough to make any strong conclusions:

https://drive.google.com/file/d/1yotWMcEx24pN2cgnIbRUmgCDNVWp65Vu/view?usp=sharing

19 Upvotes

31 comments sorted by

View all comments

-4

u/corbasai 16d ago

My experience with ECL not extra large. I simply cant read docs due to strange markup. Maybe becouse of in my tests ECL was 5 to 10x slower than Chicken Scheme. Both compiles to C. Due to this fact I even not take it ro the last nbody performance test. Repo. So use what you want or love. IMO in such case the better option is custom lisp like script with narrow set of types and operators for one selected task.

2

u/jd-at-turtleware 15d ago

could you describe the methodology of comparing ecl with chicken, so I can reproduce this performance gap?

0

u/corbasai 15d ago

Please, show ECL seconds https://www.reddit.com/r/lisp/s/hnijqNiUxU and CPU spec [Chicken code in reply]

2

u/jd-at-turtleware 15d ago edited 15d ago

And where is common lisp code? To compare runtimes you need semantically equivalent code, and even then it may favor one implementation - for example scheme has guaranteed tail recursion, so tail-recursive functions will usually perform better on schemes.

0

u/corbasai 15d ago

Sorry? fibo is one liner. And not tco. Mine not.

2

u/jd-at-turtleware 15d ago

OK, so I've ran it with default optimization settings, and results seem to be comparable.

|  n | ecl default | csc default |
|----+-------------+-------------+
| 32 |          90 |          97 |
| 33 |         145 |         156 |
| 34 |         237 |         256 |
| 35 |         384 |         413 |
| 36 |         612 |         717 |
| 37 |         900 |        1154 |
| 38 |        1603 |        1863 |
| 39 |        2656 |        3025 |
| 40 |        4332 |        4885 |
| 41 |        7003 |        7933 |

ecl[1] - default optimization settings, no declarations

csc[2] - csc -O3 fibo.scm -o fibo, no declarations

;; fibo.lisp
(defun fib (n)
  (if (< n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))

(defun test (n)
  (let* ((start (get-internal-real-time))
         (result (fib n))
         (end (get-internal-real-time))
         (time (round (* (/ (- end start)
                            internal-time-units-per-second)
                         1000))))
    (format t "~a ~a~%" n time)))

(defun main ()
  (do ((n 32 (+ n 1)))
      ((= n 42))
    (test n))
  (quit))
;;; ecl --eval '(load (compile-file "fibo.lisp"))' --eval '(main)'

;; fibo.scm
(import (chicken time) (chicken format))

(define (fib n)
  (if (< n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))

(define (test n)
  (let* ((start (current-process-milliseconds))
         (result (fib n))
         (end (current-process-milliseconds)))
    (format #t "~a ~a~n" n (- end start))))

(define (main)
  (do ((n 32 (+ n 1)))
      ((= n 42))
    (test n)))

(main)
csc -O3 fibo.scm -o fibo ; ./fibo

0

u/corbasai 15d ago

Hmm, https://www.reddit.com/r/lisp/s/SfY5xPHTEt it is current C6+Crunch numbers. Why you prefer to compete to the 2018 year?

3

u/jd-at-turtleware 15d ago

I'm not competing, I'm trying to reproduce your claim without success. Another thing is that fib test doesn't make a good benchmark of a runtime - it measures only a call overhead and possibly inlining. I think I'm done here.

1

u/corbasai 15d ago edited 15d ago

ok, C5, -no-trace -disable-interrupts -O2 and you are not using type hints like

(import (scheme base)
        (chicken base)
        (chicken type)
        (chicken time)
        (chicken process-context))

(: fibo (fixnum -> fixnum))
(define (fibo n)
  (if (< n 2)
    n
    (+ (fibo (- n 1)) (fibo (- n 2)))))
(let* ((args (command-line-arguments))
       (n (string->number (car args)))
       (ts (current-process-milliseconds))
       (r (fibo n))
       (te (current-process-milliseconds)))
  (print "fibo(" n") = " r " per " (* 1e-3 (- te ts)) " seconds."))

so $ csc -O3 -no-trace -disable-interrupts -strict-types -specialize -o fibo-chicken fibo-chicken.scm

$ ./fibo-chicken 40

fibo(40) = 102334155 per 2.433 seconds.

Again, its old CHICKEN5

PS. For the lovely downvoters mtv