<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8624700733410390433</id><updated>2011-08-02T23:05:35.095-07:00</updated><category term='Lisp'/><title type='text'>risupu - notes on Lisp and programming</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>8</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-3366614748820256410</id><published>2009-12-20T04:50:00.000-08:00</published><updated>2009-12-20T05:03:44.285-08:00</updated><title type='text'>closing in on the SBCL thread mystery?</title><content type='html'>I have distilled &lt;a href="http://risupu.blogspot.com/2009/12/sbcl-multi-threaded-computation-mystery.html"&gt;the messy code from yesterday&lt;/a&gt; into 
the minimal chunk of code that will cause bizarre multi-thread behavior on SBCL 1.0.33

&lt;pre&gt;
(defun compute-thread (thread-num rows-per-proc min max mul)
   ;; a pretty straightforward computation
  (let* ((local-min (+ min (* thread-num rows-per-proc)))
     (local-max (1- (+ local-min rows-per-proc)))
     (local-count 0))
    (loop for i from local-min upto local-max
      do (loop for j from min upto max
           do (incf local-count
               (let ((xc (* mul i)))
             (loop for count from 0 below 100
                   do (when (&gt;=  xc 4)
                    (return count))
                   (incf xc)
                   finally (return 100)))
               )))
    #+nil(format *out* "Thread ~a local-min=~a, local-max=~a local-count=~d~%"
        thread-num local-min local-max local-count)
    local-count))


(defun main (num-threads)
  ;; spawn some threads and sum the results
  (loop with rows-per-proc = (/ 100 num-threads)
    for thread in
    (loop for thread-num from 0 below num-threads
          collect
          (let ((thread-num thread-num));establish private binding of thread-num for closure
        (sb-thread:make-thread (lambda ()
                     (compute-thread thread-num rows-per-proc -250 250 0.008d0)))))
    summing (sb-thread:join-thread thread)))

(defun test (num-threads num-iterations expected-val)
  ;; this is just a test wrapper which tests that the result of main is consistent
  (loop for i from 0 below num-iterations do
    (format t "Run ~a:" i)
    (let ((result (main num-threads)))
      (format t "result=~a~%" result)
      (assert (=  expected-val result)))))
&lt;/pre&gt;


I expect:
&lt;tt&gt; CL-USER&gt; (test 10 1000 (main 1)) &lt;/tt&gt;
to complete without assertions, because the result of &lt;tt&gt;(main num-threads)&lt;/tt&gt; should always be the same as the result of &lt;tt&gt;(main 1)&lt;/tt&gt;

Unfortunately:
&lt;pre&gt;
CL-USER&gt; (test 10 1000 (main 1)) ;; test 10 threads
Run 0:result=300600
Run 1:result=300600
Run 2:result=300600
Run 3:result=300600
.
.  &lt;snip&gt;
.
Run 494:result=300600
Run 495:result=300600
Run 496:result=300600
Run 497:result=300600
Run 498:result=300601   ;;&lt;---- Oh no!!!
; Evaluation aborted.
&lt;/pre&gt;

