Page 1 of 1

(incf x) slower than (setf x (+ x 1))?

Posted: Fri Aug 19, 2011 5:46 pm
by sylwester
I'm testing in GNU CLISP. I havent tried sbcl or others (yet)
Without testing I have used incf and decf since I htough it would be a optimization. Today I benchmarked a little and found out incf performes 3 times slower than setf.
For me this doesn't make any sense. Why do we have incf/decf if not for speed?

Code: Select all

[7]> (time (dotimes (n 10000) (setf test (+ 1 test))))
Real time: 0.055758 sec.
Run time: 0.056004 sec.
Space: 400840 Bytes
GC: 1, GC time: 0.012 sec.
NIL
[8]> (time (dotimes (n 10000) (setf test (1+ test))))
Real time: 0.042886 sec.
Run time: 0.040002 sec.
Space: 400840 Bytes
NIL
[9]> (time (dotimes (n 10000) (incf test 1)))
Real time: 0.152384 sec.
Run time: 0.152009 sec.
Space: 3040840 Bytes
GC: 4, GC time: 0.028002 sec.
NIL

Re: (incf x) slower than (setf x (+ x 1))?

Posted: Fri Aug 19, 2011 6:07 pm
by Duke
sylwester wrote:I'm testing in GNU CLISP. I havent tried sbcl or others (yet)
Without testing I have used incf and decf since I htough it would be a optimization. Today I benchmarked a little and found out incf performes 3 times slower than setf.
For me this doesn't make any sense. Why do we have incf/decf if not for speed?[/code]
We don't have it for speed. INCF wraps SETF, to put it simply, and I'd have to figure that SETF compiles more or less directly to a MOV opcode anyway, so you're not going to top SETF for efficiency. Rather, INCF exists for convenience. I'm guessing that INCF is slower in CLISP because CLISP has to do so much consing... That is what it means by "space" isn't it?

You might find the output of these expressions interesting:

CL-USER> (let ((bork 0)) (disassemble #'(lambda () (setf bork (1+ bork)))))
CL-USER> (let ((bork 0)) (disassemble #'(lambda () (incf bork))))

Also: define-modify-macro

Re: (incf x) slower than (setf x (+ x 1))?

Posted: Fri Aug 19, 2011 6:26 pm
by saulgoode
sylwester wrote:Why do we have incf/decf if not for speed?
For code readability/conciseness?

Try writing the 'setf' equivalent to
  • (incf (aref myarray (incf indx)))

Re: (incf x) slower than (setf x (+ x 1))?

Posted: Sat Aug 20, 2011 12:16 am
by ramarren
sylwester wrote:For me this doesn't make any sense. Why do we have incf/decf if not for speed?
CLISP is an interpreter, and when executed from the REPL the expressions appears to be fully interpreted, that is, INCF is expanded inside the loop, which obviously costs time and space. If you wrap the operations in a function the time difference disappears, and you can see using DISASSEMBLE that they emit the same code.

Re: (incf x) slower than (setf x (+ x 1))?

Posted: Sun Aug 21, 2011 2:12 pm
by sylwester
So I was fooled by the interprenter repl. Since I prefer incf I'll continue with it and for the future I'll make compiled functions out of my benchmarks :)

Re: (incf x) slower than (setf x (+ x 1))?

Posted: Mon Aug 22, 2011 4:09 pm
by huangho
In compiled code, there is no difference:

Code: Select all

[8]> (defun f () (let ((test 0)) (dotimes (i 10000) (incf test))))
f
[9]> (defun g () (let ((test 0)) (dotimes (i 10000) (setf test (+ test 1)))))
g
[10]> (compile 'f)
f ;
nil ;
nil
[11]> (compile 'g)
g ;
nil ;
nil
[12]> (time (f))
Real time: 0.001149 sec.
Run time: 0.0 sec.
Space: 0 Bytes
nil
[13]> (time (f))
Real time: 0.001147 sec.
Run time: 0.004001 sec.
Space: 0 Bytes
nil
[14]> (time (g))
Real time: 0.00115 sec.
Run time: 0.004 sec.
Space: 0 Bytes
nil
[15]> (time (g))
Real time: 0.001142 sec.
Run time: 0.004 sec.
Space: 0 Bytes
nil
Indeed, (incf x) expands into a setf form (actually setq, but setf also expands into a setq):

Code: Select all

[16]> (macroexpand '(incf x))
(setq x (+ x 1)) ;
t
[17]> (macroexpand '(setf x (+ x 1)))
(setq x (+ x 1)) ;
t