<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE message PUBLIC
  "-//MLarc//DTD MLarc output files//EN"
  "../../mlarc.dtd"[
  <!ATTLIST message
    listname CDATA #REQUIRED
    title CDATA #REQUIRED
  >
]>

  <?xml-stylesheet href="../../mlarc.xsl" type="text/xsl"?>


<message 
  url="2003/01/f09c1cd83ec921dc87b5dc72261e2890"
  from="Issac Trotts &lt;ijtrotts@u...&gt;"
  author="Issac Trotts"
  date="2003-01-31T23:07:46"
  subject="Re: [Caml-list] @, List.append, and tail recursion"
  prev="2002/12/dde2396dd114706e72b695584f205c22"
  next="2003/01/25dd585393832a00ec069df6b9aa3e64"
  prev-in-thread="2003/01/a9673766c5e0333ea0c657cce6e6a1c6"
  next-in-thread="2003/01/43d4021b732340168a7aa2d608ef4e32"
  prev-thread="2003/01/0e9c2bc35f3c3fbbe7e967dccafbddfc"
  next-thread="2003/01/d579dc7183e18352238ff4a8f4a069ee"
  root="../../"
  period="month"
  listname="caml-list"
  title="Archives of the Caml mailing list">

<thread subject="[Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/4a9754e53ff07723caf21b4496d1d267"
  from="Brian Hurt &lt;brian.hurt@q...&gt;"
  author="Brian Hurt"
  date="2003-01-24T01:14:50"
  subject="[Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/899bda8a77b5c4c96e73e91b5cca074b"
  from="Olivier Andrieu &lt;andrieu@i...&gt;"
  author="Olivier Andrieu"
  date="2003-01-30T18:10:32"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/fab35b022410822cb86a9793044747e0"
  from="Brian Hurt &lt;brian.hurt@q...&gt;"
  author="Brian Hurt"
  date="2003-01-30T19:37:04"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/84b55a0c74cfad2033538b3b7b54c310"
  from="Olivier Andrieu &lt;oandrieu@n...&gt;"
  author="Olivier Andrieu"
  date="2003-01-31T09:40:48"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/2431333c3c6b36d46b3e0780dfccb921"
  from="Brian Hurt &lt;brian.hurt@q...&gt;"
  author="Brian Hurt"
  date="2003-01-30T21:48:04"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/9676deab43bb44cb174c80433348dec3"
  from="james woodyatt &lt;jhw@w...&gt;"
  author="james woodyatt"
  date="2003-01-31T02:18:43"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/a9673766c5e0333ea0c657cce6e6a1c6"
  from="Brian Hurt &lt;brian.hurt@q...&gt;"
  author="Brian Hurt"
  date="2003-01-31T17:04:15"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/f09c1cd83ec921dc87b5dc72261e2890"
  from="Issac Trotts &lt;ijtrotts@u...&gt;"
  author="Issac Trotts"
  date="2003-01-31T23:07:46"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
</msg>
<msg 
  url="2003/01/43d4021b732340168a7aa2d608ef4e32"
  from="brogoff@s..."
  author="brogoff@s..."
  date="2003-01-31T17:42:54"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
</msg>
<msg 
  url="2003/01/879004760584f7faab4a520558ec76fd"
  from="Russ Ross &lt;caml@r...&gt;"
  author="Russ Ross"
  date="2003-01-31T19:18:57"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/02/e6489db58afc9a883258a73c728cb74c"
  from="brogoff@s..."
  author="brogoff@s..."
  date="2003-02-01T02:30:58"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
</msg>
<msg 
  url="2003/01/e8ff6f38a8931153eb164322e7095221"
  from="Alexander V. Voinov &lt;avv@q...&gt;"
  author="Alexander V. Voinov"
  date="2003-01-31T19:39:44"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
