Mixalot is the audio back-end of Shuffletron, factored into its own system(s) because it might be useful for other purposes. It includes a mixer which pulls audio from any number of streamer objects and outputs them to ALSA. It also includes FFI definitions for libmpg123 with some helpers for decoding MP3 files and reading ID3 tags, and a streamer class for decoding and playing MP3 files in real time. The libmpg123 portions are usable independently of the audio mixing/output code.
#July 1, 2009 07:09 AMThe European Common Lisp Meeting will be in Hamburg, on the weekend of September 12 and 13, 2009. I greatly enjoyed last year’s ECLM, in Amsterdam. It’s relaxed and gives you a lot of opportunity to meet great Lisp experts from all over the world. Arthur Lemmens and Edi Weitz did a superb job arranging for entertainment and space, and making sure everyone was happy.
I’m also looking forward to seeing Hamburg; I’ve never been there, and it sounds great.
I’m giving a talk entitled A Highly-Available Large-Scale Transaction Processing System in Common Lisp. It’s about the airline reservation system that we’re building at ITA Software, specifically about the issues involved in using Common Lisp, which is not widely thought of as being a language for writing large-scale transaction processing.
The lightning talks at the International Lisp Conference last March went so well that Edi and Arthur are trying out this format at the ECLM. After the ILC, someone told me that at another sofware-related conference he had been to, the lightning talks fell flat: few people signed up to give talks, and they weren’t very good. At the ILC, I thought they were nearly all great. We learned about new tools, stories, and so on. There was a great one about using Lisp in a Lisp-unfriendly world. In a nutshell: if they force you to program in PERL, then run a PERL-coded Scheme interpreter and write in Scheme! I anticipate more fun lightning talks in Hamburg!
So, I encourage you to join the fun!
#June 30, 2009 11:40 AM
(eval-when (:compile-toplevel) (declaim (optimize speed)))to ensure that the body is evaluated in the dynamic execution context of the compiler which makes a practical difference on Allegro. It goes without saying that you don't want (PROCLAIM '(OPTIMIZE ...)) in a library, either.
SBCL has always had a fairly efficient MAKE-INSTANCE -- as long as the class argument is a quoted symbol and all the keywords in initargs are constant as well.
This is due to what are called ctors. Very briefly, a call such as
(make-instance 'point :x x :y y)
is transformed into something along the lines of
(let ((#:ctor (load-time-value (ensure-ctor 'foo :x :y)))) (funcall #:ctor x y))
where CTOR is a funcallable instance. The first time it is called, an optimized constructor is generated, saved as the funcallable instance function, and finally called with the provided arguments. Later calls get the optimized constructor ready made.
There are multiple efficiency gains here: initargs can be checked for validity at compile time, and (assuming certain things hold true about the metaclass of the class and its methods) essentially the whole MAKE-INSTANCE -> ALLOCATE-INSTANCE | INITIALIZE-INSTANCE -> SHARED-INITIALIZE call tree can be open coded, and slot accesses can be performed using STANDARD-INSTANCE-ACCESS with constant slot locations. This all works with class redefinitions as ctors are updated automatically.
Unfortunately, this made the performance of non-constant class arguments to MAKE-INSTANCE extremely unattractive in comparison: 10-100 times slower is fairly typical. Ouch!
At ELS 2009 Didier Verna asked if we could do something similar for the non-constant case as well. Turns out we can:
(make-instance class :x x :y y)
can become something like
(let* ((#:cache (load-time-value (cons 'ctor-cache nil)))
(#:store (cdr #:cache)))
(multiple-value-bind (#:ctor #:new-store)
(ensure-cached-ctor class :x :y #:store)
(setf (cdr #:cache) #:new-store)
(funcall #:ctor x y)))
where the ctor corresponding to the class argument is first looked for in the cache: if it is not found a new one is generated and cached.
This was implemented in SBCL 1.0.29.41; now the non-constant class arguments performs much better, being roughly only 1.15-1.25 times slower than the constant ones. Hopefully this will make a real difference in some applications out there!
A bit later another ctor optimization improvement was implemented, improving cases where the full open coding could not be done due to defined methods, but where we are still able to check initargs at compile time and replace the MAKE-INSTANCE generic function call with a slightly faster normal function call to a simplified constructor function: this makes such calls 4-10 times slower than the fast path, but still considerably faster than the worst case scenario.
The message to take home is actually not about SBCL optimizations -- as much as I like to talk about them: I think ctors are an excellent example of compiler macros doing inline caching and specialization. A nice technique that could probably be used more often than it is.
Note: SBCL inherited its ctor optimizations from CMUCL: they are the work of the estimable Gerd Moellmann. Things described above do not replace his work, just extend it around the edges. Those interested in the gory details can look at src/pcl/ctor.lisp in the SBCL source tree.
#June 29, 2009 08:50 AMComplex single- and double- floats used to be represented as pairs of SSE registers on the x86-64 port. Doing so simplified the porting work, since more code could be shared with other backends. Representing complexes packed in SSE registers as would be obviously preferable was left for the future. Luckily, most of my work on SSE intrinsics is directly applicable to that task: all the SSE{1,2} instructions were finally defined, and modifying a pre-existing primitive object type is much simpler than adding a new one.
The initial work was mostly straightforward, since, by default, the only operations on complexes are moves (to/from registers, the stack/arguments, vectors, or the heap [as boxed objects]), creation from scalar components, and extraction of components. The modifications mostly entailed modifying the way register allocation manipulates complexes (they only need a single SSE register, like scalar values), and making the boxed representation more SSE-friendly (alignment, packing single floats for MOVQ). There's only one problem I can remember in that part of the code: the stack isn't guaranteed to be aligned on 16 byte boundaries! That means that I'm forced to use MOVUPD when moving complex double floats between the stack and an SSE register.
Already, using half the registers and half the memory accesses is interesting. The real pay-off with packed complexes, however, is the ability to use vector instructions in arithmetic operations. The obvious cases are addition and subtraction (both complex-complex and complex-real), which exhibit natural horizontal parallelism: each pair of components (real-real or imaginary-imaginary) can be processed separately. For complex-real multiplication or division, things are only slightly more complex: the real operand has to be duplicated before operating in parallel. I encountered two important bugs in that code.
First, scalar values were still passed around with narrow register-register moves (movsd or movss). When we only ever manipulated scalars, that wasn't an issue. Complex-real addition and subtraction, however, really want to exploit the fact that the high part of a scalar value is filled with 0. That's not the case when a narrow move is used to move a single float value into a register that used to hold a double... Using full register moves instead (for both complex and scalar values) not only made the code correct, but also improved performance, due to their being simpler for the SSE pipeline to handle.
Second, packed single float division executes four divisions, not two. Thus, it's not enough to duplicate the real operand in a complex-real division before executing a packed division: depending on the exception mask, the 0.0/0.0 in the higher half of the result may trigger an arithmetic exception. The simplest solution I found was to perform the same operation in both halves of the operands. Obviously, an exception will be signalled iff operating only on the lower half would have signalled too. The cost is one additional shuffle step for the complex value: the real value always has to be duplicated, and the high half of the result must be reset to 0 anyway.
That leaves complex-complex multiplication, and complex-complex/real-complex division. Complex multiplication is a fairly common operation; common enough for Intel to pretty much dedicate an instruction to that operation (ADDSUBP{S,D}) in SSE3. Even when restricting ourselves to SSE2, the code is fairly simple. The most unnatural part is computing the conjugate, given the absence of ADDSUB (more on that later). Complex division, however, is a much more complicated beast. I decided to stick to an implementation in CL, but rewritten to use block (complex-at-a-time) instead of component-wise arithmetic. In the end, it still wanted an additional operation: swapping the real and imaginary components of a complex.
Finally, that leaves non-arithmetic operations, like equality tests (both bitwise and as numbers), conjugate and negation. Once there's a guarantee that the unused components in a SSE register are 0-filled (a guarantee that is supported by most load-from-memory instructions), testing for equality is very straightforward. Computing the conjugate of a complex or negating one, on the other hand, isn't so nice. One look at the way floats are laid out in register shows that they may be implemented as simple bit-wise operations. That leaves the problem of loading the relevant constants in. That's where a later patch comes in.
Negating and taking the absolute value of a real requires constants for bit-wise operations, like conjugating or negating a complex. Until now, SBCL generated code that loaded the constant in a GPR, and then moved it from the GPR into an SSE register. Generating the constant usually required some shifting (often slow), and moving data between the GPR and SSE register files takes a relatively long time on current microarchitectures. Loading such constants from memory directly into SSE registers would be preferable. However, by the time assembly code is generated, it's too late to add constants, and some of these constants would have to be boxed (be it as fixnums or bignums). In other words, both not feasible and not always a good idea.
A second patch adds support for unboxed constants stored inline, after the executable code. The system must already support code relocation, so the moving GC isn't an issue more than it already is (RIP-relative addressing on x86-64, and post-GC fixups on x86). I haven't bothered to look at the situation for other platforms yet, but, with some luck, things will be similarly simple. Storing constants in the code segments was already possible, with a small bit of hacking. What the patch mostly offers is the ability to pack the constants together. The advantage is that they can be stored far enough away from code (one cache line) to not confuse caches, and that a more global outlook allows the code generator to merge identical constants together and to reorder them to guarantee sufficient alignment without wasting too much space.
With that infrastructure in place, it was easy to generate much simpler and faster code for the previous bitwise operations on floats. It also became interesting to store some unboxed constants inline. On x86-64, I implemented that for constant floats (both real and complex) and fixnums that fit in registers, but can't be assembled as immediates (so +/- 2^29 and wider). On x86, fixnums can always be assembled as immediates, so only floats are stored as unboxed inline constants. Float constants are interesting to store inline because they're frequently found in speed-oriented code. Wider-than-fixnum constants, on the other hand, not so much. There's also a certain trade-off to consider: unboxed constants cause additional consing (and code bloat) each time they must be boxed.
Unboxed constants open the door for another set of improvements for x86oids: many instructions can load one operand directly from memory. Using that feature may save a register, and always makes the code smaller. The latter can often have a heavier impact on performance than the former. Using fewer registers mostly matters when the compiler has to spill values; that does not happen so often in inner loops on the x86-64. Smaller code, on the other hand, helps with memory (fits better in cache, needs less bandwidth) and reduces the pressure on the instruction decoder. Finally, when a constant shows up as an operand, it pretty much always pays off to store is inline, so even wider-than-fixnum integer constants are stored inline in that case.
The union of these changes transparently speeds up most complex float operations by up to ~100% on my Core 2. In the current version of Bordeaux-FFT, that translates into a 110% speed-up for 1024-point FFTs. Scalar operations also show significant improvements. Nikodemus Siivola's implementation of the (broken) mandelbrot routine at http://random-state.net/log/3452921796.html shows a 55% speed-up for hand-rolled complex arithmetic (within 20% of the execution time of the fastest g++ version). The complex-ful version shows a lesser speed-up, 38%, probably because a good portion of the runtime is spent operating element-wise to compute the 2-norm of complexes. If you have performance-sensitive code that performs a lot of floating point computations, you might be pleasantly surprised by this month's release.
P.S. Considering the size of these patches, I would probably be even more pleasantly surprised if they were bug-free. Bug reports (to sbcl-bugs, or, more realistically, directly to sbcl-devel) are always welcome, especially during the code freeze period starting today!
#June 29, 2009 06:00 AMHi everyone,
Auphelia Technologies is looking for highly skilled resources for work
in Gambit / JazzScheme, Web development and Database architecture.
Auphelia Technologies offers integrated management software, entirely
customized for small and medium-sized businesses, using innovative
technology and revolutionary features. Auphelia is located in
Montreal, Canada. See http://www.auphelia.com (under construction).
Gambit is a complete, portable, efficient and reliable implementation
of the Scheme programming language. See
http://dynamo.iro.umontreal.ca/~gambit.
JazzScheme is an open source programming language and cross-platform
framework built on top of Gambit . It includes a sophisticated IDE and
has been used for more than 10 years to develop high-quality
commercial software. See http://www.jazzscheme.org.
DESCRIPTION OF THE PROJECT
We are starting development on a new Enterprise Resource Planning
(ERP) software that we plan on making top of its class. Work will be a
complete rewrite and a major evolution of an existing product already
in use by a large client base.
Note that this project is a highly complex undertaking as we will not
be developing just another ERP software but a complete dynamic ERP
creation platform. Project is planned to last around 1 1/2 year for
the first release. Current team is composed of 10 people and is
planned to grow to around 25.
TECHNOLOGIES
After an exhaustive evaluation period, decision has been made to
develop the backend entirely in Gambit / JazzScheme. We still haven’t
decided between Qt and JazzScheme for our desktop client solution but
if the JazzScheme platform can be evolved to suit our needs, we have a
definite preference for using it accross the whole project.
EMPLOYMENT OPPORTUNITIES
We need resources for the following 4 positions :
LANGUAGE / FRAMEWORK DEVELOPPER
1 or 2 resources to work closely with Gambit and JazzScheme’s authors
to evolve the JazzScheme langage and framework during a period of
about 6 months. Planned work includes :
- Language
- Module / build system
- Performance / memory
- IDE / Remote debugger
- X11 and MacOS X frontends
Note that Gambit’s author Marc Feeley, has confirmed his participation
part time in the project!
Ideally, the resources we are looking for would have expert knowledge
in Lisp, Scheme, Gambit and JazzScheme! But realisticaly, if you don’t
know those but are brilliant, motivated and want to be part of an
exciting project that could put Scheme to the forefront, please send
us your resume!
PROJECT DEVELOPPER
1 or 2 resources to complete our existing team and work on the Desktop
client and / or Server layer. Knowledge of the following is a definite
plus :
- Lisp, Scheme, Gambit, JazzScheme
- GUI development
- Highly scalable server development
- Databases, SQL
WEB DEVELOPPER
1 resource to work on the Web client. We need a world class expert in
advanced web interfaces as the Web client will have to implement all
the functionality of the Desktop client. Knowledge of the following is
critical :
- Dynamic web development with AJAX
- Advanced web interfaces
- Javascript
- PHP
DATABASE ARCHITECT
1 resource to be lead in every database related work. Knowledge of the
following is critical :
- World class expertise in database modeling
- Databases : PostgreSQL (will probably be the first one supported),
Oracle, SQLite (for the offline mode)
Ideally we are looking for full time resources located near Montreal
but are definitely open to remote work and other options. Auphelia
Technologies offers competitive & flexible employment conditions.
PS: Please feel free to pass this announce to your friends!
Cheers,
Guillaume Cartier
gc@auphelia.com

A while back I read Christophe Rhodes's paper "SBCL: A Sanely-Bootstrappable Common Lisp" which describes SBCL's bootstrap procedures.
The paper includes a bunch of diagrams for each build stage. These were pretty helpful in improving my understanding of the build process. So, I tried to take them a step further and create a single diagram that provides a global overview of the build process:

I'm interested in hearing any comments you might have. If you already know how the build process works, does it make you cringe? If you are vaguely familiar with (parts of) the process, does it provide you with some sort of new insight? Given that I haven't included a legend, does it make any sense at all?
#June 24, 2009 08:41 PMThis sounds pretty cool:
So finally, here is the skinny on the Sunday, July 19th in the big conference room at Franz Inc: 6:00pm ~ Meet in front of the Franz office (2201 Broadway, Suite 715, Oakland, CA 94612). The kind folks at Franz Inc will herd us cats up to their big conference room before 6:15. 6:15pm ~ Introduction to Clojure Amit Rathore will give an introduction to Clojure aimed at lispers. 6:45pm ~ Milk and cookies (http://www.meetup.com/balisp/polls/189504/) 7:00pm ~ Coders at Work: The Lisp Perspective Peter Seibel talks about the interviews from his new book Coders at Work (http://www.codersatwork.com/), particularly what his subjects had to say about Lisp and Lisp related topics. Will include bits that didn't make it into the book. 7:30pm ~ Coders at Work QA 7:45pm ~ &optional Belgian beer outside and around the corner at Lukas Thanks for staying tuned, hope to see you there. For more info see: http://www.meetup.com/balisp/calendar/10589286/#June 24, 2009 04:56 PM


In the end, it was a surprisingly simple fix, after "startx 2>&1 > trace-file" gave me the crucial bits of info. An expected symbol was not around in a dynamic linking stage and chasing that down gave a simple(ish) fix. All I had to do, in the end, was to uninstall the fglrx driver (something I installed in the first place to get working accelerated 3D primitives and direct rendering).
But, it did made me wonder, if the Xorg server can write to stderr, why can't it log the lack of a symbol to the og file? Maybe, I don't know, because that writing happens in a non-X library? I should probably have a poke at that, at some point. #June 23, 2009 08:53 AM

-bash-3.2$ /usr/bin/time -v ./mandelbrot.gcc-6.gcc_run 16000 > /dev/null
User time (seconds): 19.26
System time (seconds): 0.08
Percent of CPU this job got: 795%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.43
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 8266
Voluntary context switches: 22
Involuntary context switches: 1001
-bash-3.2$ /usr/bin/time -v sbcl --core gcls_partitioned.core --noinform --eval "(gcls/opt/qtree2 16000 16000)" --eval '(quit)' > /dev/null
User time (seconds): 8.27
System time (seconds): 1.08
Percent of CPU this job got: 650%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.44
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 271688
Voluntary context switches: 206
Involuntary context switches: 488
-bash-3.2$ /usr/bin/time -v sbcl --core gcls_partitioned.core --noinform --eval "(gcls/opt/qtree2 16000 16000)" --eval '(quit)' > /dev/null
User time (seconds): 8.09
System time (seconds): 0.42
Percent of CPU this job got: 657%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.29
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 101670
Voluntary context switches: 105
Involuntary context switches: 449
(defun gcls/opt/qtree2 (width height &key; (max-iters 51) (nthreads 8) (partitions 10) (min-size 4)
(real-min -1.5d0) (real-max 0.5d0) (imag-min -1d0) (imag-max 1d0))
(declare (type fixnum width height max-iters min-size) (double-float real-min real-max imag-min imag-max))
(let* ((array (make-array (list height (ceiling width 8)) :element-type '(unsigned-byte 8)))
(done (make-array (list height width) :element-type 'bit :initial-element 0))
(cache (make-array (list height width) :element-type '(unsigned-byte 32) :initial-element 0))
(seq (make-array (array-total-size array) :element-type '(unsigned-byte 8) :displaced-to array))
(regions)
(region-mutex (sb-thread:make-mutex))
(real-step (/ (- real-max real-min) width)) ;;with fencepost error in spec
(imag-step (/ (- imag-max imag-min) height)))
(declare (type double-float real-step imag-step))
(labels ((set-bit (i j v)
(multiple-value-bind (byte bit) (truncate j 8)
(declare (fixnum byte bit))
(if (zerop v)
(setf (aref array i byte) (logandc2 (aref array i byte) (ash 128 (- bit))))
(setf (aref array i byte) (logior (aref array i byte) (ash 128 (- bit)))))
(setf (aref done i j) 1)))
(result-at (i j)
(when (zerop (aref done i j))
(iterate i j))
(multiple-value-bind (byte bit) (truncate j 8)
(if (logand (aref array i byte) (ash 128 (- bit))) 1 0)))
(iterate (i j)
(if (zerop (aref done i j))
(let ((res (count-iterations (+ real-min (* j real-step)) (- imag-max (* i imag-step)) max-iters)))
(declare (type (unsigned-byte 8) res))
(setf (aref cache i j) res)
(set-bit i j (if (= res max-iters) 1 0))
res)
(aref cache i j)))
(compute-region (i-min i-max j-min j-max)
(loop for i from i-min upto i-max
do (loop for j from j-min upto j-max
do (result-at i j))))
(fill-region (i-min i-max j-min j-max val)
(loop for i from i-min upto i-max
do (loop for j from j-min upto j-max
do (set-bit i j val))))
(quadtree (i-min i-max j-min j-max)
(declare (type fixnum i-min i-max j-min j-max))
(if (or (<= (- i-max i-min) min-size) (<= (- j-max j-min) min-size))
(compute-region i-min i-max j-min j-max)
(let ((proto (iterate i-min j-min)))
(if (and (loop for i from i-min upto i-max always (= proto (iterate i j-min)))
(loop for i from i-min upto i-max always (= proto (iterate i j-max)))
(loop for j from j-min upto j-max always (= proto (iterate i-min j)))
(loop for j from j-min upto j-max always (= proto (iterate i-max j))))
(fill-region i-min i-max j-min j-max (if (= proto max-iters) 1 0))
(let ((i-mid (+ i-min (truncate (- i-max i-min) 2)))
(j-mid (+ j-min (truncate (- j-max j-min) 2))))
(quadtree i-min i-mid j-min j-mid)
(quadtree i-min i-mid j-mid j-max)
(quadtree i-mid i-max j-min j-mid)
(quadtree i-mid i-max j-mid j-max))))))
(worker ()
(loop for p = (sb-thread:with-mutex (region-mutex) (pop regions)) until (null p)
do (apply #'quadtree p))))
(setf regions (part2d 0 height 0 width partitions))
(mapc #'sb-thread:join-thread (loop repeat nthreads collecting (sb-thread:make-thread #'worker)))
(with-open-stream (stream (sb-sys:make-fd-stream (sb-sys:fd-stream-fd sb-sys:*stdout*)
:element-type '(unsigned-byte 8) :buffering :full :output t :input nil))
(write-sequence (map 'list #'char-code (format nil "P4~%~D ~D~%" width height)) stream)
(write-sequence seq stream)))))
(defun part2d (i-min i-max j-min j-max steps)
(declare (type fixnum i-min i-max j-min j-max steps))
(labels ((partition (a b steps)
(declare (fixnum a b st eps))
(loop for n of-type fixnum downfrom (1+ steps) above 0
for i of-type fixnum = a then (+ i (truncate (- b i) n))
collecting i)))
(loop repeat steps
for a on (partition i-min i-max steps) nconc
(loop repeat steps
for b on (partition j-min j-max steps)
collect (list (car a) (cadr a) (car b) (cadr b))))))
Erik Naggum was the first person I killfiled in GNUS. His style was sometimes shockingly blunt and aggressive. After a while, though, I realized I was missing out, and I came to treasure the information and insight in his messages.
I learned yesterday that Erik died. I'm sorry to hear it; I occasionally contacted him to clarify or expand on some technical matter he wrote about in the past, and he was always helpful. I thought I would just be able to do that whenever I wanted, but now it's too late.
His death has, not surprisingly, led some people to go through the same initial experience I had, seeing some blunt and shocking language and wondering why anyone would care about its author. Here are some links that I hope show a small part of Erik's contributions to knowledge.
I think a newcomer would benefit from reading two in particular:
Here are the rest, taken from my bookmarks:
Unix solutions vs. Lisp solutions for the same problems
A lengthy explanation of types as they relate to CL
Programming in Lisp, delivering in some other language
A cute read macro dispatch scheme
Lazy-loading with SLOT-UNBOUND
Using CHANGE-CLASS for object "deletion"
"Unix quality" vs "Lisp quality", with sockets as an example
"if you can't outperform C in CL, you're too good at C. " (see the whole thread for details)
Destructors, finalizers, weak pointers
Kitchen hygiene compared to Lisp hygiene
An introduction, written just two months ago
The Long, Painful History of Time
The oil industry in Norway is really big
The "Norwegian Dream" (vs the American Dream) is to win the lottery
"most everything worth doing is associated with effort and some pain"
"Western culture is favorable to mediocre people and hostile to smart ones"
Core ideas behind SGML and XML
Feedback loops of lisp, reward, punishment, psychotic environments
"the market does not in fact lead anything or anywhere"
"Just let other people have their desires and needs. Do not let them affect yours."
"Which is the best car? How do you choose?"
Adapting emacs for rapid prose editing
The purpose of higher education
Erik's computer-oriented biography
A bibop-style memory management scheme
"people seem unable to get over the fact that they no longer want to use a language and just move on to something better" #June 21, 2009 11:47 AM
It's sad but seems to be true: Erik Naggum was found dead in his home.#June 20, 2009 09:18 PM
Last September, I wrote him a mail to thank him for the wealth of postings
he had contributed to comp.lang.lisp and for the impact they had on me.
I now feel incredibly glad I did that as I had always postponed doing so
for years.
Following is his response.
RIP, Erik.
* Tobias C. Rittweiler (2008-09-19 00:37)
> for a long time I've wanted to send an e-mail to express the
> gratitude I feel for the countless usenet postings that you have
> written in your comp.lang.lisp history. Now I finally came around
> doing so:
It's been quite a while since I posted my last article to c.l.l, but
very warm and welcome messages like yours still keep coming in at a
rate of about one a week. It amazes me.
> Thank you for the time and energy you spent in writing so many
> sophisticated and thought-provocating articles. I never understood
> where you drew this massive amount of energy and the will to
> continue from, considering the feedback you received back at that
> time.
Even when I was posting at a high rate, I received more mail from
those who appreciated what I had posted. I needed that encouragement
and knowing that despite the increasing amount of insanity on the
newsgroup, lots of people were reading what I wrote with a positive
attitude. However, there are some people whose evil ways are truly
destructive, and those are the moralists and the punishers who are
utterly unable to produce anything good at all, but all the more
hell-bent on making others pay for such things as not living up to
their expectations, doing what they think is right, etc, and I drew a
considerable amount of energy from my life-long desire to rid the
planet of moralists and punishers. It may sound contradictory, but I
hold that the use of force and violence and the lesser evils of
moralizing and punishment should only be used to prevent any of them,
never to seek any other goal: Telling a moralist that moralizing is
wrong, punishing those who punish others, using force and violence to
stop those who initiate the use of force and violence — you get the
picture — is the right thing to do. Unfortunately, this gets the
moralizers and punishers all worked up, and they prove that they
really are the psychopaths they only appeared to be when someone had
the gall to do something against /their/ desires. So even though my
general outlook on life went under-appreciated, namely that if you
know what other people should have done in the past, you never have
any clue what you or anyone else ought to do in the future, and if you
are concerned with what you yourself ought to do in the future, you
generally leave other people alone to figure out what /they/ ought to do
in the future, too, lots of people picked up on the positive message:
Exposing moralists and punishers for what they really are —
psychopaths — does a great service to any community who suffers from
their pernicious effects. Of course, if you are a horribly bad person
like that, you can only see others as reflections of yourself, and you
never understand why anyone would want to moralize against or punish
/you/, because part and parcel of being rabidly insane is never being
able to see yourself from the outside, and you think insane thoughts
like "How /dare/ anyone moralize against or punish /me/, when I'm the sole
moral authority in the whole Universe and everyone, everywhere have a
moral duty to behave the way /I/ tell them!". Or, in other words, people
who partition the world population into "the good" and "the bad"
always make the mistake of believing that they fall into the "the
good" partition, when they are actually among the very few that are
truly evil: No monumental evil act in the history of mankind has been
committed by anyone who thought of themselves as "evil" — on the
contrary, the worse the (objective) evil, the more the perpetrator was
completely convinced of the goodness of himself and of his
"purification". So when the newsgroup became plagued by the evil kind
of moron that has nothing to contributed and no rewards for anyone
doing the right thing, but only harm and punishment for those who do
the wrong thing in their eyes, it was time to quit. Had I had even
more energy, I could have stayed, but I had ran out of steam fighting
false accusations, which is another one of those hallmarks of truly
evil people who think nothing of harming the innocent in their crusade
against the "evildoers". It turns out, as any study of history will
show, that those accused by moralists and punishers are always
innocent. That's why we need courts, so those who accuse are not the
ones to decide on the guilt and the punishment. Stupid people tend not
to grasp this fact, and only see courts as means of letting people
they "know" are guilty avoid punishment.
> And I particularly mean your non-technical contributions. I read all
> those now already several years ago when I was sixteen if I'm
> remembering correctly. I recall how I was shocked at the tone you
> used, and the aggression you seemed to feel towards people, as I
> grew up in feel-good communities myself. [The postings] made me value
> technical expertise and competence over civil masquerade, and they
> revealed how the latter can sometimes even impair the communication
> for the former.
Civility and politeness are extremely useful tools in communication
with people who are more wrong than right, but of very little use with
people who are vastly more right than wrong. This counter-intuitive
observation comes directly from the fact that we simply do not need
civil and polite ways of telling people that they are right about
something. So the people who have most to gain from civility and
politeness are people who know they are and intend to /stay/ wrong while
they force everybody else hold their tongues. That may have been a
very good way of building societies before /anyone/ was usually right
about anything. It is only in the twentieth century that a sizable
fraction of the population had any means to know whether they were in
the right or in the wrong to begin with. Before we invented the
concept of the real world, everybody lived their entire lives in their
own emotional world. After science and technology invented the concept
of the real world and of truth as correspondence between thoughts and
reality, the internal, private world turned out to be /untrue/ almost
all the time. These days, I keep telling people that you only /really/
grow up and become a human being (as opposed to a mere animal) when
you realize that most of what you think and almost all you feel is
/wrong/ and every person, however smart or highly esteemed by their
peers, is utterly and completely incapable of determining where they
are right among all this wrongness on their own. We tend to believe we
are mostly right, however, and only notice when the consequences of
our actions contradict our best expectations. Now, there are many
areas of life where there /is/ no way to sort right from wrong, and it
would certainly be impolite to point out an error that was only
relative to our own personal values, which is where the impoliteness
of moralizing comes in, but modern man enjoys a growing number of
areas where we can unequivocally sort right from wrong, and then it is
impolite /not/ to point out an error, for that means we let someone
believe something that will cause them harm, or at least undesired
consequences, later on. This means that in the areas of life where
people are mostly wrong, it is indeed a good thing to be civil and
polite all the time, as one wouldn't want others constantly to point
out one's own mistakes, either, but in the many areas where it is
possible to be right, and positively harmful to be wrong, allowing
people to hold on to false beliefs in order to protect their feelings
is really, really bad for everyone. The key, therefore, is knowing
which areas can and which still cannot tell right from wrong. It is my
firm position that no areas of science, technology, engineering,
mathematics, and allied disciplines such as medicine, are proper
arenas for politeness and civility. If people are wrong and are
spreading dis- or misinformation in these fields, everybody hurts
because it becomes that much harder to know right from wrong. However,
in areas where no one can really tell, such as ethics, politics,
fashion, etc, even though we may have pretty good ideas and
communities who /choose/ a particular set of beliefs, it behooves people
to be humble and civil and polite because fighting with people who are
wrong, but believe they are right, yet there are no means to prove
that, would be extremely tiresome, as it became on c.l.l when people
who stopped thinking about issues where we /can/ decide right from
wrong, started to bother everyone with their /personal/ problems when
others disagreed with them.
> In retrospect, I'm pretty sure that you helped me become the
> individuum I am now.
I'm very pleased to hear that, and particularly that you took the time
to write and tell me. I wish you the best of luck for the future!
Best regards, Erik Naggum
--
Member of AAAI AAAS ACM AMS APS ASA ASL IEEE IMS MAA NYAS PSA SIAM USENIX
The United States of America still symbolizes individualism, rationality,
and intellectual achievement to me — even though most Americans disagree.
(defun iterate-point (c max-iterations)
(loop for n below max-iterations
for z = (complex 0 0) then (+ (* z z) c)
when (> (abs z) 2) do (loop-finish)
finally (return n)))
(defun iterate/df (re0 im0 bound)
(declare (type double-float re0 im0) (type fixnum bound))
(do ((n 1 (1+ n)) ;the first iterate always takes z->c, so start there
(re re0) (im im0)
(re^2 (* re0 re0) (* re re))
(im^2 (* im0 im0) (* im im)))
((>= n bound) 1)
(declare (double-float re im re^2 im^2) (fixnum n))
(when (> (+ re^2 im^2) 4d0) (return 0))
(psetf re (+ re0 (- re^2 im^2))
im (+ im0 (let ((a (* re im))) (+ a a))))));faster than (* 2 (* re im))
(defun gcls/opt (width height &key; (bound 51) (nthreads 8) (real-min -1.5d0) (real-max 0.5d0) (imag-min -1d0) (imag-max 1d0))
(declare (type fixnum width height bound) (double-float real-min real-max imag-min imag-max))
(let* ((array (make-array (list height (ceiling width 8)) :element-type '(unsigned-byte 8)))
(seq (make-array (array-total-size array) :element-type '(unsigned-byte 8) :displaced-to array))
(rows -1)
(real-step (/ (- real-max real-min) width)) ;; with shifted sampling as spec image
(imag-step (/ (- imag-max imag-min) height)))
(declare (type fixnum rows) (type double-float real-step imag-step))
(labels ((worker ()
(loop for i = (incf rows) while (< i height) ;incf atomic, no mutex needed
for im0 of-type double-float = (- imag-max (* i imag-step)) do
(loop with byte of-type fixnum = 0
with code of-type (unsigned-byte 8) = 0
with b of-type bit = 0
for bit-number of-type fixnum upfrom 0
for j below width
for re0 of-type double-float = (+ real-min (* j real-step)) do
(setf b (iterate/df re0 im0 bound))
(if (= bit-number 8)
(setf (aref array i byte) code code b byte (1+ byte) bit-number 0)
(setf code (logior (ash code 1) b)))
finally (unless (= bit-number 1) (setf (aref array i byte) code))))))
(mapc #'sb-thread:join-thread (loop repeat nthreads collecting (sb-thread:make-thread #'worker))))
(with-open-stream (stream (sb-sys:make-fd-stream (sb-sys:fd-stream-fd sb-sys:*stdout*)
:element-type '(unsigned-byte 8) :buffering :full :output t :input nil))
(write-sequence (map 'list #'char-code (format nil "P4~%~D ~D~%" width height)) stream)
(write-sequence seq stream))))
-bash-3.2$ SBCL --eval '(load (compile-file "gclsopt.lisp"))' --eval '(save-lisp-and-die "inline.core" :purify t)'
-bash-3.2$ /usr/bin/time -v SBCL --core inline.core --noinform --eval "(gcls/opt 16000 16000)" --eval "(quit)" > /dev/null
User time (seconds): 35.38
System time (seconds): 0.11
Percent of CPU this job got: 782%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:04.53
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 22260
Voluntary context switches: 57
Involuntary context switches: 2817
-bash-3.2$ /usr/bin/time -v SBCL --core notinline.core --noinform --eval "(gcls/opt 16000 16000)" --eval "(quit)" > /dev/null
User time (seconds): 65.12
System time (seconds): 8.92
Percent of CPU this job got: 412%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:17.97
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 164098
Voluntary context switches: 522128
Involuntary context switches: 12716
-bash-3.2$ /usr/bin/time -v sbcl --core inline.core --noinform --eval "(gcls/opt 2000 2000 :bound 5001)" --eval "(quit)" > /dev/null
User time (seconds): 41.81
System time (seconds): 0.11
Percent of CPU this job got: 787%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:05.32
Minor (reclaiming a frame) page faults: 14060
Voluntary context switches: 58
Involuntary context switches: 3305
-bash-3.2$ /usr/bin/time -v sbcl --core notinline.core --noinform --eval "(gcls/opt 2000 2000 :bound 5001)" --eval "(quit)" > /dev/null
User time (seconds): 42.04
System time (seconds): 0.13
Percent of CPU this job got: 784%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:05.37
Minor (reclaiming a frame) page faults: 19336
Voluntary context switches: 2178
Involuntary context switches: 3346
-bash-3.2$ /usr/bin/gcc -pipe -Wall -O3 -fomit-frame-pointer -march=native -D_ISOC9X_SOURCE -mfpmath=sse -msse2 -lm -lpthread mandelbrot.gcc-6.c -o mandelbrot.gcc-6.gcc_run
-bash-3.2$ /usr/bin/time -v ./mandelbrot.gcc-6.gcc_run 16000 > /dev/null
User time (seconds): 19.38
System time (seconds): 0.08
Percent of CPU this job got: 795%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.44
Minor (reclaiming a frame) page faults: 8173
Voluntary context switches: 16
Involuntary context switches: 1605
(defun iterate/df/double (a-re0 a-im0 b-re0 b-im0 max-iter)
(declare (type double-float a-re0 a-im0 b-re0 b-im0) (fixnum max-iter))
(do ((n 1 (1+ n ))
(a-re a-re0) (a-im a-im0) (a-re^2 0d0) (a-im^2 0d0)
(b-re b-re0) (b-im b-im0) (b-re^2 0d0) (b-im^2 0d0)
(mask 0) (test 3 (logxor mask 3)))
((or (>= n max-iter) (zerop test)) test)
(declare (double-float a-re a-im a-re^2 a-im^2 b-re b-im b-re^2 b-im^2)
(fixnum n) (type (integer 0 3) mask))
(unless (zerop (logxor mask 2))
(setf a-re^2 (* a-re a-re) a-im^2 (* a-im a-im))
(when (> (+ a-re^2 a-im^2) 4d0) (setf mask (logior mask 2))))
(unless (zerop (logxor mask 1))
(setf b-re^2 (* b-re b-re) b-im^2 (* b-im b-im))
(when (> (+ b-re^2 b-im^2) 4d0) (setf mask (logior mask 1))))
(psetf a-re (+ a-re0 (- a-re^2 a-im^2)) a-im (+ a-im0 (let ((a (* a-re a-im))) (+ a a)))
b-re (+ b-re0 (- b-re^2 b-im^2)) b-im (+ b-im0 (let ((a (* b-re b-im))) (+ a a))))))
-bash-3.2$ /usr/bin/time -v sbcl --core double.core --noinform --eval "(gcls/opt/double 16000 16000)" --eval "(quit)" > /dev/null
User time (seconds): 33.81
System time (seconds): 0.11
Percent of CPU this job got: 782%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:04.33
Minor (reclaiming a frame) page faults: 22190
Voluntary context switches: 59
Involuntary context switches: 3005
-bash-3.2$ /usr/bin/time -v SBCL --core SBCL.core --noinform --eval "(main)" --eval "(quit)" 16000 8 > /dev/null
User time (seconds): 40.55
System time (seconds): 0.12
Percent of CPU this job got: 671%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:06.05
Minor (reclaiming a frame) page faults: 25138
Voluntary context switches: 66
Involuntary context switches: 2682
In this blog post Ingvar Mattsson was wondering about the printed line resulting from evaluating:
(defun frob (x) (format t 'Frob: ~a~%' x))
(frob (defun frob (x) (format t 'New frob: ~a~%' x))
This provides an example where I think the ANSI Common Lisp standard is actually very helpful, since it often tries to go out of its way to point out things that are explicitly left undefined, instead of simply leaving them left undefined by not defining them, as many other standards (out of fear of being ambiguous) do. Quoting from the HyperSpec, Section 3.1.2.1.2.3 Function Forms:
Although the order of evaluation of the argument subforms themselves is strictly left-to-right, it is not specified whether the definition of the operator in a function form is looked up before the evaluation of the argument subforms, after the evaluation of the argument subforms, or between the evaluation of any two argument subforms if there is more than one such argument subform. For example, the following might return 23 or 24.
(defun foo (x) (+ x 3)) (defun bar () (setf (symbol-function 'foo) #'(lambda (x) (+ x 4)))) (foo (progn (bar) 20))
Thus we can see that the effect of evaluating the orginal two forms is indeed undefined (as already suspected by Ingvar Mattsson) as to which function definition is called in the second form, without going to the trouble of trying to take into account possible differences between evaluation and compilation, compile-time effects of defun, etc.
Which is my long-winded way of saying a big thank you to the people involved in creating the ANSI Common Lisp standards document (with special thanks to all involved in creating, releasing and maintaining the HyperSpec online text equivalent thereof, especially of course Kent Pitman), which is one of the nicest language standards I have had the pleasure of working with and against.
#June 17, 2009 02:38 PMAnvita-Health of San Diego California is seeking programmers to develop and maintain its health care decision support tools.
Prerequisites for the position include:
An advanced degree in Computer Science from a US or UK university
Strong C++ skills; Lisp programming a plus
At least 2 yrs of programming experience
Web skills a plus (PERL, PHP) and familiarity with web UI tools
A willingness to relocate to San Diego, CA
Salary and Benefits commensurate with experience.
Interested persons should send CV to:
Sheldon Ball MD, PhD
Physician Informaticist
Anvita Health
sball@anvitahealth.com
www.anvitahealth.com

Basically, in the case of the following:
(defun frob (x) (format t "Frob: ~a~%" x)) (frob (defun frob (x) (format t "New frob: ~a~%" x))does the printed line say "Frob" or "New frob"?
It is, I believe, fully specified what will happen when you do either of (funcall #'frob ...) or (funcall 'frob ...), but out of the two implementations I have tried (SBCL and CLisp), I have two different behaviours. SBCL prints "New frob" and CLisp prints "Frob".
I shall have to ponder this, for a bit, I think. #June 17, 2009 11:40 AM
A Boston Lisp Meeting will take place on Monday, June 29th 2009 at 1800 at MIT 34-401B, where Eli Barzilay will speak about Implementing Domain Specific Languages with PLT Scheme.
Additionally, we are still accepting proposals for up to two volunteers to each give of a 5-minute Lightning Talk (followed by 2-minute Q&A).
Also, there will be a buffet offered by ITA Software. Registration is not necessary but appreciated. See details below.
Eli Barzilay will speak about Implementing Domain Specific Languages with PLT Scheme.
Many problems call for domain-specific languages (DSLs) to express them and their solutions; such languages enable a dialogue between domain experts and software developers. The Lisp and Scheme community has a decades-old tradition of creating and embedding special-purpose languages via macros. Over the last twenty years, we PLT Schemers have continued to develop this technology to the point where making up new languages is so quick and easy that PROGRAMMERS CREATE A LANGUAGE FOR WRITING A SINGLE PROGRAM.
Embedded DSLs are appropriate for a whole range of domains and applications -- in both academia and industry. Notable examples include research languages, teaching languages, and application-specific languages like our text-friendly documentation language.
In this talk Eli will demonstrate how to implement embedded practical DSLs in PLT Scheme.
Eli Barzilay is a Researcher in the PLT group at Northeastern University. He has been a core PLT Scheme developer since 2003, and has used PLT's ability to implement new languages to an extreme. For his Programming Languages undergraduate course, he creates nearly one language per week. In addition to writing new languages, he is involved in helping PLT develop into a multi-lingual environment.
His website is at http://barzilay.org/
Having observed the success of the formula at ILC'2009, we have instituted Lightning Talks at the Boston Lisp Meeting. At every meeting, before the main talk, there are two slots for strictly timed 5-minute talks followed by 2-minute for questions and answers.
The slots for next meeting are still open. Step up and come talk about your pet project!
The Lisp Meeting will take place on Monday June 29th 2009 at 1800 (6pm) at MIT, Room 34-401B.
As the numbers indicate, the room is in Building 34, on the 4th floor. This is the usual location, on 50 Vassar Street, Cambridge.
MIT map: http://whereis.mit.edu/bin/map?selection=34
Google map: http://maps.google.com/maps?q=50+Vassar+St,+Cambridge,+MA+02139,+USA
Many thanks go to Alexey Radul for arranging for the room, and to MIT for welcoming us.
Dinner: ITA Software, a fine employer of Lisp hackers (disclaimer: I work there), is kindly purchasing a buffet to accompany our monthly Boston Lisp meeting. Anyone who attends is welcome to partake.
We appreciate it if you let us know you're coming, and what food taboos you have, so that we can order the correct amount and kind of food. Tell us by sending email to boston-lisp-meeting-register at common-lisp.net. We won't send any acknowledgement unless requested; importantly, we'll keep your identity and address confidential and won't communicate any such information to anyone, not even to our sponsors.
The previous Boston Lisp Meeting on May 26th had 40 participants. Norman Ramsey gave a talk about Using Higher-Order Functions and Continuation-Passing Style to Make Dataflow Optimization Simple.
In the near future, we expect to have Bruce Lewis on 2009-07-27 about BRL and ourdoings.com, Emmanuel Schanzer on 2009-08-31 about bootstrapworld.org, Christine Flood on some undetermined date about Fortress.
We're always looking for more speakers. The call for speakers and all the other details are at http://fare.livejournal.com/120393.html Volunteers to give Lightning Talks are also sought.
For more information, see our web site boston-lisp.org. For posts related to the Boston Lisp meetings in general, follow this link: http://fare.livejournal.com/tag/boston-lisp-meeting or subscribe to our RSS feed: http://fare.livejournal.com/data/rss?tag=boston-lisp-meeting
Please forward this information to people you think would be interested. Please accept my apologies for your receiving this message multiple times. My apologies if this announce gets posted to a list where it shouldn't, or fails to get posted to a list where it should. Feedback welcome by private email reply to fare@tunes.org.
#June 17, 2009 04:19 AMEach program should plot the Mandelbrot set [-1.5-i,0.5+i] on an N-by-N bitmap. Write output byte-by-byte in portable bitmap format.
cmp program output N = 200 with this 5KB output file to check your program is correct before contributing.
(defun iterate-point (c max-iterations)
(loop for n below max-iterations
for z = (complex 0 0) then (+ (* z z) c)
when (> (abs z) 2) do (loop-finish)
finally (return n)))
(defun mb-set (width height &key (max-iterations 51)
(real-min -1.5d0) (real-max 0.5d0) (imag-min -1d0) (imag-max 1d0))
(let ((array (make-array (list height width))))
(with-square-grid ((im i :steps height :first imag-max :last imag-min)
(re j :steps width :first real-min :last real-max))
(dotimes (i height array)
(dotimes (j width)
(setf (aref array i j)
(if (= (iterate-point (complex re im) max-iterations)
max-iterations)
1
0)))))))
(defmacro with-square-grid (((var index &key steps first last) &rest others) &body body)
(let ((var-step (gensym)))
`(let ((,var-step (/ (- ,last ,first) (1- ,steps))))
(symbol-macrolet ((,var (+ ,first (* ,index ,var-step))))
,@(if others
`((with-square-grid ,others ,@body))
body)))))
(defun pack-array/byte-aligned (array)
(let ((seq (make-array (ceiling (array-total-size array) 8) :element-type '(unsigned-byte 8))))
(dotimes (n (length seq) seq)
(loop for b from (* 8 n) below (+ (* 8 n) 8)
for code of-type (unsigned-byte 8) = (row-major-aref array b)
then (logior (ash code 1) (row-major-aref array b))
finally (setf (aref seq n) code)))))
(defun write-pbm (filename width height packed-array)
(with-open-file (s filename :direction :output :element-type '(unsigned-byte 8))
(write-sequence (map 'list #'char-code (format nil "P4~%~D ~D~%" width height)) s)
(write-sequence packed-array s)))


CL-USER> (setf x-min -1 x-max 1 dx (- x-max x-min) samples 4)
CL-USER> (loop for n below samples collecting (+ x-min (* n (/ dx (1- samples)))))
(-1 -1/3 1/3 1)
CL-USER> (loop for n below samples collecting (+ x-min (* (+ n 1/2) (/ dx samples))))
(-3/4 -1/4 1/4 3/4)
CL-USER> (loop for n below samples collecting (+ x-min (* n (/ dx samples))))
(-1 -1/2 0 1/2)
CL-USER> (time (mb-set 1000 1000))
Evaluation took:
15.593140 seconds of total run time (14.772507 user, 0.820633 system)
[ Run times consist of 3.651 seconds GC time, and 11.943 seconds non-GC time. ]
39,699,456,062 processor cycles
8,436,938,592 bytes consed
#<(SIMPLE-ARRAY T (1000 1000)) {121AF447}>
CL-USER> (time (pack-array/byte-aligned *))
Evaluation took:
0.025524 seconds of total run time (0.025439 user, 0.000085 system)
64,063,813 processor cycles
125,008 bytes consed
#<(SIMPLE-ARRAY (UNSIGNED-BYTE 8) (125000)) {12479007}>
CL-USER> (time (write-pbm "test1000.pbm" 1000 1000 *))
Evaluation took:
0.002482 seconds of total run time (0.000926 user, 0.001556 system)
6,306,962 processor cycles
7,552 bytes consed
CL-USER> (defparameter *array* (make-array (list 16000 16000) :element-type 'fixnum))
CL-USER> (dotimes (n (array-total-size *array*)) (setf (row-major-aref *array* n) (random 2)))
CL-USER> (time (pack-array/byte-aligned/fast *array*))
Evaluation took:
1.518081 seconds of total run time (1.486409 user, 0.031672 system)
[ Run times consist of 0.013 seconds GC time, and 1.506 seconds non-GC time. ]
3,818,926,037 processor cycles
32,000,576 bytes consed
#<(SIMPLE-ARRAY (UNSIGNED-BYTE 8) (32000000)) {4FD44007}>
When we created the Mandelbrot benchmark in December of 2004, the machines that were used were much slower ... the image limits were something like 200x200, and so an iteration limit of 50 seemed reasonable... Another interesting thing to note is that I seem to recall that with this benchmark we smoked out compiler bugs in O'Caml and MLton. FWIW.
To mark the milestone of finally bringing hunchentoot-auth, hunchentoot-vhost, hunchenoot-cgi and nuclblog into the present such that they work properly with the hunchentoot-1.0 release, I've rolled up the following packages:
Of course these are probably best gotten from the git repo's, but for those of you who like released versions, I figured I'd roll new ones since it had been quite some time (almost two years in some cases!).
#June 12, 2009 04:05 PMIt sounds like the first TC Lispers meeting was a success. Patrick Stein really liked it. #June 12, 2009 03:29 PM
For older items, see the Planet Lisp Archives.
Last updated: July 1, 2009 07:09 AM