Newsgroups: comp.lang.lisp
Path: cantaloupe.srv.cs.cmu.edu!das-news.harvard.edu!news2.near.net!MathWorks.Com!usenet.eel.ufl.edu!usenet.cis.ufl.edu!eng.ufl.edu!saimiri.primate.wisc.edu!aplcenmp!hall
From: hall@aplcenmp.apl.jhu.edu (Marty Hall)
Subject: Some Optimization Examples
Message-ID: <CwqzE1.2E4@aplcenmp.apl.jhu.edu>
Organization: JHU/APL AI Lab, Hopkins P/T CS Faculty
Date: Mon, 26 Sep 1994 17:32:24 GMT
Lines: 98


To followup on Ken Anderson's excellent example, I was hoping to start
a thread on optimizing Lisp code: for common examples, what are the
costs of various operations, and what are the benefits of alternative
approaches? To get the ball rolling, here are some relatively simple
examples of trying to do COLLECTING by hand -- looping and collecting
results into a list. It is intended for beginner or advanced beginner
Common Lisp programmers, but in fact it is surprising how many
professional Lisp programmers fail to consider the costs of list
operations when they use them. How many times have you heard someone
say "I did such and such in C and it took 7 seconds, but in Lisp it
took 43 minutes", only to discover they were using arrays in C and
lists in Lisp, or failing to keep a pointer to the tail of the list in
Lisp? 
					- Marty
(proclaim '(inline skates))

============================== CUT HERE ==============================
;;; -*- Mode: LISP; Syntax: Common-lisp; Package: User; Base: 10 -*-
(in-package :User)

;;;===========================================================================
;;;===========================================================================
;;; Various methods of collecting lists. In this case, we are accumulating a
;;; list of the first N integers. Assume for the sake of argument that we can
;;; not simply loop in the reverse direction.
;;; Use (first (time (First-N-Vxx 1000))) to test.
;;; 1994 Marty Hall. hall@aplcenmp.apl.jhu.edu
;;;===========================================================================
;;;===========================================================================

;;;===========================================================================
;;; You care more about how long it takes at high optimization levels than at
;;; low ones.

(proclaim '(optimize (speed 3) (safety 1) (compilation-speed 0)))

;;;===========================================================================
;;; Version1: If compiler is smart, this should be the best method, and
;;; should be O(N) time and space. For N=1000, takes 0.01 seconds and
;;; allocates 8,288 bytes on Sun SPARC2 under Lucid Common Lisp v4.1.

(defun First-N-V1 (N)
  (loop for I from 1 to N collecting I))

;;;===========================================================================
;;; Version 2. This naive method takes O(N^2) time and space since APPEND
;;; copies its first argument each time. Code like this appears surprisingly
;;; often :-(. For N=1000, takes 1.95 seconds and allocates 4,020,304 bytes
;;; on Sun SPARC2.

(defun First-N-V2 (N)
  (let ((Nums NIL))
    (loop for I from 1 to N do
      (setq Nums (append Nums (list I))))
    Nums))

;;;===========================================================================
;;; Version 3. Put the numbers on the front, which is an O(1) operation,
;;; instead of on the back, which is an O(N) operation. Then reverses the list
;;; at the end. This is O(N) time and space, but has an extra constant factor
;;; of 2 in both time and space to reverse the list. For N=1000, takes 0.01
;;; seconds and allocates 16,288 bytes.

(defun First-N-V3 (N)
  (let ((Nums NIL))
    (loop for I from 1 to N do
      (push I Nums))
    (reverse Nums)))

;;;===========================================================================
;;; Version 4. Same as above but use NREVERSE to avoid the extra allocation.
;;; For N=1000, takes 0.01 seconds and allocates 8,288 bytes on Sun SPARC2.
;;; In principle, this does make two passes on the list, as opposed to the one
;;; pass of version 5. But on the other hand, it is doing less work per pass,
;;; and in practice is virtually identical.

(defun First-N-V4 (N)
  (let ((Nums NIL))
    (loop for I from 1 to N do
      (push I Nums))
    (nreverse Nums)))

;;;===========================================================================
;;; Version 5, which should be as good as the builtin way. Keep a pointer to
;;; the tail of the list as you build it, and just tack the next cons cell
;;; onto this tail. For N=1000, takes 0.01 seconds and allocates 8,288 bytes
;;; on Sun SPARC2.

(defun First-N-V5 (N)
  (let* ((Nums (list 1))   ; You need something in the list to get a cons cell
         (Tail Nums))
    (loop for I from 2 to N do
      (setf (cdr Tail) (list I))
      (setq Tail (cdr Tail)))
    Nums))

;;;===========================================================================
