From: Mike Rosen
Subject: efficiency and programming practices in Lisp
Date: 
Message-ID: <fd9b196d.0306060852.889b69@posting.google.com>
Lispers,

Is there any reason to disagree with my (MAR) position in this dispute
with NMR (who insists on placing (let) bindings around predicate
functions).  Is it possible I am overlooking something?  Thanks, Mike
A Rosen

>> I prefer to add variables that are then initialized and returned
>> explicitly only because it makes it easier to follow for 
>> programmers that are not very proficient in LISP.  I've had
problems
>> in finding LISP programmers in the past, so I prefer to
>> make the LISP code as easy to follow as possible even if it
violates
>> LISP programming style.  NMR.

NMR,

That claim is just nonsense.
Look, anyone that can read and understand (nmr) can read and
understand and understand (mar), below.  Besides, who is going to ever
read either one, but LISP programmers?  There is no clarity added,
there is only memory wasted.

;;function nmr
 (defun nmr () 
  (let ((result nil))
    (when (and (= 1 1) (= 2 2)) (setf result t))
  result ))

;; function mar
  (defun mar ()  (and (= 1 1) (= 2 2)))

;;;; time both functions over 5000 iterations

(time (dotimes (i 5000 t) (nmr)))

Timing the evaluation of (DOTIMES (I 5000 T) (NMR))
user time    =      2.690
system time  =      0.110
Allocation   = 6425180 bytes

(time (dotimes (i 5000 t) (mar)))

Timing the evaluation of (DOTIMES (I 5000 T) (MAR))
user time    =      0.860
system time  =      0.000
Allocation   = 1564672 bytes

The "well-written" formulation uses one quarter the memory, and
executes 3 times faster.  It's not a style question, it's a quality
question.  What is illustarted in the experiment, is exactly the
difference between "good code" and shabby code.  Good code is as lean
and fast as it can possibly be.  It has payoffs at compile-time, at
load-time, and at run-time.  It eases the burden of the garbage
collector.  It involves less typing.  It's shorter and doesn't scroll
off the screen in an edit session.  The (let) binding is especially
wasteful.  Even (when (and (test 1) (test 2)) t) is measurably better.
 One cannot build a complex application, without giving weight to the
efficiency of frequently used functions.

MAR

From: Joe Marshall
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <brxb83zb.fsf@ccs.neu.edu>
·······@ford.com (Mike Rosen) writes:

> Lispers,
> 
> Is there any reason to disagree with my (MAR) position in this dispute
> with NMR (who insists on placing (let) bindings around predicate
> functions).  Is it possible I am overlooking something?  Thanks, Mike
> A Rosen
> 
> >> I prefer to add variables that are then initialized and returned
> >> explicitly only because it makes it easier to follow for 
> >> programmers that are not very proficient in LISP.  I've had
> >> problems in finding LISP programmers in the past, so I prefer to
> >> make the LISP code as easy to follow as possible even if it
> >> violates LISP programming style.  NMR.
> 
> ;;function nmr
>  (defun nmr () 
>   (let ((result nil))
>     (when (and (= 1 1) (= 2 2)) (setf result t))
>   result ))

He missed two conditions:

(defun nmr ()
  (let ((result      nil)
        (subresult-a nil)
        (subresult-b nil))
    (when (= 1 1) (setf subresult-a t))
    (when (= 2 2) (setf subresult-b t))
    (when (and subresult-a subresult-b) (setf result t))
    result))

or even:

