From: Don March on
A function I'm writing has a section like this:

(dolist (x long-list)
(do-some-stuff)
(push (if fn
(funcall fn x)
x)
new-list))

Which is fine, but if long-list is actually long, it's silly and
possibly slow to evaluate the "(if fn..." stuff every time. I came up
with the following 2.5 alternatives:

;; option 1 (probably implemented with macros to avoid duplicate code)
(if fn
(dolist (x long-list)
(do-some-stuff)
(push (funcall fn x) new-list))
(dolist (x long-list)
(do-some-stuff)
(push x new-list)))

;; option 2a
(let ((push-lambda (if fn
(lambda (x) (funcall fn x))
(lambda (x) x))))
(dolist (x long-list)
(do-some-stuff)
(push (funcall push-lambda x) new-list)))

;; option 2b
(let ((push-exp (if fn
'(funcall fn x)
'x)))
(dolist (x long-list)
(do-some-stuff)
(push (eval push-exp) new-list)))

Questions:
* Which of these is to be preferred? Am I missing another option?
* This is a pattern I'm running into a lot: almost duplicate chunks of
code with small changes, but those changes have to be determined at
run-time. Is there a more general way of dealing with this problem?
* Assuming either 2a or 2b had to be chosen, which is "best"? I'm
still unclear about when eval can be used without somebody saying it's
inappropriate.
From: Tamas K Papp on
On Wed, 25 Nov 2009 20:48:13 -0800, Don March wrote:

> A function I'm writing has a section like this:
>
> (dolist (x long-list)
> (do-some-stuff)
> (push (if fn
> (funcall fn x)
> x)
> new-list))
>
> Which is fine, but if long-list is actually long, it's silly and
> possibly slow to evaluate the "(if fn..." stuff every time. I came up

Not at all. It is not silly because it is easy to understand
(compared to the alternatives), and whether it is slow is something
that you would have to benchmark, but I doubt that it makes a big
difference.

> with the following 2.5 alternatives:
>
> ;; option 1 (probably implemented with macros to avoid duplicate code)
> (if fn
> (dolist (x long-list)
> (do-some-stuff)
> (push (funcall fn x) new-list))
> (dolist (x long-list)
> (do-some-stuff)
> (push x new-list)))
>
> ;; option 2a
> (let ((push-lambda (if fn
> (lambda (x) (funcall fn x))
> (lambda (x) x))))
> (dolist (x long-list)
> (do-some-stuff)
> (push (funcall push-lambda x) new-list)))
>
> ;; option 2b
> (let ((push-exp (if fn
> '(funcall fn x)
> 'x)))
> (dolist (x long-list)
> (do-some-stuff)
> (push (eval push-exp) new-list)))
>
> Questions:
> * Which of these is to be preferred? Am I missing another option? *
> This is a pattern I'm running into a lot: almost duplicate chunks of
> code with small changes, but those changes have to be determined at
> run-time. Is there a more general way of dealing with this problem? *
> Assuming either 2a or 2b had to be chosen, which is "best"? I'm still
> unclear about when eval can be used without somebody saying it's
> inappropriate.

Option 2b is ugly, eval is totally unnecesary and it is slow. So your
optimization made your code ugly and slow. There is a lesson in there
:-)

If you insist on doing these optimizations and they come up all the
time, use macros to generate something like 1. But I don't think it
is worth it. Benchmark/profile things first, this is unlikely to be
your bottleneck.

HTH,

Tamas


From: Frode V. Fjeld on
Don March <don.march(a)gmail.com> writes:

> A function I'm writing has a section like this:
>
> (dolist (x long-list)
> (do-some-stuff)
> (push (if fn
> (funcall fn x)
> x)
> new-list))
>
> Which is fine, but if long-list is actually long, it's silly and
> possibly slow to evaluate the "(if fn..." stuff every time.

You can bind fn to (or fn #'identity) and factor that out of the loop,
but I'd be somewhat surprised to find an implementation where that
version is any faster than your original version. I would expect the
original if-test to be compiled quite efficiently, with the main
performance hit coming from CPU pipeline stalls due to failed branch
prediction(s) -- which should happen at most once if your branch
prediction (buffer) is worth its salt, which again means that in effect
the CPU will automatically and dynamically factor out of the loop the
(performance cost of the) if-test for you. But by now we are long gone
into microoptimization-land where I would be very wary to trust any
intuition about what performs better. And if you really were interested
in performance at this level, you should rather study your lisp
implementation's performance optimization guide.

In other words, I think your original function is clearly superior,
certainly in terms of readability, and very likely also in (overall)
performance.

--
Frode V. Fjeld
From: joswig on
On 26 Nov., 05:48, Don March <don.ma...(a)gmail.com> wrote:
> A function I'm writing has a section like this:
>
> (dolist (x long-list)
>   (do-some-stuff)
>   (push (if fn
>             (funcall fn x)
>             x)
>         new-list))
>
> Which is fine, but if long-list is actually long, it's silly and
> possibly slow to evaluate the "(if fn..." stuff every time.  I came up
> with the following 2.5 alternatives:
>
> ;; option 1 (probably implemented with macros to avoid duplicate code)
> (if fn
>     (dolist (x long-list)
>       (do-some-stuff)
>       (push (funcall fn x) new-list))
>     (dolist (x long-list)
>       (do-some-stuff)
>       (push x new-list)))
>
> ;; option 2a
> (let ((push-lambda (if fn
>                        (lambda (x) (funcall fn x))
>                        (lambda (x) x))))
>   (dolist (x long-list)
>     (do-some-stuff)
>     (push (funcall push-lambda x) new-list)))
>
> ;; option 2b
> (let ((push-exp (if fn
>                     '(funcall fn x)
>                     'x)))
>   (dolist (x long-list)
>     (do-some-stuff)
>     (push (eval push-exp) new-list)))
>
> Questions:
> * Which of these is to be preferred?  Am I missing another option?
> * This is a pattern I'm running into a lot: almost duplicate chunks of
> code with small changes, but those changes have to be determined at
> run-time.  Is there a more general way of dealing with this problem?
> * Assuming either 2a or 2b had to be chosen, which is "best"?  I'm
> still unclear about when eval can be used without somebody saying it's
> inappropriate.

Another way would be something like this:

(macrolet ((iter (ob)
`(dolist (x long-list)
(do-some-stuff)
(push ,ob new-list))))
(if fn
(iter (funcall fn x))
(iter x)))

From: Pascal J. Bourguignon on
Don March <don.march(a)gmail.com> writes:

> A function I'm writing has a section like this:
>
> (dolist (x long-list)
> (do-some-stuff)
> (push (if fn
> (funcall fn x)
> x)
> new-list))
>
> Which is fine, but if long-list is actually long, it's silly and
> possibly slow to evaluate the "(if fn..." stuff every time. I came up
> with the following 2.5 alternatives:
>
> ;; option 1 (probably implemented with macros to avoid duplicate code)
> (if fn
> (dolist (x long-list)
> (do-some-stuff)
> (push (funcall fn x) new-list))
> (dolist (x long-list)
> (do-some-stuff)
> (push x new-list)))

Notice that any compiler worth its bits will do that transformation
automatically (moving constant computations outside of loops), perhaps
with (declaim (optimize (speed 3) (space 1))).

Otherwise if you have to do it yourself, use a macrolet as indicated
by joswig.


--
__Pascal Bourguignon__