unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* Elisp performance
@ 2009-07-29 12:50 Daniel Kraft
  2009-07-30  3:23 ` Ken Raeburn
                   ` (2 more replies)
  0 siblings, 3 replies; 23+ messages in thread
From: Daniel Kraft @ 2009-07-29 12:50 UTC (permalink / raw)
  To: guile-devel; +Cc: Andy Wingo

[-- Attachment #1: Type: text/plain, Size: 3836 bytes --]

Hi all,

as promised, here are some performance results for the current elisp 
compiler.  BTW, it supports now to disable checks for void-value on 
variable access via compiler options as well as the 
lexical-let/lexical-let* constructs that should mimic the ones from 
emacs' cl package (but in emacs they degrade performance, in Guile they 
improve it).

Lambda arguments are still always dynamically bound, which is quite a 
pity as it inhibits tail-call optimization; I'll work on a compiler 
option to always bind certain or all symbols lexically, including lambda 
arguments to counter this (and there's also an emacs branch doing this, 
so we're ultimatly even trying to be compatible...).

For the timings, I used a simple prime sieve implemented imperatively 
with while and set's, because the recursive one would put elisp without 
tail-calls at a disadvantage (and because lexical binding does not yet 
work for lambda arguments); the code is attached.  Both Scheme and Elisp 
version are identical with just syntax changed (set! -> setq, define -> 
defun, begin -> progn).  Additionally, for lexical let all let's were 
replaced by lexical-let's in the elisp version.  Here are the results:

Iterative prime sieve, (length (find-primes-to 5000)):
   Scheme: 0.42s
   Elisp, no void checks, lexical let: 3.40s
   Elisp, no void checks, dynamic let: 4.43s
   Elisp, void checks, dynamic let: 5.12s
   Elisp, void checks, lexical let: 4.06s

So it apparent that both disabling void checks and using lexical let's 
instead of the standard dynamic ones improve performance notably. 
However, were quite out of reach of the Scheme version.

My conjecture is that the remaining bottle-neck are the primitive calls, 
as they are still resolved using the dynamic-binding and fluid 
mechanisms; so maybe it is really a good idea to change function 
bindings to just global ones without dynamic scoping, and implement 
flet/flet* with dynamic-wind.  In contrast to variables, for functions 
access performance is a lot more important than let performance, I 
guess, so this might be the way to go.  What do you think?

As another test I implemented just a small loop, again both in Scheme 
and Elisp with identical code (also attached).  Here are the timing results:

Tight loop, (test 1000000):
   Scheme: 0.72s
   Elisp, no void checks, lexical let: 2.33s
   Elisp, no void checks, lexical let, guile-ref: 1.29s
   Elisp, no void checks, lexical let, guile-primitive: 0.92s

In the guile-ref and guile-primitive runs, I replaced both primitives (< 
and 1+) using the extension constructs (guile-ref (guile) </1+), which 
is like (@ (guile) </1+) in Scheme, or (guile-primitive </1+) which 
builds a (make-primitive-ref) in TreeIL.  The guile-ref timing is what 
we would also get if we changed function bindings like I suggested above.

Obviously, it would help a lot to do so.  On the other hand, switching 
to primitive-ref's would help even more, but I fear we can not easily do 
so, because we can not know if a symbol targets a primitive or was 
rebound at compile time...  BTW, a quick test with Scheme:

scheme@(guile-user)> (set! + (lambda (a b) 42))
scheme@(guile-user)> +
#<program b700a790 at <unknown port>:0:8 (a b)>
scheme@(guile-user)> (+ 1 2)
3
scheme@(guile-user)> (let ((+ (lambda (a b) 42)))
... (+ 1 2))
42

So it seems that the Scheme compiler just ignores this possibility... 
Is (set! + ...) and expecting (+ 1 2) to change invalid, or should this 
strictly be regarded as a bug?

In any case, because of dynamic scoping and the expected behaviour of 
flet to change possibly primitives during its extent, I think we can't 
do anything like that for Elisp (except providing guile-primitive for 
hand-optimizing such calls).

Yours,
Daniel

-- 
Done:  Arc-Bar-Cav-Ran-Rog-Sam-Tou-Val-Wiz
To go: Hea-Kni-Mon-Pri

[-- Attachment #2: iterate_sieve.el --]
[-- Type: text/plain, Size: 1098 bytes --]

; Imperative prime sieve in Elisp.

(defun sieve-out (pivot number-list)
  (lexical-let ((i number-list)
        (result '())
        (result-tail '()))
    (while (not (null i))
      (if (not (zerop (% (car i) pivot)))
        (if (consp result)
          (progn
            (setcdr result-tail (cons (car i) '()))
            (setq result-tail (cdr result-tail)))
          (progn
            (setq result (cons (car i) '()))
            (setq result-tail result))))
      (setq i (cdr i)))
    result))

(defun sieve (number-list)
  (lexical-let ((i number-list)
        (result '())
        (result-tail '()))
    (while (not (null i))
      (if (consp result)
        (progn
          (setcdr result-tail (cons (car i) '()))
          (setq result-tail (cdr result-tail)))
        (progn
          (setq result (list (car i)))
          (setq result-tail result)))
      (setq i (sieve-out (car i) i)))
    result))

(defun find-primes-to (to)
  (lexical-let ((numbers '())
        (i to))
    (while (> i 2)
      (setq numbers (cons i numbers))
      (setq i (1- i)))
    (sieve numbers)))

[-- Attachment #3: iterate_sieve.scm --]
[-- Type: text/plain, Size: 1089 bytes --]

; Imperative prime sieve in Scheme.

(define (sieve-out pivot number-list)
  (let ((i number-list)
        (result '())
        (result-tail '()))
    (while (not (null? i))
      (if (not (zero? (modulo (car i) pivot)))
        (if (pair? result)
          (begin
            (set-cdr! result-tail (cons (car i) '()))
            (set! result-tail (cdr result-tail)))
          (begin
            (set! result (list (car i)))
            (set! result-tail result))))
      (set! i (cdr i)))
    result))

(define (sieve number-list)
  (let ((i number-list)
        (result '())
        (result-tail '()))
    (while (not (null? i))
      (if (pair? result)
        (begin
          (set-cdr! result-tail (cons (car i) '()))
          (set! result-tail (cdr result-tail)))
        (begin
          (set! result (cons (car i) '()))
          (set! result-tail result)))
      (set! i (sieve-out (car i) i)))
    result))

(define (find-primes-to to)
  (let ((numbers '())
        (i to))
    (while (> i 2)
      (set! numbers (cons i numbers))
      (set! i (1- i)))
    (sieve numbers)))

[-- Attachment #4: tight_loop.el --]
[-- Type: text/plain, Size: 151 bytes --]

; Just a tight loop in Elisp

(defun test (to)
  (lexical-let ((i 0))
    (while ((guile-primitive <) i to)
      (setq i ((guile-primitive 1+) i)))))

[-- Attachment #5: tight_loop.scm --]
[-- Type: text/plain, Size: 110 bytes --]

; Just a tight loop in Scheme.

(define (test to)
  (let ((i 0))
    (while (< i to)
      (set! i (1+ i)))))

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2009-08-08 22:15 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-07-29 12:50 Elisp performance Daniel Kraft
2009-07-30  3:23 ` Ken Raeburn
2009-07-31  5:15   ` Daniel Kraft
2009-08-04 15:51   ` Andy Wingo
2009-07-30 20:18 ` Neil Jerram
2009-07-30 23:54   ` Ken Raeburn
2009-07-31  6:09     ` Daniel Kraft
2009-08-04 10:26       ` Andy Wingo
2009-08-04 10:26     ` Andy Wingo
2009-07-31  6:02   ` Daniel Kraft
2009-07-31  9:59     ` Ken Raeburn
2009-07-31 15:14       ` Daniel Kraft
2009-08-04 11:14         ` Andy Wingo
2009-08-04 11:00     ` Andy Wingo
2009-08-08 22:15       ` Ludovic Courtès
2009-08-04 10:17   ` Andy Wingo
2009-08-04 10:54     ` Daniel Kraft
2009-08-04 15:58     ` Ken Raeburn
2009-08-04 15:47 ` Andy Wingo
2009-08-04 16:12   ` Ken Raeburn
2009-08-04 19:28     ` Andy Wingo
2009-08-04 16:17   ` Daniel Kraft
2009-08-04 19:25     ` Andy Wingo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).