(defun nmr ()
  (let ((result      nil)
        (subresult-a nil)
        (subresult-b nil))
    (when (= 1 1) (setf subresult-a t))
    (unless (not subresult-a)
      (when (= 2 2) (setf subresult-b t))
      (unless (not subresult-b)
        (when (and (eq subresult-a 't) (eq subresult-b t)) (setf result t))))
    result))

This just gets more and more readable!
From: Geoffrey Summerhayes
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <ln5Ea.6955$V77.780690@news20.bellglobal.com>
"Mike Rosen" <·······@ford.com> wrote in message ·······························@posting.google.com...
> Lispers,
>
> Is there any reason to disagree with my (MAR) position in this dispute
> with NMR (who insists on placing (let) bindings around predicate
> functions).  Is it possible I am overlooking something?  Thanks, Mike
> A Rosen

Yup.

>
> ;;function nmr
>  (defun nmr ()
>   (let ((result nil))
>     (when (and (= 1 1) (= 2 2)) (setf result t))
>   result ))
>
> ;; function mar
>   (defun mar ()  (and (= 1 1) (= 2 2)))
>

Too slow, too big!

(time (dotimes (i 5000 t) (nmr)))

Timing the evaluation of (DOTIMES (I 5000 T) (NMR))

user time    =      0.050
system time  =      0.000
Elapsed time =   0:00:00
Allocation   = 0 bytes standard / 110011 bytes fixlen
0 Page faults
T

(time (dotimes (i 5000 t) (mar)))

Timing the evaluation of (DOTIMES (I 5000 T) (MAR))

user time    =      0.020
system time  =      0.000
Elapsed time =   0:00:00
Allocation   = 0 bytes standard / 110011 bytes fixlen
0 Page faults
T

(defmacro grs () `t) ;  :-P

(time (dotimes (i 5000 t) (grs)))

Timing the evaluation of (DOTIMES (I 5000 T) (GRS))

user time    =      0.010
system time  =      0.000
Elapsed time =   0:00:00
Allocation   = 0 bytes standard / 55011 bytes fixlen
0 Page faults
T

Pfui. Small snippets like this rarely prove anything.

> The "well-written" formulation uses one quarter the memory, and
> executes 3 times faster.  It's not a style question, it's a quality
> question.  What is illustarted in the experiment, is exactly the
> difference between "good code" and shabby code.  Good code is as lean
> and fast as it can possibly be.  It has payoffs at compile-time, at
> load-time, and at run-time.  It eases the burden of the garbage
> collector.  It involves less typing.  It's shorter and doesn't scroll
> off the screen in an edit session.  The (let) binding is especially
> wasteful.  Even (when (and (test 1) (test 2)) t) is measurably better.
>  One cannot build a complex application, without giving weight to the
> efficiency of frequently used functions.
>

A programmer has to balance numerous factors. Time (user), time (programmer),
space, legibility, correctness, flexibility, and safety, for example. It's
usually tradeoffs, a gain in one, a loss in the other.

'Good' is the code that finds the correct balance for the program being
written.

On one of my jobs, the user requirements for the interface changed on
a daily basis during development (large client base with slightly different
requirements), changing the 'fast' code was quickly becoming all I was doing.
So I rewrote the entire UI to make changing the interface easier. It ran
a lot slower, you could only prove it with a profiler, and I could
actually work on the rest of the program.

IIRC, Michael Abrash wrote about optimising the piss out of a routine
only to find the original code was already too fast for the hardware. The
profiling was including a stall for the previous write to complete and the
improved routine was compact, fast, obscure, and pointless. By the time
the hardware could keep up the routine was obsolescent, the new hardware
had taken over the job.

--
Geoff
From: Drew McDermott
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <bbqtca$3ti$1@news.wss.yale.edu>
Mike Rosen wrote:
>
> 
> The "well-written" formulation uses one quarter the memory, and
> executes 3 times faster.  It's not a style question, it's a quality
> question.  

You weren't very clear on whether you compiled the two programs, or the 
test iteration.  I would be surprised if a compiler produced 
substantially different object code for nmr and mar, and surprised that 
there was any significant discrepancy in the run times.

If you're comparing interpreted versions, then of course the longer 
program will lose.  But you're metering the interpreter, not the 
programs it emulates.

Sorry if this question seems insulting.  But I really have trouble 
figuring out why either function allocated over a megabyte of storage if 
  it was compiled.

    -- Drew McDermott
From: Wade Humeniuk
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <e%3Ea.14515$6f3.3176093@news1.telusplanet.net>
"Mike Rosen" <·······@ford.com> wrote in message
·······························@posting.google.com...
> Lispers,
>
> Is there any reason to disagree with my (MAR) position in this dispute
> with NMR (who insists on placing (let) bindings around predicate
> functions).  Is it possible I am overlooking something?  Thanks, Mike
> A Rosen

No.  The example given is just a toy.  When it comes to real
code NMR's style taken to the extreme will just confuse things.
Say there are 50 more lines between the let and the result, with
intermediate results scattered throughout the body.  It is
untenable.

Wade
From: Barry Margolin
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <wm5Ea.15$e%3.217@paloalto-snr1.gtei.net>
In article <·······················@news1.telusplanet.net>,
Wade Humeniuk <····@nospam.nowhere> wrote:
>
>"Mike Rosen" <·······@ford.com> wrote in message
>·······························@posting.google.com...
>> Lispers,
>>
>> Is there any reason to disagree with my (MAR) position in this dispute
>> with NMR (who insists on placing (let) bindings around predicate
>> functions).  Is it possible I am overlooking something?  Thanks, Mike
>> A Rosen
>
>No.  The example given is just a toy.  When it comes to real
>code NMR's style taken to the extreme will just confuse things.
>Say there are 50 more lines between the let and the result, with
>intermediate results scattered throughout the body.  It is
>untenable.

But I think MAR's approach taken to the extreme is also unweildy.  A huge
calculation is best broken up into smaller expressions, with mnemonic names
given to the intermediate values.

This is often when I've found use for LET*:

(let* ((var1 ...)
       (var2 ... var1 ...)
       (var3 ... var2 ...))
  ... var3 ...)

This allows you to keep each intermediate expression to a reasonable,
understandable size.

A decent compiler should be able to optimize this into practically the same
code as if it had been one huge expression.  All it takes is simple live
variable analysis to see that each variable is only used once, so the
expressions can be merged.

-- 
Barry Margolin, ··············@level3.com
Level(3), Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
From: Coby Beck
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <bbrcs8$2ii9$1@otis.netspace.net.au>
"Mike Rosen" <·······@ford.com> wrote in message
·······························@posting.google.com...
> Lispers,
>
> Is there any reason to disagree with my (MAR) position in this dispute
> with NMR (who insists on placing (let) bindings around predicate
> functions).  Is it possible I am overlooking something?  Thanks, Mike
> A Rosen
>
> >> I prefer to add variables that are then initialized and returned
> >> explicitly only because it makes it easier to follow for
> >> programmers that are not very proficient in LISP.  I've had
> problems
> >> in finding LISP programmers in the past, so I prefer to
> >> make the LISP code as easy to follow as possible even if it
> violates
> >> LISP programming style.  NMR.

I understand the motives but the effort is misplaced.  The fact is writing C
in Lisp will satisfy neither Lisp coders nor C coders.

> Look, anyone that can read and understand (nmr) can read and
> understand and understand (mar), below.  Besides, who is going to ever
> read either one, but LISP programmers?

Well, this is not really fair to your oppositions argument.  He is concerned
about "reluctant" Lispers.  (Though I do not think he serves them well by
code like NMR)

> There is no clarity added,
> there is only memory wasted.

I don't think the issue is about anything but readability.  Altering your
general coding style to "conserve memory" is the worst of premature
optimization.

> ;;function nmr
>  (defun nmr ()
>   (let ((result nil))
>     (when (and (= 1 1) (= 2 2)) (setf result t))
>   result ))

I have seen production code with an even uglier extension of the above:

(defun nmr ()
  (let ((result nil))
    (when (and (= 1 1) (= 2 2))
      (setf result t))
  (return-from nmr result))


> ;; function mar
>   (defun mar ()  (and (= 1 1) (= 2 2)))

This is a toy example obviously, but even a C programmer would be able to
grok this.  I think better C code would be

int mar()
{
    return ((1 == 1) && (2 == 2)) : 1 ? 0;
}
(or something..)

But the principle is the same and I think it is a bad idea.  If you chose to
use a language, use it properly.  No one is served by willful misuse of a
tool.

-- 
Coby Beck
(remove #\Space "coby 101 @ bigpond . com")
From: Bruce Hoult
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <bruce-4DF4DA.13470207062003@copper.ipg.tsnz.net>
In article <··························@posting.google.com>,
 ·······@ford.com (Mike Rosen) wrote:

> ;;function nmr
>  (defun nmr () 
>   (let ((result nil))
>     (when (and (= 1 1) (= 2 2)) (setf result t))
>   result ))
> 
> ;; function mar
>   (defun mar ()  (and (= 1 1) (= 2 2)))
> 
> ;;;; time both functions over 5000 iterations
> 
> (time (dotimes (i 5000 t) (nmr)))
> 
> Timing the evaluation of (DOTIMES (I 5000 T) (NMR))
> user time    =      2.690
> system time  =      0.110
> Allocation   = 6425180 bytes
> 
> (time (dotimes (i 5000 t) (mar)))
> 
> Timing the evaluation of (DOTIMES (I 5000 T) (MAR))
> user time    =      0.860
> system time  =      0.000
> Allocation   = 1564672 bytes
> 
> The "well-written" formulation uses one quarter the memory, and
> executes 3 times faster.  It's not a style question, it's a quality
> question

You must be using a really shitty compiler (or an interpreter).

* (time (dotimes (i 100000000 t) (nmr)))
Compiling LAMBDA NIL: 
Compiling Top-Level Form: 

Evaluation took:
  2.79 seconds of real time
  2.31 seconds of user run time
  0.04 seconds of system run time
  0 page faults and
  0 bytes consed.
T.

* (time (dotimes (i 100000000 t) (mar)))
Compiling LAMBDA NIL: 
Compiling Top-Level Form: 

Evaluation took:
  2.42 seconds of real time
  2.04 seconds of user run time
  0.04 seconds of system run time
  0 page faults and
  0 bytes consed.
T
* 

That's CMUCL.  Translating to Dylan and using CMU's d2c the times (and 
the generated code) are identical.  Of course this is a stupid test, 
since the optimizer can easily rewrite each function to just return T.

-- Bruce
From: William D Clinger
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <b84e9a9f.0306071421.4deb8fd4@posting.google.com>
Bruce Hoult wrote:
> That's CMUCL.  Translating to Dylan and using CMU's d2c the times (and 
> the generated code) are identical.  Of course this is a stupid test, 
> since the optimizer can easily rewrite each function to just return T.

On a SPARC, both ACL and Twobit/Larceny compile both NMR and MAR
to the same machine code.  I won't bother to try other compilers.

IMO there's a little more to this issue than there was to this test
program, but not much.

Will
From: William D Clinger
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <b84e9a9f.0306071801.5d15df2b@posting.google.com>
I goofed:
> On a SPARC, both ACL and Twobit/Larceny compile both NMR and MAR
> to the same machine code.  I won't bother to try other compilers.

In fact, NMR has one more machine instruction when compiled by ACL.
Twobit/Larceny v0.50 really does compile both into the same code.

Will
From: Michael Sullivan
Subject: Re: efficiency and programming practices in Lisp
Date: 
Message-ID: <1fw8g7v.1j7fq301uj94hsN%mes@panix.com>
Mike Rosen <·······@ford.com> wrote:

> That claim is just nonsense.
> Look, anyone that can read and understand (nmr) can read and
> understand and understand (mar), below.  Besides, who is going to ever
> read either one, but LISP programmers?  There is no clarity added,
> there is only memory wasted.

> ;;function nmr
>  (defun nmr () 
>   (let ((result nil))
>     (when (and (= 1 1) (= 2 2)) (setf result t))
>   result ))

> ;; function mar
>   (defun mar ()  (and (= 1 1) (= 2 2)))

Premature optimization is the root of [all|most] evil, so I don't really
care what kind of constant factor speed and memory increases are
generated here, unless this is a routine called in a heavily iterated
loop.  

So the only issue is readability.  That said, I still agree that the
claim is nonsense, at least for simple cases like this one.  The reason
is that nmr is probably *less* readable, even for someone who has never
seen lisp, unless the only languages they've ever looked at are old
dain-bramaged BASICs.

All of the popular algol derivatives contain the concept of truth as a
value that can be manipulated, assigned and returned, and using it that
way is idiomatic in C at least.  So the idea that non-lisp-programmers
would have more trouble understanding nmr than mar is silly.  

The only thing in mar that could throw anyone is the syntax, and if you
can understand the when clause in nmr, you can understand that.  Adding
in a let, when and setf just begs for *additional* syntactical confusion
on the part of a neophyte/non lisper.  IMO.

I can only see using variables to store these values as an aid, when the
nesting gets quite deep, deep enough that you really need to break it
down into pieces to understand what's going on.  Of course, my
experience tells me that when that happens it usually means your
function is too big and refactoring is in order.  And again, while that
comes from a mostly functional perspective, an awful lot of C coders
would also agree with that stance.


Michael