Arghh... ;-(

As a sanity check:
&lt;tt&gt;(test 1 1000 (main 1))&lt;/tt&gt; completes without problems -- in other words, 1 thread always seems to computes the same answer, so it seems to be a multi-thread problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-3366614748820256410?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/3366614748820256410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/12/closing-in-on-sbcl-thread-mystery.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/3366614748820256410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/3366614748820256410'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/12/closing-in-on-sbcl-thread-mystery.html' title='closing in on the SBCL thread mystery?'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-8699796162596141335</id><published>2009-12-19T03:23:00.000-08:00</published><updated>2009-12-20T05:06:13.861-08:00</updated><title type='text'>A SBCL multi-threaded computation mystery</title><content type='html'>Update: 2009/12/20 -- this is obsolete -- I now have a &lt;a href="http://risupu.blogspot.com/2009/12/closing-in-on-sbcl-thread-mystery.html"&gt;a much cleaner, shorter, piece of code &lt;/a&gt;which replicates the bizarre behavior below, in today's post.

I ran into this thread-related mystery a while ago, forgot all about it, and then ran into the problem again today on a similar chunk of code and wasted a bunch of time, so I'll put it up here. Maybe somebody with experience in SBCL+threads is reading and could give me a hint ;-)

Here is a multithreaded version of the simple Mandelbrot benchmark &lt;a href="http://risupu.blogspot.com/2009/06/parallel-mandelbrot-computation-in-lisp.html"&gt; which I earlier parallelized using CL-MPI&lt;/a&gt;:

&lt;pre&gt;
(defconstant +resolution+ 500)
(defconstant +iterations+ 100)
(defparameter *start-time* nil )  
(defparameter *out* *standard-output*)

(defun iters (max xc yc)
  (let ((x xc)
        (y yc))
    (declare (double-float x y)
             (fixnum max))
    (loop for count from 0 below max
          do (when (&gt;= (+ (* x x) (* y y)) 4.0d0)
               (return-from iters count))
             (let ((tmp (+ (- (* x x) (* y y)) xc)))
               (setf y (+ (* 2.0d0 x y) yc)
                     x tmp)))
    max))

(defun compute-thread (thread-num rows-per-proc min max mul)
  (declare (fixnum thread-num rows-per-proc min max))
  (declare (type double-float  mul))
  (let* ((local-min (+ min (the fixnum (* thread-num rows-per-proc))))
     (local-max (1- (+ local-min rows-per-proc)))
     (local-count 0))
    (declare (fixnum local-min local-max local-count))
    (loop for i fixnum from local-min upto local-max
      do (loop for j fixnum from min upto max
           do (incf local-count
               #+call-func (iters +iterations+ (* mul i) (* mul j))
               #-call-func
               (let* ((xc (* mul i))
                  (yc (* mul j))
                  (x xc)
                  (y yc))
                  (declare (double-float x y))
                  (loop for count from 0 below +iterations+
                          do (cond((&gt;= (+ (* x x) (* y y)) 4.0d0)
                                          (return count))
                                        (t
                                         (let ((tmp (+ (- (* x x) (* y y)) xc)))
                                            (setf y (+ (* 2.0d0 x y) yc)
                                                  x tmp))))
                        finally (return +iterations+)))
               )))
    (format *out* "Thread ~a local-min=~a, local-max=~a local-count=~d time ~,4f~%"
        thread-num local-min local-max local-count
        (float (/ (- (get-internal-run-time) *start-time*) internal-time-units-per-second)))
    local-count))

(defun mandelbrot-main (num-threads)
  "straightforward parallelization which partitions the work into equal regions,
   and each region is assigned to a process."
  (declare (fixnum num-threads))
  (format t "---~%")
  (setf *start-time* (get-internal-run-time))
  (let* ((max (truncate +resolution+ 2))
         (min (- max))
         (mul (/ 2.0d0 max)))
    ;; spawn computation threads
    (loop with rows-per-proc = (/ +resolution+ num-threads)
      for thread in
      (loop for thread-num from 0 below num-threads
        collect
        (let ((thread-num thread-num)); needed to establish private binding for closure
          (sb-thread:make-thread (lambda ()
                       (compute-thread thread-num rows-per-proc min max mul)))))
      summing (sb-thread:join-thread thread))))

&lt;/pre&gt;

Note that in &lt;tt&gt;compute-thread&lt;/tt&gt;, there is a conditionally compiled chunk of code:
if *features* contains :call-func, the function &lt;tt&gt;iters&lt;/tt&gt; is called. However, if :call-func is not in *features*, then instead, I compile a chunk of code which performs the exact same computation as the &lt;tt&gt;iters&lt;/tt&gt; function.

This code should behave identically regardless of #+call-func or #-call-func, right?
Wrong -- apparently... ;-(


The mandelbrot-main function returns the number of points which are determined to be in the Mandelbrot set, so here is a test function to verify that the computation is consistent and always computes the same, expected value:
&lt;pre&gt;
(defun test (num-threads num-iterations expected-val)
  (loop for i from 0 below num-iterations do
    (format t "Run ~a:" i)
    (let ((result (mandelbrot-main num-threads)))
      (format t "result=~a~%" result)
      (assert (=  expected-val result)))))
&lt;/pre&gt;

For &lt;tt&gt;+resolutions+ = 500&lt;/tt&gt; and &lt;tt&gt;+iterations+ = 100&lt;/tt&gt;,  the result
should be 2905274.


When I compiled with  #-call-func, 
&lt;tt&gt; (test 10 1000 2905274)&lt;/tt&gt;
completes without asserting an inconsistenty.

However, when compiled with #+call-func,
&lt;tt&gt; (test 10 1000 2905274)&lt;/tt&gt;
asserts an inconsistency after some non-deterministic number of repetitions.

e.g.,
&lt;pre&gt;
CL-USER&gt; (test 10 1000 2905274)
Run 0:---
Thread 1 local-min=-200, local-max=-151 local-count=103224 time 0.0000
Thread 0 local-min=-250, local-max=-201 local-count=29924 time 0.0000
Thread 3 local-min=-100, local-max=-51 local-count=596048 time 0.0120
Thread 2 local-min=-150, local-max=-101 local-count=364384 time 0.0200
Thread 4 local-min=-50, local-max=-1 local-count=978228 time 0.0400
Thread 6 local-min=50, local-max=99 local-count=52917 time 0.0400
Thread 7 local-min=100, local-max=149 local-count=24513 time 0.0400Thread
8 local-min=150, local-max=199 local-count=17788 time 0.0400
Thread 9 local-min=200, local-max=249 local-count=10354 time 0.0440
Thread 5 local-min=0, local-max=49 local-count=727894 time 0.0480
result=2905274
.
. &lt;truncated&gt;
.
Run 146:---
Thread 0 local-min=-250, local-max=-201 local-count=29924 time 0.0000
Thread 1 local-min=-200, local-max=-151 local-count=103224 time 0.0000
Thread 2 local-min=-150, local-max=-101 local-count=364384 time 0.0120
Thread 3 local-min=-100, local-max=-51 local-count=596048 time 0.0200
Thread 4 local-min=-50, local-max=-1 local-count=978228 time 0.0240
Thread 7 local-min=100, local-max=149 local-count=24513 time 0.0240
Thread 6 local-min=50, local-max=99 local-count=52917 time 0.0280
Thread 8 local-min=150, local-max=199 local-count=17788 time 0.0360
Thread 9 local-min=200, local-max=249 local-count=10354 time 0.0360
Thread 5 local-min=0, local-max=49 local-count=727894 time 0.0440
result=2905274
Run 147:---
Thread 0 local-min=-250, local-max=-201 local-count=29924 time 0.0080
Thread 1 local-min=-200, local-max=-151 local-count=103224 time 0.0080
Thread 2 local-min=-150, local-max=-101 local-count=364384 time 0.0120
Thread 8 local-min=150, local-max=199 local-count=17788 time 0.0280
Thread 6 local-min=50, local-max=99 local-count=52917 time 0.0360
Thread 3 local-min=-100, local-max=-51 local-count=596147 time 0.0360
Thread 9 local-min=200, local-max=249 local-count=10354 time 0.0360
Thread 7 local-min=100, local-max=149 local-count=24513 time 0.0360
Thread 5 local-min=0, local-max=49 local-count=727894 time 0.0360
Thread 4 local-min=-50, local-max=-1 local-count=978228 time 0.0400
result=2905373    &lt;--- Uh, oh!!!!!!!!!!!
.
. and SLIME complains:
The assertion (= EXPECTED-VAL RESULT) failed.
&lt;/pre&gt;


Yikes!

Looking closer, it looks like Thread 3  has a discrepancy:
In the "correct" Runs (i.e., for the first 146 Runs), the output from Thread 3 was:
&lt;pre&gt;Thread 3 local-min=-100, local-max=-51 local-count=596048 time 0.0200&lt;/pre&gt;
but on the 147th Run, the output is:
&lt;pre&gt;Thread 3 local-min=-100, local-max=-51 local-count=596147 time 0.0360&lt;/pre&gt;


I have absolutely no idea what could be causing this.
The most baffling thing is that when compiled with #-call-func , there is no problem -- that is, calling the &lt;tt&gt;iter&lt;/tt&gt; function instead of executing an inlined version of the same computation seems to be responsible for causing this problem!
Very, very bizarre because the &lt;tt&gt;iter&lt;/tt&gt; function and  its caller don't touch any special variables or do anything else which looks unsafe to me.

In addition, there's no problem if I compile with #+call-func but running but only use  1 thread
&lt;pre&gt;(test 1 1000 2905274)&lt;/pre&gt;.
If I use more than 1 thread, the inconsistency eventually shows up (the more threads, the earlier the problem shows up..). So, this must be something related to multithreading.

Is it possible that this is a bug in SBCL, or am I making  some silly thread-related mistake here?
(I'm running this  with SBCL1.0.33  and SBCL1.0.32, x86-64)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-8699796162596141335?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/8699796162596141335/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/12/sbcl-multi-threaded-computation-mystery.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/8699796162596141335'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/8699796162596141335'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/12/sbcl-multi-threaded-computation-mystery.html' title='A SBCL multi-threaded computation mystery'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-5920219622101515666</id><published>2009-06-04T07:51:00.000-07:00</published><updated>2009-06-05T00:24:41.316-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>Parallel  Mandelbrot computation in Lisp on a cluster</title><content type='html'>[quick announcement - &lt;A HREF="http://code.google.com/p/cl-mpi/downloads/list"&gt;CL-MPI 0.2.2&lt;/A&gt; is available - this contains today's example code, and adds the &lt;tt&gt;with-mpi&lt;/tt&gt; macro mentioned in the previous post, which was missing in 0.2.1]
&lt;p&gt;
This post describes a very simple parallelization of Mandelbrot set computation in Common Lisp using &lt;A HREF="http://code.google.com/p/cl-mpi"&gt;CL-MPI&lt;/A&gt;, with results on a multicore machine as well as a cluster using 50 cores.
&lt;p&gt;
Recently, there was a &lt;A HREF="http://www.reddit.com/r/programming/comments/8oz2b/c_encourages_the_usage_of_the_most_sophisticated/"&gt; Reddit discussion&lt;/a&gt; about a &lt;A HREF="http://en.wikipedia.org/wiki/Mandelbrot_set"&gt;Mandelbrot set&lt;/a&gt; benchmark. SBCL developer Nikodemus Siivola &lt;A HREF="http://random-state.net/log/3452921796.html"&gt;posted&lt;/a&gt; a Common Lisp version on his blog. By coincidence, the Mandelbrot set is a nice and simple computation which can be easily parallelized. I am &lt;b&gt;not&lt;/b&gt; jumping into the benchmark game / language speed debate with this post [I'll save that for later ;-) ] I'm just using this as a simple example of parallelization using MPI.
&lt;p&gt;
The original sequential code by Nikodemus is &lt;A HREF="http://random-state.net/files/mandelbench.lisp"&gt;here&lt;/A&gt;. Here's the parallelized version:&lt;p&gt;
&lt;pre&gt;
(declaim (optimize (speed 3) (debug 0)(safety 0)))

(defconstant +resolution+ 5000)
(defconstant +iterations+ 100)

(declaim (inline iters))

(defun iters (max xc yc)
  (declare (double-float xc yc))
  (let ((x xc)
        (y yc))
    (declare (double-float x y)
             (fixnum max))
    (loop for count from 0 below max
          do (when (&gt;= (+ (* x x) (* y y)) 4.0d0)
               (return-from iters count))
             (let ((tmp (+ (- (* x x) (* y y)) xc)))
               (setf y (+ (* 2.0d0 x y) yc)
                     x tmp)))
    max))


(defun main ()
  "straightforward parallelization which partitions the work into equal regions,
   and each region is assigned to a process."
  (let* ((max (truncate +resolution+ 2))
         (min (- max))
         (mul (/ 2.0d0 max))
         (local-count 0))
    (declare (fixnum local-count))
    (mpi:with-mpi
      (let* ((start-time (mpi:mpi-wtime))
             (num-procs (mpi:mpi-comm-size))
             (my-rank (mpi:mpi-comm-rank))
             (rows-per-proc (/ +resolution+ num-procs))
             (local-min (+ min (* my-rank rows-per-proc)))
             (local-max (1- (+ local-min rows-per-proc))))
        (declare (fixnum local-min local-max my-rank num-procs))
        (mpi:formatp t "local-min=~a, local-max=~a~%" local-min local-max)
        (loop for i from local-min upto local-max
              do (loop for j from min upto max
                       do (incf local-count (iters +iterations+ (* mul i) (* mul j)))))
        (mpi:formatp t "local-count = ~d computed after time ~,4f seconds~%" 
                     local-count (- (mpi:mpi-wtime) start-time))
        ;; aggregate local counts at rank 0
        (cond ((&gt; my-rank 0) ; send local result to rank 0
               (mpi:mpi-send-auto local-count 0))
              (t ;my-rank is  0. Receive and aggregate local results.
               (loop with count fixnum = local-count
                     for rank from 1 below num-procs
                     do (incf count (mpi:mpi-receive-auto rank))
                     finally (format t "runtime=~,4f seconds, result ~d~%" 
                                      (- (mpi:mpi-wtime) start-time) count))))))))

&lt;/pre&gt;

The &lt;tt&gt;iters&lt;/tt&gt; function is identical to the sequential code, but the &lt;main&gt; function has been modified for parallelization.
&lt;p&gt;
I used the simplest possible parallelization strategy, which is to partition the &lt;tt&gt;+resolution+ x +resolution+&lt;/tt&gt; grid where the Mandelbrot set is computed into  &lt;tt&gt;num-procs&lt;/tt&gt; regions, assigning each region to a processor. As described in the &lt;A HREF="http://risupu.blogspot.com/2009/06/message-passing-hello-world-in-cl-mpi.html"&gt;previous post&lt;/A&gt;, MPI will execute this program on all processors, but each processor has a unique &lt;em&gt; rank&lt;/em&gt; (ID). For each process, the variables &lt;tt&gt;local-min&lt;/tt&gt; and &lt;tt&gt;local-max&lt;/tt&gt; will determine the block of work to be performed (the rows assigned to the process), and &lt;tt&gt;iters&lt;/tt&gt; is called for every point in the assigned region. After the computations are completed, the local results (&lt;tt&gt;local-count&lt;/tt&gt;) are sent to the rank 0 process and aggregated into the final result, (&lt;tt&gt;count&lt;/tt&gt;). See &lt;A HREF="http://risupu.blogspot.com/2009/06/message-passing-hello-world-in-cl-mpi.html"&gt;the previous post&lt;/a&gt; for a explanation of point-to-point communication using &lt;tt&gt;mpi-send-auto&lt;/tt&gt; and &lt;tt&gt;mpi-receive-auto&lt;/tt&gt;.
&lt;p&gt;
Here's the output of running this (a 5000 x 5000 region, 100 iterations per point) on 1 core of a quad-core desktop machine. Note that I'm using &lt;tt&gt;(mpi-wtime)&lt;/tt&gt; , a wrapper for &lt;A HREF="http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Wtime.html"&gt;&lt;tt&gt;MPI_Wtime&lt;/tt&gt;&lt;/A&gt;, which is a clock in the MPI runtime environment, so this only measures the code within the &lt;tt&gt;with-mpi&lt;/tt&gt; macro (time to start up SBCL+MPI is not included).
&lt;pre&gt;
alexfs04@tesla1:~/cl-mpi/examples$ mpirun -np 1 /usr/local/bin/sbcl --load "mandel-bench-1" --eval "(progn (main) (sb-ext:quit))"
[0 :1244124821.1584]: local-min=-2500, local-max=2499
[0 :1244124824.3105]: local-count = 290068052 computed after time 3.1526 seconds
runtime=3.1527 seconds, result 290068052
&lt;/pre&gt;
Now, here's the output using all 4 cores on the same machine (Core2, 2GHz)
&lt;pre&gt;
alexfs04@tesla1:~/cl-mpi/examples$ mpirun -np 4 /usr/local/bin/sbcl --load "mandel-bench-1" --eval "(progn (main) (sb-ext:quit))"
[1 :1244125086.7273]: local-min=-1250, local-max=-1
[2 :1244125086.7327]: local-min=0, local-max=1249
[3 :1244125086.7390]: local-min=1250, local-max=2499
[0 :1244125086.7478]: local-min=-2500, local-max=-1251
[3 :1244125086.8080]: local-count = 3840768 computed after time 0.0699 seconds
[0 :1244125087.1473]: local-count = 31518526 computed after time 0.4001 seconds
[2 :1244125087.5538]: local-count = 78775348 computed after time 0.8220 seconds
[1 :1244125088.5280]: local-count = 175933410 computed after time 1.7702 seconds
runtime=1.7811 seconds, result 290068052
&lt;/pre&gt;

The speedup compared to 1 core is 3.1527/1.7811 = 1.77. The reason for the inefficiency is clear: there is a huge variance in how long it takes to complete the 4 local-count computations. While proc 3 finished in 0.0699 seconds, proc 1 took 1.7702 seconds! This is because  the number of times &lt;tt&gt;iters&lt;/tt&gt; goes through its loop depends on each particular point. We could do certainly do better by devising a better parallelization strategy, and that's a topic for a future post.
&lt;p&gt;
OK, so what's the big deal? We could have gotten the same 4-core results with thread-based parallelization using the same algorithm, so why bother with &lt;A HREF="http://en.wikipedia.org/wiki/MPI"&gt;MPI&lt;/a&gt;?
&lt;p&gt;
The reason, of course, is that we can run this exact same code on any &lt;b&gt;cluster&lt;/b&gt; of machines supporting MPI, so unlike threads, we're not limited to a single machine. I am fortunate enough to have access to &lt;A HREF="http://www.gsic.titech.ac.jp/"&gt;a huge cluster&lt;/A&gt;, so here are the results using 50 cores (Opteron, 2.4GHz). 
&lt;p&gt;
&lt;pre&gt;
[snip...]
[24 :0.1349]: local-min=-100, local-max=-1
[23 :0.1350]: local-min=-200, local-max=-101
[26 :0.1351]: local-min=100, local-max=199
[47 :0.1352]: local-min=2200, local-max=2299
[49 :0.1353]: local-min=2400, local-max=2499
[48 :0.1354]: local-min=2300, local-max=2399
[34 :0.1349]: local-min=900, local-max=999
[49 :0.1410]: local-count = 94380 computed after time 0.0071 seconds
[48 :0.1418]: local-count = 169998 computed after time 0.0079 seconds
[47 :0.1419]: local-count = 217754 computed after time 0.0080 seconds
[snip a bunch of output...]
[18 :0.2935]: local-count = 15838486 computed after time 0.1594 seconds
[20 :0.3024]: local-count = 16782526 computed after time 0.1683 seconds
[21 :0.3091]: local-count = 17468466 computed after time 0.1750 seconds
[24 :0.3328]: local-count = 20181214 computed after time 0.1992 seconds
[23 :0.3574]: local-count = 22819862 computed after time 0.2238 seconds
[22 :0.3400]: local-count = 20718664 computed after time 0.2060 seconds
runtime=0.2286 seconds, result 290068052

Cleaning up all processes ...
done.
[Ending Job 4764466]
EXIT_CODE:0
&lt;/pre&gt;

There are other alternatives for running parallel lisp programs on a cluster, such as &lt;A HREF="http://www.cliki.net/Philip-Jose"&gt;Fare's library&lt;/A&gt;, and you can always build your own parallelization infrastructure using sockets. However, on most large-scale, shared clusters used by many users, such as the one I use, these are not feasible because users can't directly address/access individual compute nodes (among other things, that would make task/job scheduling a nightmare), but MPI is the standard API available in such environments.&lt;p&gt;

In future posts, I'll describe how to parallelize the Mandelbrot computation more efficiently to get better speedups.

[edit: removed incorrect statement that MPI_Wtime is synchronized - MPI_Wtime is not synch'd unless MPI_WTIME_IS_GLOBAL is set]
[edit: fixed mangled code indentation.]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-5920219622101515666?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/5920219622101515666/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/06/parallel-mandelbrot-computation-in-lisp.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/5920219622101515666'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/5920219622101515666'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/06/parallel-mandelbrot-computation-in-lisp.html' title='Parallel  Mandelbrot computation in Lisp on a cluster'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-1871138560343461320</id><published>2009-06-02T05:19:00.000-07:00</published><updated>2009-06-02T20:57:46.662-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>A message-passing   "Hello World" in CL-MPI</title><content type='html'>&lt;p/&gt;
[Edit: added paragraph breaks]&lt;p&gt;

Let's start the tour of &lt;A HREF="http://code.google.com/p/cl-mpi"&gt;CL-MPI&lt;/A&gt; with a parallel "Hello World" that demonstrates point-to-point communications among processes, and a introduction to the MPI runtime. This post assumes you know little or nothing about &lt;A HREF="https://computing.llnl.gov/tutorials/mpi/"&gt;MPI&lt;/A&gt;, so the pace may be a bit slow for people who already know MPI. I promise to pick up the pace in future posts, so stay tuned!

&lt;pre&gt;
1  #+sbcl(require 'asdf)
2  (eval-when (:compile-toplevel :load-toplevel :execute)
3   (asdf:operate 'asdf:load-op 'cl-mpi))
4
5  (defun mpi-hello-world ()
6    "Each process other than the root (rank 0) sends a message to process 0"
7    (mpi::with-mpi  ;macro which initializes and finalizes  MPI
8       (let ((tag 0))
9         (cond ((/= 0 (mpi:mpi-comm-rank))
10               (let ((msg (format nil "Greetings from process ~a!" (mpi:mpi-comm-rank))))
11                 ;;send msg to proc 0
12                 (mpi:mpi-send-auto msg 0 :tag tag)))
13              (t ; rank is 0
14               (loop for source from 1 below (mpi:mpi-comm-size) do
15                     ;; receive and print message from each processor
16                     (let ((message (mpi:mpi-receive-auto  source :tag tag)))
17                       (format t "~a~%" message))))))))
&lt;/pre&gt;

Before describing this code, a quick overview of  the &lt;A HREF="http://en.wikipedia.org/wiki/SPMD"&gt;Single-Program, Multiple-Data (SPMD)&lt;/A&gt; the MPI execution model, used by MPI: In the SPMD model, all processes execute the same program independently. If we want to run a program on 4 processors using MPI, then MPI spawns 4 separate instances of the program, each as a separate process. Each process has a corresponding identifier, which in MPI terminology is a &lt;b&gt;rank&lt;/b&gt;.
&lt;p&gt;
Let's try to run our hello-world from the shell  (keep reading to see why it has to be from the shell; if it doesn't become obvious, see the footnotes).
&lt;p&gt;
If this was an ordinary, sequential SBCL program, we'd execute it as:
&lt;pre&gt;
alexf@tesla1:~/cl-mpi/examples$ /usr/local/bin/sbcl --noinform --load "mpi-hello-world" --eval "(progn (mpi-hello-world)(sb-ext:quit))"
&lt;/pre&gt;

Using the MPICH1.2 implementation of MPI, we do the following (with resulting output):
&lt;pre&gt;
alexf@tesla1:~/cl-mpi/examples$ mpirun -np 4 /usr/local/bin/sbcl --noinform --load "mpi-hello-world" --eval "(progn (mpi-hello-world)(sb-ext:quit))"

Greetings from process 1!
Greetings from process 2!
Greetings from process 3!
alexf@tesla1:~/cl-mpi/examples$ 
&lt;/pre&gt;


As you can see, all we did was add "mpirun -np 4" to the previous command line. The MPI runtime environment is started with an executable, usually called mpirun or mpiexec. We used &lt;A HREF="http://www.mcs.anl.gov/research/projects/mpi/www/www1/mpirun.html"&gt;mpirun&lt;/a&gt;. The "-np 4" option specifies that 4 processes are to be started. 
&lt;p&gt;
We can see what goes on by adding a &lt;tt&gt;(break)&lt;/tt&gt; between lines 8 and 9 in the code. Now, if we enter the same command line, we'll get a break and REPL. We can then run ps (from another window), and see that there are in fact 4 SBCL processes are running (spawned by mpirun), all started with the same command line parameters.&lt;sup&gt;&lt;A HREF="#1"&gt;1&lt;/a&gt;&lt;/sup&gt;
&lt;pre&gt;
alexf@tesla1:~/cl-mpi$ ps -aef | grep sbcl
alexf 25743 25740  0 17:58 ?        00:00:22 /usr/local/bin/sbcl
alexf 26978 26975  0 22:46 pts/3    00:00:00 /bin/sh -c mpirun -np 4 /usr/local/bin/sbcl --load "mpi-hello-world" --eval "(progn (mpi-hello-world)(sb-ext:quit))"
alexf 26979 26978  0 22:46 pts/3    00:00:00 /bin/sh /usr/bin/mpirun -np 4 /usr/local/bin/sbcl --load mpi-hello-world --eval (progn (mpi-hello-world)(sb-ext:quit))
alexf 27005 26979 14 22:46 pts/3    00:00:00 /usr/local/bin/sbcl --load mpi-hello-world --eval (progn (mpi-hello-world)(sb-ext:quit))
alexf 27006 27005  0 22:46 pts/3    00:00:00 /usr/local/bin/sbcl --load mpi-hello-world --eval (progn (mpi-hello-world)(sb-ext:quit))
alexf 27007 27005  0 22:46 pts/3    00:00:00 /usr/local/bin/sbcl --load mpi-hello-world --eval (progn (mpi-hello-world)(sb-ext:quit))
alexf 27008 27005  0 22:46 pts/3    00:00:00 /usr/local/bin/sbcl --load mpi-hello-world --eval (progn (mpi-hello-world)(sb-ext:quit))
alexf 27011 28544  0 22:46 pts/6    00:00:00 grep sbcl
&lt;/pre&gt;


The output shown above from the hello-world program show that processes 1-3 sent a greeting message (to process 0), and these were displayed to standard output.
Now, let's look at the code: Lines 1-3 are boilerplate for loading the CL-MPI library. The macro &lt;tt&gt;with-mpi&lt;/tt&gt; (line 7) executes the body in a MPI environment. The &lt;A HREF="http://linux.die.net/man/3/mpi_init"&gt;MPI_Init&lt;/A&gt; and &lt;A HREF="http://linux.die.net/man/3/mpi_finalize"&gt;MPI_Finalize&lt;/A&gt; functions are called before and after the body.
&lt;p&gt;
The interesting behavior start on Line 9. Up to this point, all processors have been executing the same code (all processors have loaded the cl-mpi library, started executing the mpi-hello-world function, entered the body of the &lt;tt&gt;with-mpi&lt;/tt&gt; macro, and initialized tag to 0).  Recall that each process has an associated rank. Rank 0 is usually referred to as the "root".  Line 9 uses &lt;tt&gt;mpi-comm-rank&lt;/tt&gt;, a wrapper for &lt;A HREF="http://linux.die.net/man/3/mpi_comm_rank"&gt;MPI_Comm_rank&lt;/a&gt;, to get the process rank, and branches on the rank of the processor, so lines 10-12 are executed on all processors &lt;b&gt;except&lt;/b&gt; processor 0, and lines 14-17 are executed only on processor 0.
&lt;p&gt;
Let's consider what happens on processors with rank &gt; 0.  Line 10 creates a message which contains the process rank, and line 12 calls &lt;tt&gt;mpi-send-auto&lt;/tt&gt; to perform a blocking send operation which sends the message to process 0.  A blocking send operation is a point-to-point, synchronous operation where the sender process waits for the receiver to receive the message before continuing execution (MPI and CL-MPI also support non-blocking, asynchronous communications). 
&lt;p&gt;
Meanwhile, the process with rank 0 executes the loop in line 14,  Since &lt;tt&gt;source&lt;/tt&gt; iterates from 1 to &lt;tt&gt;(mpi-comm-rank)&lt;/tt&gt;, a wrapper for &lt;A HREF="http://linux.die.net/man/3/mpi_comm_size"&gt;MPI_Comm_size&lt;/a&gt;, which specifies the total number of processes&lt;sup&gt;&lt;A HREF="#2"&gt;2&lt;/a&gt;&lt;/sup&gt;. At each iteration, line 16 performs a blocking receive operation using &lt;tt&gt;mpi-receive-auto&lt;/tt&gt;, which waits to receive a message from a given source, and line 17 prints the received message.
&lt;p&gt;
Note that lines 10-12 are executed in parallel in the processes with rank &gt; 0. The order in which these processes initiate the message send is not specified.  However, on process 0, the receiver, lines 14-17 are execute sequentially, and line 16 first waits for a message from process 1 to be received before continuing, regardless of the order in which messages were actually sent. Thus, the structure of lines 14-17 imposes a serialization on the order in which messages are processed and printed by process 0, so the output above shows the message from process 1 first, followed by process 2, and finally process 3.  (We do not have to process the messages in this particular order. For example,  &lt;A HREF="https://computing.llnl.gov/tutorials/mpi/man/MPI_Probe.txt"&gt;MPI_Probe&lt;/A&gt; or &lt;A HREF="https://computing.llnl.gov/tutorials/mpi/man/MPI_Waitany.txt"&gt;MPI_Waitany&lt;/A&gt; can be used to process messages in the order in which they arrive).
&lt;p&gt;


The send/receive functions &lt;tt&gt;mpi-send-auto&lt;/tt&gt; and &lt;tt&gt;mpi-receive-auto&lt;/tt&gt; are wrappers around the &lt;A HREF="http://linux.die.net/man/3/mpi_send"&gt;MPI_Send&lt;/A&gt; and &lt;A HREF="http://linux.die.net/man/3/mpi_recv"&gt;MPI_Recv&lt;/a&gt; functions, but they do a bit more work than their C counterparts. Both &lt;tt&gt;MPI_Send&lt;/tt&gt; and &lt;tt&gt;MPI_Recv&lt;/tt&gt; requires that the type and size of the objects be fully specified. The "-auto" part of &lt;tt&gt;mpi-send-auto&lt;/tt&gt; and &lt;tt&gt;mpi-receive-auto&lt;/tt&gt; implement a simple protocol by which any object can be sent and received without specification of type or size. This is convenient in cases where communications efficiency is not critical. CL-MPI also provides lower-level, functions which map more directly to &lt;tt&gt;MPI_Send&lt;/tt&gt; and &lt;tt&gt;MPI_Recv&lt;/tt&gt; so that you can squeeze out performance, but even for applications where every bit of performance matters, it's nice to be able to use the &lt;tt&gt;-auto&lt;/tt&gt; versions during the development phase (you know, the whole "dynamic language" thing...)
&lt;p&gt;

So that was a simple example of point-to-point communications.  Coming up: more complicated examples that show what parallel programming in CL-MPI is actually good for.
&lt;p&gt;

&lt;hr&gt;
&lt;A NAME="1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; Warning -- if you actually try this, you'll have to kill all of the SBCL processes  -- as can be expected when we have multiple Lisp processes all contending for the standard in/out, I/O in MPI when multiple REPLs get invoked is a messy issue.
If you were puzzled about all this talk of &lt;em&gt;running things from the command line in Lisp&lt;/em&gt;, it should be clear now -- the multi-process MPI runtime and the usual way of using Lisp with interactive REPLs don't mix too well.  There can be some issues with debugging and thread-based parallelism, but with process-based parallelism such as MPI, the problem is worse. On the other hand, the REPLs &lt;b&gt;do&lt;/b&gt; work in a more or less reasonable way, but you just have to be careful how you use them (maybe I'll write a post on parallel debugging at some point). Also, the debugging situation with CL-MPI is no worse than with any other MPI language binding -- this is the price to be paid for a framework that allows parallelism across different machines.
&lt;p&gt;

&lt;A NAME="2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; MPI Communicators (what "Comm" stands for) are more complex than that, but let's ignore that for now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-1871138560343461320?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/1871138560343461320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/06/message-passing-hello-world-in-cl-mpi.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/1871138560343461320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/1871138560343461320'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/06/message-passing-hello-world-in-cl-mpi.html' title='A message-passing   &quot;Hello World&quot; in CL-MPI'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-6237185331660657944</id><published>2009-05-31T07:56:00.000-07:00</published><updated>2009-05-31T20:55:25.193-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>Parallel Programming in Common Lisp</title><content type='html'>&lt;p&gt;
To kick off a series of posts about parallel programming in Common Lisp, let's first review what's available.&lt;p&gt;
&lt;H3&gt; Two models for parallel programming &lt;/H3&gt;
First, there are basically two models for parallel programming:&lt;p&gt;
&lt;UL&gt;
&lt;LI&gt;Parallelism based on threads (a.k.a. shared-memory model). Multiple threads are spawned by a single process. This is the most common approach in a multi-core machine. For example, we can spawn one computational thread per core. Memory can be shared among threads, so communications between computations is usually implemented by threads writing to shared memory spaces.&lt;p&gt;
&lt;LI&gt;Parallelism based on message passing (a.k.a. distributed memory model). This is the most common and natural approach in a cluster of machines, because in a cluster of commodity machines, there's no hardware support for sharing RAM among multiple machines. Communication among computations is done by explicitly sending message structures according to some messaging protocol.&lt;p&gt;
&lt;/UL&gt;
It is possible to mix these two models. That is, we can have message passing among multi-core machines, and within each machine, use thread-based parallelism.&lt;p&gt;

&lt;H3&gt; Threads in Common Lisp&lt;/H3&gt;

When discussing threads, we first have to distinguish between &lt;i&gt;native threads&lt;/i&gt; and &lt;i&gt;non-native threads&lt;/i&gt;.  Native threads are handled by the operating system. Native threads allow us to take advantage of multiple cores. For example, in a 4-core machine, we can spawn 4 computational threads that are executed in parallel.  Non-native threads are handled by the Lisp implementation, and do not allow true parallelism. If we spawn multiple non-native threads, they are executed sequentially (the threads yield control, usually cooperatively, to each other). So, when talking about parallel processing, we are really interested in native threads.&lt;p&gt;
Some languages such as Java provide a standardized threading library. The Common Lisp standard does not include threads, so it is up to the implementations to provide threading facilities.
Although there is no standard thread API for Common Lisp,  &lt;A HREF="http://common-lisp.net/project/bordeaux-threads/"&gt;Bordeaux-Threads&lt;/a&gt; is an attempt to provide a portable layer on top of the implementation-specific APIs.&lt;p&gt;
Here's a table of threading support for most of the current Common Lisp implementations.&lt;p&gt;
&lt;table border="1"&gt;
&lt;TR&gt;&lt;td&gt;&lt;/td&gt; &lt;td&gt;Native&lt;/td&gt;&lt;td&gt; Non-native&lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;ABCL&lt;td&gt; all platforms &lt;/td&gt; &lt;td&gt;  - &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;Allegro&lt;td&gt; Windows &lt;/td&gt; &lt;td&gt;  other platforms &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;CLISP       &lt;td&gt; -  &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;CMUCL     &lt;td&gt; - &lt;/td&gt; &lt;td&gt; x86 (Linux,BSD)&lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;Clozure CL&lt;td&gt; all platforms &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;Corman CL &lt;td&gt; Windows &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;GCL       &lt;td&gt; -  &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/TR&gt;
&lt;TR&gt;&lt;td&gt;Scieneer  &lt;td&gt; all platforms  &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/Tr&gt;
&lt;TR&gt;&lt;td&gt;SBCL      &lt;td&gt; X86 (Linux,BSD)  &lt;/td&gt; &lt;td&gt; - &lt;/td&gt;&lt;/TR&gt;
&lt;/table&gt;
So, native thread support is not universal.  If we're interested in parallelizing computations in Common Lisp, this is unfortunate, if a particular implementation of interest happens to not support native threading on the selected platform.  You might think that since SBCL offers native threading on x86, and Clozure CL offers native threading everywhere, we're in pretty good shape.  However, suppose that we want to use CLISP because the computational bottleneck in our application is bignum arithmetic, and CLISP has much faster bignum arithmetic than the other implementations -- no dice.  Also, a particular application may require the use of non-portable code or libraries which require a particular implementation that doesn't support native threads for a required platform.&lt;p&gt;

&lt;H3&gt; Message-Passing in Common Lisp&lt;/H3&gt;
Message passing is less commonly used than threading in most languages (with the notable exception of Erlang). As far as I know, there is only one implementation which supports parallelism based on message passing, which is GCL, which includes an interface to MPI, &lt;A HREF="http://www.ccs.neu.edu/home/gene/pargcl.html"&gt;originally by Gene Cooperman&lt;/A&gt;.&lt;p&gt;

&lt;H3&gt; Coming up: A New, Portable Message-Passing Library for Common Lisp&lt;/H3&gt;
I've been working on &lt;A HREF="http://code.google.com/p/cl-mpi/"&gt;CL-MPI&lt;/A&gt;, a portable, CFFI-based binding for the &lt;A HREF="http://en.wikipedia.org/wiki/Message_Passing_Interface"&gt;MPI&lt;/A&gt; message passing library. CL-MPI enables portable, parallel programming in Common Lisp using a message-passing model on either a cluster of machines, or a single multicore machine. I have just made the most recent version (0.2.1) available for download. So far, I've tested CL-MPI with SBCL and CMUCL on Linux.

I initially developed CL-MPI to do massively parallel computations on a large cluster at my university. But since we can run MPI on a single multi-core machine, one interesting use for CL-MPI is to provide a "poor man's multiprocessing solution" for some Common Lisp implementations which don't have native threading capabilities, such as CMUCL and CLISP.

Unfortunately, CL-MPI is not really well documented yet, and as a prerequisite, you'll need to have basic understanding of the MPI programming model (&lt;A HREF="https://computing.llnl.gov/tutorials/mpi/"&gt;here is an excellent tutorial&lt;/A&gt;).

I'll write a series of posts describing CL-MPI and some example applications, and hopefully, this will be the basis of a tutorial. Stay tuned...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-6237185331660657944?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/6237185331660657944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/05/parallel-programming-in-common-lisp.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/6237185331660657944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/6237185331660657944'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/05/parallel-programming-in-common-lisp.html' title='Parallel Programming in Common Lisp'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-2651157871414130463</id><published>2009-05-29T22:48:00.000-07:00</published><updated>2009-05-31T06:17:58.750-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>CFFI vs UFFI performance</title><content type='html'>The current, de facto portable &lt;A HREF="http://common-lisp.net/project/cffi/manual/cffi-manual.html#Tutorial"&gt;foreign function interface (FFI)&lt;/A&gt;  for Common Lisp is &lt;A HREF="http://common-lisp.net/project/cffi/"&gt;CFFI&lt;/a&gt;. An alternative to CFFI is &lt;A href="http://uffi.b9.com/"&gt;UFFI&lt;/a&gt;, a slightly older FFI created by Kevin Rosenberg.&lt;p&gt;
The CFFI package includes a compatibility layer for UFFI called CFFI-UFFI-COMPAT, which can be used as a drop-in replacement for UFFI -- in other words, if CFFI is installed on our machine, we can use systems that depend on UFFI simply by changing the dependency  in the ASDF system definition from "UFFI" to "CFFI-UFFI-COMPAT". The obvious benefit is that installing UFFI becomes unnecessary.&lt;p&gt;
A surprising side benefit can be improved performance.&lt;p&gt;
I recently tried using &lt;A HREf="http://www.weitz.de/cl-gd/"&gt;CL-GD&lt;/A&gt;, a FFI wrapper for the &lt;A HREf="http://www.boutell.com/gd/"&gt;GD&lt;/a&gt; graphics library. CL-GD uses UFFI. I implemented a simple function which compares the RGB components of both images.
&lt;pre&gt;
(defun square (x)
  (the fixnum (* (the fixnum x) (the fixnum x))))

(defun compare-images (image1 image2)
 "Compare two images -- uses API exported by CL-GD"
 (declare (optimize (debug 0) (speed 3)(safety 0)))
 (let ((err 0)
       (height (cl-gd:image-height image1))
       (width (cl-gd:image-width image1)))
   (declare (fixnum err height width))
   (loop for y fixnum from 0 below height do
         (loop for x fixnum from 0 below width do
               (let ((img-color (cl-gd:get-pixel y x :image image2))
                     (target-color (cl-gd:get-pixel y x :image image1)))
                 (incf err (square (- (the fixnum (cl-gd:color-component 
                                                        :red img-color :image image2))
                                      (the fixnum (cl-gd:color-component 
                                                        :red target-color :image image1)))))
                 (incf err (square (- (the fixnum (cl-gd:color-component 
                                                        :blue img-color :image image2))
                                      (the fixnum (cl-gd:color-component 
                                                        :blue target-color :image image1)))))
                 (incf err (square (- (the fixnum (cl-gd:color-component 
                                                        :green img-color :image image2))
                                      (the fixnum (cl-gd:color-component 
                                                        :green target-color :image image1))))))))
   err))
&lt;/pre&gt;
&lt;p&gt;
First, the timing with the standard CL-GD:
(SBCL 1.0.28, CL-GD 0.5.6, UFFI-1.6.1, 3GHz Intel Core2 Duo, Linux))&lt;p&gt;
&lt;pre&gt;
EVO-LISA&gt; (progn
 (defparameter *image1* (cl-gd:create-image-from-file "023.JPG"))
 (defparameter *image2* (cl-gd:create-image-from-file "023.JPG"))
 (time (compare-images *image1* *image2*)))

height1=1024, height2=768
Evaluation took:
 1.684 seconds of real time
 1.688105 seconds of total run time (1.688105 user, 0.000000 system)
 100.24% CPU
 5,061,067,326 processor cycles
 141,296 bytes consed
0
&lt;/pre&gt;&lt;p&gt;

1.68 seconds to compare a pair of 1024x768 images is &lt;b&gt;extremely&lt;/b&gt; slow. Since iterating over a couple of 1024x768 arrays and computing their difference in Common Lisp doesn't take anything close to 1.7 seconds (see below), it seemed that the bottleneck must be in the FFI.&lt;p&gt;
For many applications, the FFI overheads don't really matter that much, because the goal of using the FFI is to use a library written in another language without having to reimplement the library in Lisp. On the other hand, another reason for using foreign libraries is to use fast libraries implemented in C. Since CFFI is being used for low-level libraries such as &lt;A HREF="http://common-lisp.net/project/iolib/"&gt;IOLib&lt;/a&gt;, where performance matters, I thought I'd try  using the UFFI compatibility layer for CFFI.&lt;p&gt;
All I did was edit the cl-gd.asd file so that the dependency on UFFI was replaced by a dependency on CFFI-UFFI-COMPAT  (CL-GD already depends on CFFI-UFFI-COMPAT for CLISP and OpenMCL, but by default, depends on UFFI for SBCL).&lt;p&gt;
The improvement wasimpressive - 0.336 seconds using CFFI-UFFI-COMPAT, comapred to 1.68 seconds with UFFI, a 5x speedup.&lt;p&gt;
&lt;pre&gt;
EVO-LISA&gt; (progn
 (defparameter *image1* (cl-gd:create-image-from-file "023.JPG"))
 (defparameter *image2* (cl-gd:create-image-from-file "023.JPG"))
 (time (compare-images *image1* *image2*)))

height1=1024, height2=768
Evaluation took:
 0.336 seconds of real time
 0.336021 seconds of total run time (0.336021 user, 0.000000 system)
 100.00% CPU
 1,010,111,976 processor cycles
 21,712 bytes consed
&lt;/pre&gt;
&lt;p&gt;
Of course, there is still a massive overhead due to accessing pixel information through the CL-GD API, which is partially due to the FFI layer, partially due to GD, and partially due to CL-GD. How large is this overhead? Here's a simple function which compares two color arrays written in pure Common Lisp: This is not even that optimized -- it doesn't even declare the types of image1 and image2.&lt;p&gt;
&lt;pre&gt;
(defstruct color
  (red 0 :type fixnum)
  (blue 0 :type fixnum)
  (green 0 :type fixnum))
(defun compare-2darrays (image1 image2 height width)
 "Compare two images -- uses API exported by CL-GD"
 (declare (fixnum  height width))
 (declare (optimize (debug 0) (speed 3)(safety 0)))
 (let ((err 0))
   (declare (fixnum err height width))
   (loop for y fixnum from 0 below height do
          (loop for x fixnum from 0 below width do
               (let ((color1 (aref image1 y x))
                     (color2 (aref image2 y x)))
                 (incf err (square (- (color-red color1) (color-red color2))))
                 (incf err (square (- (color-blue color1) (color-blue color2))))
                 (incf err (square (- (color-green color1) (color-green color2)))))))))
&lt;/pre&gt;

The results?

&lt;pre&gt;
CL-USER&gt; (let ((img1 (make-array '(1024 768) :initial-element (make-color)))
               (img2 (make-array '(1024 768) :initial-element (make-color))))
           (time (compare-2darrays img1 img2 1024 768)))
Evaluation took:
  0.048 seconds of real time
  0.048003 seconds of total run time (0.048003 user, 0.000000 system)
  100.00% CPU
  143,562,681 processor cycles
  0 bytes consed  
&lt;/pre&gt;&lt;p&gt;

0.048 seconds to perform basically the same operations (iterate over both arrays, computing the square of their difference, compared to 0.336 seconds for the code which accesses CL-GD via CFFI.&lt;p&gt;
I considered the overhead within CFFI/UFFI, but there's another source of inefficiency, which is the CL-GD binding code. As clearly stated in the docs, CL-GD wasn't designed for speed, and in order to provide a nice API, there is some overhead in the binding code. I'm abusing CL-GD by using it for repetitive pixel-level access because GD is a image creation library and not an image processing library, but I needed to load and create some GIF files, and the first library I ran into was CL-GD.&lt;p&gt;
Here is another version of the CL-GD based code where I bypass the officially exported CL-GD API and use some internal functions (I know some people don't like it, but being able to access internal symbols with "::" is a great feature of Common Lisp, imho).&lt;p&gt;
&lt;pre&gt;
(defun compare-images-raw (image1 image2)
 "Compare two images -- use internal functions to eliminate overhead
added by CL-GD API"
 (declare (optimize (debug 0) (speed 3)(safety 0)))
 (let ((err 0)
       (height (cl-gd:image-height image1))
       (width (cl-gd:image-width image1))
       (img1 (cl-gd::img image1))
       (img2 (cl-gd::img image2)))
   (declare (fixnum err height width))
   (loop for y fixnum from 0 below height do
         (loop for x fixnum from 0 below width do
               (let ((img2-color (cl-gd:get-pixel y x :image image2))
                     (img1-color (cl-gd:get-pixel y x :image image1)))
                 (incf err (square (- (the fixnum(cl-gd::gd-image-get-red img2 img2-color))
                                      (the fixnum(cl-gd::gd-image-get-red img1 img1-color)))))
                 (incf err (square (- (the fixnum(cl-gd::gd-image-get-green img2 img2-color))
                                      (the fixnum(cl-gd::gd-image-get-green img1 img1-color)))))
                 (incf err (square (- (the fixnum(cl-gd::gd-image-get-blue img2 img2-color))
                                      (the fixnum(cl-gd::gd-image-get-blue img1 img1-color))))))))
   err))
&lt;/pre&gt;


The results:
&lt;pre&gt;
EVO-LISA&gt; (progn
           (defparameter *image1* (cl-gd:create-image-from-file "023.JPG"))
           (defparameter *image2* (cl-gd:create-image-from-file "023.JPG"))
           (time (compare-images-raw *image1* *image2*)))

height1=1024, height2=768
Evaluation took:
 0.282 seconds of real time
 0.284018 seconds of total run time (0.284018 user, 0.000000 system)
 100.71% CPU
 845,516,097 processor cycles
 21,936 bytes consed
&lt;/pre&gt;


So we're down to 0.282 seconds from 0.336 seconds. Noticeable, but still a long ways to go from the raw CL code. If this was an important enough example (it's not), I could try, for exmaple,  eliminating the FFI overhead by digging around the GD API and possibly modifying it to implement a quicker way to grab an entire image at once, instead of pixel by pixel.&lt;p&gt;
I haven't looked deeper into why CFFI turned out to be 5x faster than UFFI for this example, and there may be examples where UFFI is faster. But the lesson remains the same: (1) FFI can in the wrapper libraries we use can be a peformance bottleneck, (2) CFFI-UFFI-COMPAT offers a quick way to swap in CFFI for UFFI.&lt;p&gt;

I sent a summary of the above to Edi Weitz, author of CL-GD (and many other great libraries!), and he suggested that I send a patch, after more testing -- unfortunately, CL-GD failed a couple of tests in the test suite and I don't have time to dig around right now. Maybe a little later...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-2651157871414130463?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/2651157871414130463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/05/cffi-vs-uffi-performance.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/2651157871414130463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/2651157871414130463'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/05/cffi-vs-uffi-performance.html' title='CFFI vs UFFI performance'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-4156887797676684135</id><published>2009-05-29T19:59:00.000-07:00</published><updated>2009-05-31T06:17:58.750-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>CFFI-Grovel Rocks!</title><content type='html'>This weekend, I was working on  &lt;a href="http://code.google.com/p/cl-mpi/"&gt;CL-MPI&lt;/a&gt;, my Common Lisp bindings for MPI, a standard message passing interface which allows distributed memory parallel programming based on message passing.I had previously developed and tested CL-MPI with  &lt;a href="http://www.mcs.anl.gov/research/projects/mpi/mpich1/"&gt;MPICH1.2&lt;/a&gt;, and I wanted to see how it worked with the latest version of &lt;a href="http://www.mcs.anl.gov/research/projects/mpich2/"&gt;MPICH2&lt;/a&gt;.

Since the bindings are implemented with &lt;a href="http://common-lisp.net/project/cffi/"&gt;CFFI&lt;/a&gt;, I didn't expect any problems. However, the test suite started behaving in a very bizarre manner, crashing on some tests and freezing on others.
I was stumped for a while, but the the investigation was an eye-opener:

I needed to use a struct defined as follows in MPICH 1.2
&lt;pre class="htmlize"&gt;
typedef struct {
 int count;
 int MPI_SOURCE;
 int MPI_TAG;
 int MPI_ERROR;
#if (MPI_STATUS_SIZE &gt; 4)
 int extra[MPI_STATUS_SIZE - 4];
#endif
} MPI_Status;&lt;/pre&gt;

so I had declared the foreign struct in &lt;a href="http://code.google.com/p/cl-mpi/source/browse/trunk/mpi.lisp?spec=svn9&amp;amp;r=7#65"&gt;my binding source (line 65)&lt;/a&gt;:
&lt;pre&gt;
(cffi:defcstruct MPI_Status
 (count &lt;span class="builtin"&gt;:int&lt;/span&gt;)
 (MPI_SOURCE &lt;span class="builtin"&gt;:int&lt;/span&gt;)
 (MPI_TAG &lt;span class="builtin"&gt;:int&lt;/span&gt;)
 (MPI_ERROR &lt;span class="builtin"&gt;:int&lt;/span&gt;))&lt;/pre&gt;

This basically specifies that MPI_Status is a C struct with int slots, and says that the first 4 bytes (int) of the  struct is the count field, the next int is the MPI_SOURCE, and so on. Basically a straightforward translation of the C struct definition.

This worked great --  until I tried to use the bindings with MPICH2.
Running the CL-MPI test suite resulted in mysterious crashes and freezes -- not a happy transition to from MPICH to MPICH2! After thrashing around trying to isolate the problem, it turns out that in MPICH2, the MPI_Status struct definition had been changed to:
&lt;pre&gt;
typedef struct MPI_Status {
   int count;
   int cancelled;
   int MPI_SOURCE;
   int MPI_TAG;
   int MPI_ERROR;
  
} MPI_Status;&lt;/pre&gt;
Oops! No wonder everything got messed up --
Note the new 'cancelled' field inserted after the count field.
The fields of the struct no longer aligned with the defcstruct defintion above! &lt;p&gt;
OK, so what to do? I could create two different versions of the defcstruct, one which works with MPICH1.2 and the other for MPICH, and then use conditional compilation (e.g., #+MPICH1 #+MPICH2...). The problem with that solution is that if the C definition of MPI_Status changes yet again in a future MPICH release, then we are back to square one.&lt;/p&gt;&lt;p&gt;

In this case, the solution is to not directly use the defcstruct to create a foreign struct definition. Instead, &lt;a href="http://common-lisp.net/project/cffi/manual/html_node/The-Groveller.html"&gt;CFFI-Grovel&lt;/a&gt; can be used to automagically generate the correct defcstruct.
This is the file I created to use CFFI-Grovel for CL-MPI.
&lt;a href="http://code.google.com/p/cl-mpi/source/browse/trunk/mpi-grovel.lisp#47"&gt;Here (Line 47)&lt;/a&gt;, I add the CFFI-Grovel declaration:
&lt;/p&gt;
&lt;pre&gt;
(cstruct MPI_Status "MPI_Status"
 (count "count" :type :int)
 (MPI_SOURCE "MPI_SOURCE" :type :int)
 (MPI_TAG "MPI_TAG" :type :int)
 (MPI_ERROR "MPI_ERROR" :type :int))
&lt;/pre&gt;
This states that I want to use a foreign (C) struct named "MPI_Status", giving it the Lisp name MPI_Status, and that I am interested in 4 integer fields: count, MPI_SOURCE, MPI_TAG, MPI_ERROR. This declaration does &lt;b&gt; not&lt;/b&gt; specify anything about the ordering of the slots in the C struct. It also does not say anything about the completeness of this mapping. In other words, the C MPI_Status struct can contain other fields which are not mentioned in this CFFI-grovel cstruct definition. In contrast, the original CFFI defcstruct used above is a more concrete declaration, which completely specifies the memory layout of the C struct we are mapping.

The CFFI-Grovel based code does the right thing for both MPICH1.X and the latest MPICH2, and if future versions of MPICH2 change the MPI_Status struct definition by chaning field order or adding fields, no problem.

&lt;u&gt;Lesson learned&lt;/u&gt;: before directly declaring a foreign object with CFFI, consider whether CFFI-Grovel might be more appropriate. Using CFFI-Grovel is more robust to changes in the library to which we're binding, and if I had done this to begin with, it would have saved me several hours of painful debugging.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-4156887797676684135?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/4156887797676684135/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/05/cffi-rocks.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/4156887797676684135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/4156887797676684135'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/05/cffi-rocks.html' title='CFFI-Grovel Rocks!'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8624700733410390433.post-3638941598626270000</id><published>2009-05-27T06:08:00.000-07:00</published><updated>2009-05-31T06:17:58.750-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lisp'/><title type='text'>(format blog-stream "hello world")</title><content type='html'>This blog will focus on Lisp programming. I'm mostly using Common Lisp for &lt;a href="http://alexf04.maclisp.org"&gt;my AI and algorithms research&lt;/a&gt;, so entries will be related to issues I run into while I use Lisp for work.
Hopefully, these will be useful for others interested in using Lisp for algorithmic research.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8624700733410390433-3638941598626270000?l=risupu.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://risupu.blogspot.com/feeds/3638941598626270000/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://risupu.blogspot.com/2009/05/format-blog-stream-hello-world.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/3638941598626270000'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8624700733410390433/posts/default/3638941598626270000'/><link rel='alternate' type='text/html' href='http://risupu.blogspot.com/2009/05/format-blog-stream-hello-world.html' title='(format blog-stream &quot;hello world&quot;)'/><author><name>AlexF</name><uri>http://www.blogger.com/profile/06190211125016254854</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