</msg>
</msg>
</msg>
<msg 
  url="2003/01/1c9ca4f143a0ace6056cb77ebfb173e0"
  from="Diego Olivier Fernandez Pons &lt;Diego-Olivier.FERNANDEZ-PONS@c...&gt;"
  author="Diego Olivier Fernandez Pons"
  date="2003-01-31T17:06:41"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/01/5cfa2d91424bf433790e9d4c7ffeb3d6"
  from="Brian Hurt &lt;brian.hurt@q...&gt;"
  author="Brian Hurt"
  date="2003-01-31T19:43:14"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
<msg 
  url="2003/02/fe0747cbf160b030a7c262d2d3175a0e"
  from="Diego Olivier Fernandez Pons &lt;Diego-Olivier.FERNANDEZ-PONS@c...&gt;"
  author="Diego Olivier Fernandez Pons"
  date="2003-02-01T10:19:59"
  subject="Linear systems (was Re: [Caml-list] @, List.append, and tail recursion)">
</msg>
</msg>
<msg 
  url="2003/01/bc5c8643c380f6aab1a0509ce17e2872"
  from="Issac Trotts &lt;ijtrotts@u...&gt;"
  author="Issac Trotts"
  date="2003-01-31T21:30:38"
  subject="Re: [Caml-list] @, List.append, and tail recursion">
</msg>
</msg>
</msg>
</msg>
</msg>
</msg>
</msg>
</msg>
</thread>

<contents>
On Fri, Jan 31, 2003 at 11:13:26AM -0600, Brian Hurt wrote:
&gt; On Thu, 30 Jan 2003, james woodyatt wrote:
&gt; 
&gt; &gt; everyone--
&gt; &gt; 
&gt; &gt; Earlier in this thread, I suggested using a queue if you are spending 
&gt; &gt; too much time in List.append.  Lack of optimized tail recursion is not 
&gt; &gt; really a factor compared to the waste of cycles involved in 
&gt; &gt; constructing a whole new list just to append a single element on the 
&gt; &gt; end.
&gt; &gt; 
&gt; &gt; Apparently, nobody seemed to notice what I was talking about.  So I'm 
&gt; &gt; going to try to make my point again.  Sorry if you got it the first 
&gt; &gt; time.
&gt; 
&gt; I did get it the first time.  I'm just using List.append to illustrate a 
&gt; problem I'm having.
&gt; 
&gt; The problem is *constructing* lists.  If you can construct your list 
&gt; backwards, fine- but if you can't, you end up either not being tail 
&gt; recursive (and blowing up for long lists) or allocating the list twice.
&gt; 
&gt; Here's an example I have run across.  I'm working with sparse vectors, and 
&gt; basically storing them as (int * float) lists.  Now, let's write the 
&gt; vector add function.  The naive implementation would be:
&gt; 
&gt; let rec add x y = (* return x + y *)
&gt;     match (x, y) with
&gt;         ([], _) -&gt; y
&gt;         | (_, []) -&gt; x
&gt;         | (((xidx, xval) as xhead) :: xtail, 
&gt;            ((yidx, yval) as yhead) :: ytail)
&gt;         -&gt;
&gt;             if (xidx == yidx) then
&gt;                 (xidx, xval +. yval) :: (add xtail ytail)
&gt;             else if (xidx &lt; yidx) then
&gt;                 xhead :: (add xtail y)
&gt;             else
&gt;                 yhead :: (add x ytail)
&gt; ;;
&gt; 
&gt; It's simple, and obvious in both what it does and how it does it.  Except
&gt; opps, this isn't tail recursive.  If your sparse vectors might be 65536
&gt; elements long, this will blow up.  So we rewrite to be tail recursive:
&gt;  
&gt; let add x y = (* return x + y *)
&gt;     let add_int x y accum =
&gt;         match (x, y) with
&gt;             ([], _) -&gt; (List.rev_append accum y)
&gt;             | (_, []) -&gt; (List.rev_append accum x)
&gt;             | (((xidx, xval) as xhead) :: xtail, 
&gt;                ((yidx, yval) as yhead) :: ytail)
&gt;             -&gt;
&gt;             if (xidx == yidx) then
&gt;                 add_int xtail ytail ((xidx, xval +. yval) :: accum)
&gt;             else if (xidx &lt; yidx) then
&gt;                 add_int xtail y (xhead :: accum)
&gt;             else
&gt;                 add_int x ytail (yhead :: accum)
&gt; ;;

I get your meaning, but it has to be changed to something like this 

  let add x y = (* return x + y *)
            let rec  add_int x y accum =
                match (x, y) with
                    ([], _) -&gt; (List.rev_append accum y)
                    | (_, []) -&gt; (List.rev_append accum x)
                    | (((xidx, xval) as xhead) :: xtail, 
                      ((yidx, yval) as yhead) :: ytail)
                    -&gt;
                    if (xidx == yidx) then
                        add_int xtail ytail ((xidx, xval +. yval) :: accum)
                    else if (xidx &lt; yidx) then
                        add_int xtail y (xhead :: accum)
                    else
                        add_int x ytail (yhead :: accum)
          in   
          add_int x y []
    ;;

to work on OCaml 3.06.

&gt; This makes the function truely tail recursive, except now it's allocating 
&gt; most of the returned vector twice (once as accum, and once in 
&gt; List.rev_append) and it's signifigantly uglier IMHO.  Rewritting the code 
&gt; to use set_cdr is the best performaner, but the ugliest yet.
&gt; 
&gt; And the add function is truely simple.  It can handle minor increases in 
&gt; ugliness without losing much.  But now consider something rather more 
&gt; complicated- say matrix transposition or matrix multiplication with
&gt; matricies defined as:
&gt; 
&gt; type vector_t = (int * float) list ;; (* index * value list *)
&gt; type matrix_t = (int * vector_t) list ;; (* row index * row vector list *)
&gt; 
&gt; Now minor uglification becomes major uglification.  It'd be nicer just to 
&gt; be able to be able to construct lists forwards instead of backwards.

Well, in your first example you are mapping ints to floats.  Why not use
a map for this?  You are keeping the keys sorted, so using a map should 
be at least as good asymptotically when you're constructing it.

  module Imap = Map.Make(struct type t=int let compare=compare end);;

  let rec vec_make = function 
    [] -&gt; Imap.empty 
    | (a,b) :: tail -&gt; Imap.add a b (vec_make tail);;

  let a = vec_make [(0, 1.0); (3, 3.0)] ;;    

  let b = vec_make [(1, 2.0); (3, 4.5); (4, 5.0)] ;;  

  let vec_add x y =
    Imap.fold
      (fun index value acc -&gt;
        try 
          let acc_val = Imap.find index acc in
          let acc = Imap.remove index acc in
          Imap.add index (value +. acc_val) acc 
        with 
            Not_found -&gt; Imap.add index value acc
      )
      x
      y
  ;;

 let result = vec_add a b;;

 Imap.iter (fun i v -&gt; Printf.printf "%i : %9.6f\n" i v) result;;

0 :  1.000000
1 :  2.000000
3 :  7.500000
4 :  5.000000
- : unit = ()

For matrices, how about

  let mat_add x y =
    Imap.fold
      (fun index value acc -&gt;
        try
          let acc_val = Imap.find index acc in
          let acc = Imap.remove index acc in
          Imap.add index (vec_add value acc_val) acc
        with
            Not_found -&gt; Imap.add index value acc
      )
      x
      y
  ;;

Issac

&gt; List.append is just an obvious example to be talking about, but the 
&gt; problem is signifigantly more general.
&gt; 
&gt; Brian
&gt; 
&gt; 
&gt; 
&gt; &gt; 
&gt; &gt; On Thursday, Jan 30, 2003, at 13:57 US/Pacific, Brian Hurt wrote:
&gt; &gt; &gt; On Thu, 30 Jan 2003, Olivier Andrieu wrote:
&gt; &gt; &gt;
&gt; &gt; &gt;&gt;&gt; list1: 1.462s
&gt; &gt; &gt;&gt;&gt; list2: 1.757s
&gt; &gt; &gt;&gt;&gt; list3: 1.824s
&gt; &gt; &gt;&gt;
&gt; &gt; &gt;&gt; There's an assert in setcdr : it's important because the first
&gt; &gt; &gt;&gt; argument mustn't be an empty list. It's never the case here, so you
&gt; &gt; &gt;&gt; can safely compile with -noassert.
&gt; &gt; &gt;
&gt; &gt; &gt; Doh!  OK- now, compiling with -noassert drops the time to 1.457 seconds
&gt; &gt; &gt; (same machine, same environment)- to slightly better than the recursive
&gt; &gt; &gt; version.
&gt; &gt; 
&gt; &gt; For grins, I wrote an equivalent test program.  It uses a functional 
&gt; &gt; deque instead of a list.  (I have written one.  It's a component of my 
&gt; &gt; Cf library, which remains unreleased at the moment.  Markus Mottl has 
&gt; &gt; translated several of Chris Okasaki's functional queue implementations 
&gt; &gt; into Ocaml, and you can find them on the Hump.)
&gt; &gt; 
&gt; &gt; To get started, I timed the 'benchmarks' by running them on my iBook 
&gt; &gt; (the 700 MHz G3 model) so I could get a baseline.  My little iBook is 
&gt; &gt; nowhere near as fast as your cool 1.6GHz P4, but I'm not complaining.
&gt; &gt; 
&gt; &gt; The results of my tests were:
&gt; &gt; 
&gt; &gt; 	$ time ./list1
&gt; &gt; 	3.690u 0.070s 0:04.14 90.8%     0+0k 0+0io 0pf+0w
&gt; &gt; 
&gt; &gt; 	$ time ./list2
&gt; &gt; 	4.180u 0.020s 0:05.01 83.8%     0+0k 0+0io 0pf+0w
&gt; &gt; 	
&gt; &gt; 	$ time ./list3
&gt; &gt; 	3.700u 0.000s 0:04.49 82.4%     0+0k 0+0io 0pf+0w
&gt; &gt; 
&gt; &gt; Not real fast, but fast enough that I don't mind waiting for results.  
&gt; &gt; So, what difference does my functional deque implementation make?  Glad 
&gt; &gt; you asked.
&gt; &gt; 
&gt; &gt; My Cf_deque module matches the following signature:
&gt; &gt; 
&gt; &gt; 	(* begin cf_deque.mli *)
&gt; &gt; 	type 'a t
&gt; &gt; 
&gt; &gt; 	val nil: 'a t
&gt; &gt; 
&gt; &gt; 	module type Direction_T = sig
&gt; &gt; 	    val pop: 'a t -&gt; ('a * 'a t) option
&gt; &gt; 	    val push: 'a -&gt; 'a t -&gt; 'a t
&gt; &gt; 	end
&gt; &gt; 
&gt; &gt; 	module A: Direction_T
&gt; &gt; 	module B: Direction_T
&gt; &gt; 
&gt; &gt; 	val cat: 'a t -&gt; 'a t -&gt; 'a t
&gt; &gt; 	(* end file *)
&gt; &gt; 
&gt; &gt; (Actually, this isn't the complete interface.  I've written a variety 
&gt; &gt; of ancillary functions that make it convenient to work with the objects 
&gt; &gt; in a deque without having to translate them into lists, e.g. fold, 
&gt; &gt; iterate, etc.)
&gt; &gt; 
&gt; &gt; All the functions above are purely functional, and they perform with 
&gt; &gt; O(1) average complexity.  (Or at least, that's my untrained analysis.  
&gt; &gt; I'd like to provide proof of that assertion, and I'm working on getting 
&gt; &gt; some help in that effort-- but, I'll have more news on that when I have 
&gt; &gt; it.)
&gt; &gt; 
&gt; &gt; Here's a variant of the list1.ml test above, which uses my Cf_deque 
&gt; &gt; module instead:
&gt; &gt; 
&gt; &gt; 	(* begin t-opt.deque.ml *)
&gt; &gt; 	open Cf_deque
&gt; &gt; 
&gt; &gt; 	let rec makelist_aux c accum =
&gt; &gt; 	  let accum = B.push c accum in
&gt; &gt; 	  if c &gt; 0 then makelist_aux (pred c) accum else accum
&gt; &gt; 
&gt; &gt; 	let makelist c = makelist_aux c nil
&gt; &gt; 
&gt; &gt; 	;;
&gt; &gt; 	let _ = makelist 5000;;
&gt; &gt; 	(* end file *)
&gt; &gt; 
&gt; &gt; Here are the timing results on that same iBook:
&gt; &gt; 
&gt; &gt; 	$ time ./t-opt.deque
&gt; &gt; 	0.010u 0.010s 0:00.02 100.0%    0+0k 0+0io 0pf+0w
&gt; &gt; 
&gt; &gt; In other words, it's done before enough time passes even to measure it 
&gt; &gt; properly.
&gt; &gt; 
&gt; &gt; &gt; And for the record, I just tested with appending to a list of 500,000
&gt; &gt; &gt; elements, and it worked OK.
&gt; &gt; 
&gt; &gt; Here is the result of running my version of the test with 500,000 
&gt; &gt; elements:
&gt; &gt; 
&gt; &gt; 	$ time ./t-opt.deque
&gt; &gt; 	0.450u 0.080s 0:00.75 70.6%     0+0k 0+0io 0pf+0w
&gt; &gt; 
&gt; &gt; It took under a second of wall-clock time.  On the other hand, when I 
&gt; &gt; modified list3.ml for 500,000 elements, it took *forever* in wall-clock 
&gt; &gt; time.  I gave up after almost an hour and a half.  Ultimately, I killed 
&gt; &gt; it with SIGINT before it finished.  I have no idea how far it got.
&gt; &gt; 
&gt; &gt; Clearly, I need to push a lot more elements into my deque before I will 
&gt; &gt; get a timing result that I can measure in heartbeats.  Here is the 
&gt; &gt; result for 5,000,000 elements:
&gt; &gt; 
&gt; &gt; 	$ time ./t-opt.deque
&gt; &gt; 	5.160u 0.510s 0:06.69 84.7%     0+0k 0+0io 0pf+0w
&gt; &gt; 
&gt; &gt; At this stage, I noticed my little iBook was just starting to thrash 
&gt; &gt; when the program finished.    So, I bumped it up to 50,000,000 
&gt; &gt; elements, because I like punishing my computer from time to time-- just 
&gt; &gt; to teach it respect.
&gt; &gt; 
&gt; &gt; At around 15 seconds into the run, the program turned into the 
&gt; &gt; psychedelic pizza delivery service: the pager went into fear and 
&gt; &gt; loathing mode, and the pretty Aqua GUI started acting like it was 
&gt; &gt; sniffing glue.  If I had let it run to completion, it probably would 
&gt; &gt; have wedged the machine.  (Mac OS X is pretty stable, but it hates 
&gt; &gt; resource bombs as much as any other operating system.)
&gt; &gt; 
&gt; &gt; None of the listN.ml files were able to bring down the machine like 
&gt; &gt; that, no matter how long I let them run-- which should make sense, 
&gt; &gt; right?  The APPEND function is O(N) for lists.  Once N gets beyond a 
&gt; &gt; few hundred, the program spends almost all its cycles just copying list 
&gt; &gt; cells inside its innermost loop, only occasionally reaching the end of 
&gt; &gt; a list and tacking a new cell onto the end before starting over again.
&gt; &gt; 
&gt; &gt; The problem is not the language, or the compiler.  The problem is the 
&gt; &gt; design of the program.  The moral of this story: you really should 
&gt; &gt; consider using a queue if you find your programs are spending a lot of 
&gt; &gt; cycles appending things to very long lists.
&gt; &gt; 
&gt; &gt; 
&gt; &gt; 
&gt; 
&gt; -------------------
&gt; To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr
&gt; Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
&gt; Beginner's list: http://groups.yahoo.com/group/ocaml_beginners

-- 

-------------------
To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr
Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners

</contents>

</message>

