Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overview

lparallel is a library for parallel programming in Common Lisp, featuring

  • a simple model of task submission with receiving queue
  • constructs for expressing fine-grained parallelism
  • asynchronous condition handling across thread boundaries
  • parallel versions of map, reduce, sort, remove, and many others
  • promises, futures, and delayed evaluation constructs
  • computation trees for parallelizing interconnected tasks
  • bounded and unbounded FIFO queues
  • high and low priority tasks
  • task killing by category
  • integrated timeouts

Download

The easiest way to obtain lparallel is with Quicklisp.

(ql:quickload :lparallel)

This will download, compile, and load lparallel along with its dependency bordeaux-threads. Alternatively, it may be downloaded manually from the following links:

lparallel should run on any Common Lisp implementation supported by bordeaux-threads. The following implementations successfully pass the lparallel test suite:

  • ABCL
  • Allegro
  • Clozure
  • LispWorks
  • SBCL

To run the test suite,

(ql:quickload :lparallel-test)
(lparallel-test:execute)

To run benchmarks,

(ql:quickload :lparallel-bench)
(lparallel-bench:execute N)

where N is the number of processors/cores.

Kernel

In the context of lparallel, a kernel is an abstract entity that schedules and executes tasks. The lparallel kernel API is meant to describe parallelism in a generic manner.

The implementation uses a group of worker threads. It is intended to be efficiency-wise comparable to (or faster than) similar hand-rolled solutions while also providing full condition handling and consistency checks. All higher-level constructs in lparallel are implemented on top of the kernel.

(For an implementation of the kernel API that distributes across machines, see lfarm.)

Kernel-related operations are applied to the current kernel, stored in kernel. A kernel is typically created with

(setf lparallel:*kernel* (lparallel:make-kernel N))

where N is the number of worker threads (more options are available). In most circumstances a kernel should exist for the lifetime of the Lisp process. Multiple kernels are possible, and setting the current kernel is done in the expected manner by dynamically binding kernel.

A task is a function designator together with arguments to the function. To execute a task, (1) create a channel, (2) submit the task through the channel, and (3) receive the result from the channel.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(let ((channel (make-channel)))
  (submit-task channel '+ 3 4)
  (receive-result channel))

; => 7

If you have not created a kernel (if *kernel* is nil) then upon evaluating the above you will receive an error along with a restart offering to make a kernel for you. Evaluation commences once a kernel is created.

Multiple tasks may be submitted on the same channel, though the results are not necessarily received in the order in which they were submitted. receive-result receives one result per call.

(let ((channel (make-channel)))
  (submit-task channel '+ 3 4)
  (submit-task channel (lambda () (+ 5 6)))
  (list (receive-result channel)
        (receive-result channel)))

; => (7 11) or (11 7)

To set the priority of tasks, bind task-priority around calls to submit-task.

(let ((*task-priority* :low))
  (submit-task channel '+ 3 4))

The kernel executes a :low priority task only when there are no default priority tasks pending. The task priorities recognized are :default (the default priority) and :low.

Handlers may be established for conditions that are signaled by a task (see Handling). When an error from a task goes unhandled, an error-info object is placed in the channel. After receive-result removes such an object from the channel, the corresponding error is signaled.

Note that a kernel will not be garbage collected until end-kernel is called. Message passing

For situations where submit-task and receive-result are too simplistic, a blocking queue is available for arbitrary message passing between threads.

(defpackage :queue-example (:use :cl :lparallel :lparallel.queue))
(in-package :queue-example)

(let ((queue (make-queue))
      (channel (make-channel)))
  (submit-task channel (lambda () (list (pop-queue queue)
                                        (pop-queue queue))))
  (push-queue "hello" queue)
  (push-queue "world" queue)
  (receive-result channel))
;; => ("hello" "world")

Of course messages may also be passed between workers. Dynamic variables and worker context

When a dynamic variable is dynamically bound (for example with let or progv), the binding becomes local to that thread. Otherwise, the global (default) value of a dynamic variable is shared among all threads that access it.

Binding dynamic variables for use inside tasks may be done on either a per-task basis or a per-worker basis. An example of the former is

(submit-task channel (let ((foo *foo*))
                       (lambda ()
                         (let ((*foo* foo))
                           (bar)))))

This saves the current value of *foo* and, inside the task, binds *foo* to that value for the duration of (bar). You may wish to write a submit-with-my-bindings function to suit your particular needs.

To establish permanent dynamic bindings inside workers (thread-local variables), use the :bindings argument to make-kernel, which is an alist of (var-name . value-form) pairs. Each value-form is evaluated inside each worker when it is created. (So if you have two workers, each value-form will be evaluated twice.)

For more complex scenarios of establishing worker context, a :context function may be provided. This function is called by lparallel inside each worker and is responsible for entering the worker loop by funcalling its only parameter. The variables from :bindings are available inside the function.

(defvar *foo* 0)
(defvar *bar* 1)

(defun my-worker-context (worker-loop)
  (let ((*bar* (1+ *foo*)))
    ;; enter the worker loop; return when the worker shuts down
    (funcall worker-loop)))

(defvar *my-kernel* (make-kernel 2
                                 :bindings '((*foo* . (1+ 98)))
                                 :context #'my-worker-context))

(list *foo* *bar*)
; => (0 1)

(let* ((*kernel* *my-kernel*)
       (channel (make-channel)))
  (submit-task channel (lambda () (list *foo* *bar*)))
  (receive-result channel))
; => (99 100)

Handling

Handling conditions in lparallel is done with task-handler-bind. It is just like handler-bind except that it handles conditions signaled from inside parallel tasks.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(define-condition foo-error (error) ())

(task-handler-bind ((foo-error (lambda (e)
                                 (declare (ignore e))
                                 (invoke-restart 'number-nine))))
  (pmapcar (lambda (x)
             (declare (ignore x))
             (restart-case (error 'foo-error)
               (number-nine () "number nine")))
           '(1 2 3)))

; => ("number nine" "number nine" "number nine")

Though one may be tempted to merge handler-bind and task-handler-bind with some shadowing magic, in general the handlers which need to reach inside tasks will not always match the handlers that are suitable for the current thread. It is also useful to explicitly flag asynchronous handlers that require thread-safe behavior.

In Common Lisp, the debugger is invoked when an error goes unhandled. By default lparallel mirrors this behavior with regard to tasks: when an error is signaled inside a task, and the error is not handled by one of the task handlers established by task-handler-bind, then the debugger is invoked.

However there is an alternate behavior which may be more appropriate depending upon the situation: automatically transferring errors. Setting *debug-tasks-p* to false will transfer task errors to threads which attempt to obtain the failed results. Suppose you have several parallel tasks running and each task signals an error. If *debug-tasks-p* is false then the debugger will be invoked just once (typically in the parent thread) instead of several times (once for each task).

If *debug-tasks-p* is true then you may still elect to transfer the error yourself. Inside each task there is a restart called TRANSFER-ERROR, which you will see in the debugger. (When *debug-tasks-p* is false the restart is simply invoked for you.) The following shows the Clozure + SLIME environment.

(pmapcar (lambda (x)
           (when (evenp x)
             (restart-case (error 'foo-error)
               (number-nine ()
                 :report "Who was to know?"
                 "number nine"))))
         '(1 2 3))
=>
Error EXAMPLE::FOO-ERROR
   [Condition of type EXAMPLE::FOO-ERROR]

Restarts:
 0: [NUMBER-NINE] Who was to know?
 1: [TRANSFER-ERROR] Transfer this error to a dependent thread, if one exists
 2: [ABORT-BREAK] Reset this thread
 3: [ABORT] Kill this thread

The presence of the TRANSFER-ERROR restart indicates that we are inside a task. After inspecting the backtrace to our satisfaction, it’s time to hit TRANSFER-ERROR. In our example the top-level thread is already waiting for the result, so the debugger will appear again after we transfer.

=>
Error FOO-ERROR
   [Condition of type FOO-ERROR]

Restarts:
 0: [RETRY] Retry SLIME interactive evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT-BREAK] Reset this thread
 3: [ABORT] Kill this thread

The familiar SLIME restarts are there again. We are back in the top-level thread.

The behavior specified by debug-tasks-p may be locally overridden with task-handler-bind. To always transfer errors,

(task-handler-bind ((error #'invoke-transfer-error)) ...)

Likewise to always invoke the debugger for unhandled errors,

(task-handler-bind ((error #'invoke-debugger)) ...)

More on threads

In the second example, what if we selected the ABORT restart (“Kill this thread”) instead of transferring the error? This would not be dangerous—the killed worker would be automatically replaced with a new one—but it would be a little rude. The top-level thread would signal TASK-KILLED-ERROR instead of FOO-ERROR. In our example this does not matter, but by signaling TASK-KILLED-ERROR we potentially spoil a handler’s lifelong dream of handling a FOO-ERROR. Killing tasks

Occasionally there may be a task which has entered a deadlock (which can happen with circular references) or an infinite loop. Don’t panic! Try

(kill-tasks :default)

This is a blunt weapon, however, because passing :default may cause unrelated tasks to be killed.

Each task is given a task category identifier. When a task is submitted, it is assigned the category of task-category which has a default value of :default. The argument to kill-tasks is a task category. Any running task whose category is eql to the argument passed will be killed. Pending tasks are not affected.

To avoid killing unrelated tasks, bind *task-category* around submit-task calls.

(let ((channel (make-channel)))
  ;; ...
  (let ((*task-category* 'my-stuff))
    (submit-task channel (lambda () (loop))))  ; oops!
  (receive-result channel))

This is hung at receive-result. We can recover by calling

(kill-tasks 'my-stuff)

which will only kill our looping task, assuming my-stuff is not used as a task category elsewhere in the same package.

Keep in mind that killing tasks is expensive and should only be done in exceptional circumstances. Not only is thread creation expensive (for the worker replacements), but heavy locking is required as well.

Promises

A promise is a receptacle for a result which is unknown at the time it is created. To fulfill a promise is to give it a result. The value of a promise is obtained by forcing it.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(let ((p (promise)))
  (fulfilledp p) ; => nil
  (fulfill p 3)
  (fulfilledp p) ; => t
  (force p))

; => 3

A promise may be successfully fulfilled only once, after which force will forever return the same result. If fulfill is successful it returns true, otherwise it returns false indicating the promise is already fulfilled (or in the process of being fulfilled). When force is called on an unfulfilled promise, the call will block until the promise is fulfilled.

A future is a promise which is fulfilled in parallel. When a future is created, a parallel task is made from the code passed.

(let ((f (future
           (sleep 0.2)
           (+ 3 4))))
  (fulfilledp f) ; => nil
  (sleep 0.4)
  (fulfilledp f) ; => t
  (force f))

; => 7

Here are two futures competing to fulfill a promise:

(let* ((p (promise))
       (f (future
            (sleep 0.05)
            (fulfill p 'f-was-here)))
       (g (future
            (sleep 0.049999)
            (fulfill p 'g-was-here))))
  (list (force p) (force f) (force g)))

; => (F-WAS-HERE T NIL) or (G-WAS-HERE NIL T)

Whichever result appears is dependent upon your system. Note the return value of fulfill indicating success or failure.

Importantly, fulfill is a macro. When we consider giving fulfill an actual calculation to perform instead of an immediate value like 3, the reason for fulfill being a macro should be clear. If a promise is already fulfilled then we do not want the code passed to fulfill to be needlessly executed.

A speculation—created by speculate—is a low-priority future whose associated task is executed only when those of regular futures are not pending.

Like futures and speculations, a delay is also a promise associated with some code. However instead of being fulfilled in parallel, a delay is fulfilled when force is called upon it.

Futures, speculations, and delays are thus types of promises, and they only differ in how they are fulfilled. In fact they hardly differ in that regard since all must obey fulfill which, if successful, overrides any “fulfillment plan” that may be in place.

(let ((f (future (+ 1 2)))
      (g (delay  (+ 3 4)))
      (h (delay  (+ 5 6))))
  (fulfill f 'nevermind)       ; may or may not cancel f's computation
  (fulfill g (+ 7 8))          ; 'force' will no longer compute (+ 3 4)
  (mapcar 'force (list f g h)))

; => (3 15 11) or (NEVERMIND 15 11)

f‘s planned computation is canceled if the first fulfill happens to grab the future before a worker thread gets it.

For an object which is not a promise, force behaves like identity, returning the object passed. We may imagine that non-promise objects are like promises that are always fulfilled. fulfilledp returns true for any non-promise argument passed. Attempting to fulfill a non-promise is not an error, though of course it never succeeds.

Lastly there is chain, which links objects together by relaying force and fulfilledp calls.

(force (future (delay 3)))         ; => a delay object
(force (future (chain (delay 3)))) ; => 3

Suppose we wish to cancel a speculation and also signal an error if the speculation is forced after being canceled. This may be accomplished by giving a chained delay to fulfill.

(let ((f (speculate (+ 3 4))))
  (fulfill f (chain (delay (error "speculation canceled!"))))
  (force f))

; => 7 or #<SIMPLE-ERROR "speculation canceled!">

If chain were not present then force would return a delay object if fulfill succeeded, on which force would have to be called again in order to obtain the error.

Cognates

lparallel provides parallel versions of many Common Lisp functions and macros, each prefixed by ‘p’. They are the pmap family, preduce, and the following.

pand
pcount
pcount-if
pcount-if-not
pdotimes
pevery
pfind
pfind-if
pfind-if-not
pfuncall
plet
pnotany
pnotevery
por
premove
premove-if
premove-if-not
psome
psort

They return the same results as their CL counterparts except in cases where parallelism must play a role. For instance premove behaves essentially like its CL version, but por is slightly different. or returns the result of the first form that evaluates to something non-nil, while por may return the result of any such non-nil-evaluating form.

plet is best explained in terms of its macroexpansion.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(plet ((a (+ 3 4))
       (b (+ 5 6)))
  (list a b))

; => (7 11)

The plet form expands to

(LET ((#:A725 (FUTURE (+ 3 4)))
      (#:B726 (FUTURE (+ 5 6))))
  (SYMBOL-MACROLET ((A (FORCE #:A725))
                    (B (FORCE #:B726)))
    (LIST A B)))

See Promises for an explanation of future and force. Since a future’s result is cached (a feature all promises share), references to a and b incur little overhead once their corresponding futures have finished computing.

There are four cognates which have no direct CL counterpart:

plet-if
pmaplist-into
pmap-reduce
preduce-partial

pmap

The pmap family consists of parallelized versions of the Common Lisp mapping functions, each denoted by a ‘p’ prefix. They are:

pmap
pmap-into
pmapc
pmapcan
pmapcar
pmapcon
pmapl
pmaplist
pmaplist-into

All take the same arguments and produce the same results as their respective Common Lisp counterparts. pmaplist-into does not actually have a CL counterpart, but it does what you think it does.

By default pmaps operate on their input sequence(s) in chunks. That is, subsequences of the input sequence(s) are mapped in parallel rather than on a per-element basis. This strategy allows pmaps to be faster than their CL counterparts even in the realm of worst case scenarios (see benchmarks).

The default number of parallel-mapped parts is the number of worker threads (the number given to make-kernel). All pmaps accept a :parts keyword argument for specifying the number of parts explicitly.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(pmap 'vector (lambda (x) (* x x)) :parts 2 '(3 4 5))
; => #(9 16 25)

(There is no ambiguity in the arguments because the :parts symbol is not a sequence.) When the number of parts is greater than or equal to the number of elements in the result sequence, the subdividing stage is elided and per-element parallelism is performed directly (an optimization).

In addition to :parts, all pmaps accept a :size option for specifying the number of elements to be mapped.

(pmapcar 'identity :size 2 '(a b c d)) ; => (A B)

(map-into  (vector 1 2 3 4) 'identity '(a b))             ; => #(A B 3 4)
(pmap-into (vector 1 2 3 4) 'identity '(a b))             ; => #(A B 3 4)
(pmap-into (vector 1 2 3 4) 'identity :size 2 '(a b c d)) ; => #(A B 3 4)

As you probably know, map-into disregards the fill pointer (if one exists) of the result sequence when determining the result size. pmap-into behaves the same way, but also allows the result size to be determined by the :size argument. Like map-into, pmap-into will adjust the fill pointer of the result sequence after mapping is complete.

(let ((vec (make-array 4 :fill-pointer 4 :initial-contents '(1 2 3 4))))
  ;; same as map-into
  (pmap-into vec 'identity '(a b))) ; => #(A B)

(let ((vec (make-array 4 :fill-pointer 4 :initial-contents '(1 2 3 4))))
  (pmap-into vec 'identity :size 2 '(a b c d))) ; => #(A B)

The :size argument also acts as an optimization for lists. Lists are not an ideal structure for parallel mapping because the subdivision process requires lengths to be known. When :size is given, all length calls are skipped.

Warning: the value of the :size option must be less than or equal to the length of the smallest sequence passed. It is unspecified what happens when that condition is not met.

As a rule of thumb, prefer arrays to lists where possible when using the pmap family of functions. In order to make array usage slightly more convenient, pmapcar accepts sequences. That is, (pmapcar ...) is an abbreviation for (pmap 'list ...).

preduce

preduce function sequence &key key from-end start end initial-value parts recurse

preduce (pronounced pee-reduce) is a parallel version of reduce. It chops up the input sequence into N parts and, in parallel, calls reduce on each part. The N partial results are then reduced again, either by reduce (the default) or, if the :recurse argument is non-nil, by preduce. The default value of N is the number of worker threads (the number given to make-kernel) which may be overridden by the :parts option.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(preduce '+ #(1 2 3 4 5 6) :parts 2)

is hand-wavingly similar to

(reduce '+ (vector (reduce '+ #(1 2 3))
                   (reduce '+ #(4 5 6))))

This code is misleading, of course: the two inner reduces are done in parallel, and the two inner arrays are displaced versions of the input array (no copying is done).

In order for the outcome of preduce to be independent of the choice of parts, the function passed must be associative with respect to the sequence elements and must produce an identity-like function when the :initial-value argument (if given) is partially applied. The latter condition is a consequence of :initial-value really meaning initial value per part.

(preduce '+ #(1 2 3 4 5 6) :parts 1 :initial-value 1)  ; => 22
(preduce '+ #(1 2 3 4 5 6) :parts 2 :initial-value 1)  ; => 23
(preduce '+ #(1 2 3 4 5 6) :parts 3 :initial-value 1)  ; => 24

In similar fashion, the :from-end option means from the end of each part.

The :start and :end arguments remain as they are in reduce, referring to the original input sequence.

The :key argument is thrown out while reducing the partial results. It applies to the first pass only.

preduce-partial is a variant of preduce which returns the unmolested partial results.

(preduce-partial '+ #(1 2 3 4 5 6) :parts 2)  ; => #(6 15)
(preduce-partial '+ #(1 2 3 4 5 6) :parts 3)  ; => #(3 7 11)

We can use preduce-partial to write premove-if for lists.

(defun premove-if* (test list)
  (reduce 'nreconc
          (preduce-partial (lambda (acc x)
                             (if (funcall test x)
                                 acc
                                 (cons x acc)))
                           list
                           :initial-value nil)
          :initial-value nil
          :from-end t))

It works as follows: after the partial results are returned by preduce-partial, each being a list in the reverse order of what we wish, we then walk backwards through the results, reversing and concatenating as we go.

defpun

Using plet is often a natural way to add parallelism to an algorithm. The result of doing so may be disappointing, however. Consider the classic Fibonacci example:

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(defun fib (n)
  (if (< n 2)
      n
      (let ((a (fib (- n 1)))
            (b (fib (- n 2))))
        (+ a b))))

(defun pfib-slow (n)
  (if (< n 2)
      n
      (plet ((a (pfib-slow (- n 1)))
             (b (pfib-slow (- n 2))))
        (+ a b))))

Living up to its name, pfib-slow is slow. Since plet spawns parallel tasks each time, and since addition is cheap, the overhead of task creation, scheduling, and execution will dominate.

(time (fib 25))
=> 75025
Evaluation took:
  0.002 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  0.00% CPU
  6,912,680 processor cycles
  0 bytes consed

(time (pfib-slow 25))
=> 75025
Evaluation took:
  0.028 seconds of real time
  0.096006 seconds of total run time (0.096006 user, 0.000000 system)
  342.86% CPU
  93,778,257 processor cycles
  29,136,576 bytes consed

How do we fix this? One way is to create fewer tasks by partitioning the computation into larger chunks.

(time (pmap-reduce 'fib '+ #(21 22 22 23)))
=> 75025
Evaluation took:
  0.001 seconds of real time
  0.000000 seconds of total run time (0.000000 user, 0.000000 system)
  0.00% CPU
  2,771,120 processor cycles
  96 bytes consed

In general it may not be easy to subdivide a computation and then glue the results together, as we have done here. The purpose of defpun is to handle this for us. defpun has the syntax and semantics of defun.

(defpun pfib (n)
  (if (< n 2)
      n
      (plet ((a (pfib (- n 1)))
             (b (pfib (- n 2))))
        (+ a b))))

The above code defines the pfib function.

(time (pfib 25))
=> 75025
Evaluation took:
  0.001 seconds of real time
  0.004000 seconds of total run time (0.004000 user, 0.000000 system)
  400.00% CPU
  2,601,638 processor cycles
  16,560 bytes consed

See benchmarks for more accurate measurements. Note that a high optimization level inside the defpun form may be required in order to obtain significant speedup.

How does defpun work? The plet macro has a local definition inside defpun. It expands into two distinct versions of its input: one version is a regular let form and the other is similar to the global plet but with logic added. The strategy resembles Cilk, where a “fast clone” and a “slow clone” are created from the input code. If we imagine a computation as one large tree, the fast clone is responsible for efficiently computing a given subtree while the slow clone is responsible for passing subtrees to the fast clone as parallel tasks and combining the results.

Ptrees

In cases where futures must wait upon the results of other futures, it may be more suitable to use a ptree instead. A ptree also has built-in support for retrying failed computations.

A ptree is a computation represented by a tree together with functionality to execute the tree in parallel. The simplest way to build and execute a ptree is with the ptree macro. Its syntax matches that of flet.

(defpackage :example (:use :cl :lparallel))
(in-package :example)

(ptree ((area   (width height) (* width height))
        (width  (border)       (+ 7 (* 2 border)))
        (height (border)       (+ 5 (* 2 border)))
        (border ()             1))
  area)

; => 63

This performs a parallelized version of the computation

(* (+ 7 (* 2 1))
   (+ 5 (* 2 1)))

; => 63

Each function in the ptree macro corresponds to a node in the generated tree. The relationships between the nodes are determined by the parameter names. In this example the area node has two child nodes labeled width and height; the width and height nodes share the same child node named border; and the border node has no children.

Each node contains a function and a result. The arguments passed to a node’s function are the respective results of its child nodes. The function result is stored in the node.

The function associated with a ptree node should be a pure function with regard to that ptree. It should depend only on its parameters and should not produce side-effects that impact other functions in the ptree. Otherwise, the result of the ptree computation is not well-defined.

Futures could also be used parallelize our example computation.

(let* ((border (future 1))
       (width  (future (+ 7 (* 2 (force border)))))
       (height (future (+ 5 (* 2 (force border)))))
       (area   (future (* (force width) (force height)))))
  (force area))

; => 63

What is the purpose of ptrees if futures can do the same thing? Futures are inadequate for large trees with interconnected relationships. A worker thread is effectively hijacked whenever a future waits on another future. A new temporary worker could be spawned to compensate for each worker that enters a waiting state, however in general that is an expensive solution which does not scale well.

The underlying issue is that futures have no knowledge of the computation tree in which they participate. Futures are simple and stupid; they work fine on their own but not in the context of interconnected futures. The solution is to describe the computation explicitly with a ptree. By examining the node relationships, a ptree is able to avoid the problem of blocked workers caused by futures.

Ptrees may be built dynamically as follows.

(let ((tree (make-ptree)))
  (ptree-fn 'area   '(width height) (lambda (w h) (* w h))       tree)
  (ptree-fn 'width  '(border)       (lambda (b)   (+ 7 (* 2 b))) tree)
  (ptree-fn 'height '(border)       (lambda (b)   (+ 5 (* 2 b))) tree)
  (ptree-fn 'border '()             (lambda ()    1)             tree)
  (call-ptree 'area tree))

; => 63

This code resembles the expansion of the ptree macro example above. Note that a node identifier need not be a symbol; any object suitable for eql comparison will do.

clear-ptree restores the tree to its original uncomputed state. clear-ptree-errors restores to the last pre-error state.

If the functions in a ptree themselves make use of parallelism—for instance if a node function calls pmap—then consider using a separate kernel for node computations by binding ptree-node-kernel to a kernel instance.

Benchmarks

The following benchmarks were conducted on SBCL / Linux / Core-i7 3.4GHz and Clozure / Darwin / Core 2 Duo 1.8GHz. On SBCL the new work-stealing model with lockless queues is enabled (the default on SBCL). Clozure uses the central queue model.

As with all sequence-related functions in lparallel, pmap and preduce acheive speedup by operating on chunks of sequences rather than on individual elements. psort uses a typical quicksort algorithm, which is naturally parallelizable. pfib (Fibonacci) and pmatrix-mul (matrix multiplication) use the fine-grained parallelism feature defpun.

All arrays are of type (simple-array single-float (*)). The rightmost column is time in microseconds.

SBCL / Linux / 4 cores

size     10 | op SIN      | MAP                 3
size     10 | op SIN      | MAP                 2
size     10 | op SIN      | MAP                 3
size     10 | op SIN      | MAP                 3
size     10 | op SIN      | PMAP               12
size     10 | op SIN      | PMAP               24
size     10 | op SIN      | PMAP               22
size     10 | op SIN      | PMAP               12

size     50 | op SIN      | MAP                 6
size     50 | op SIN      | MAP                 6
size     50 | op SIN      | MAP                 6
size     50 | op SIN      | MAP                 6
size     50 | op SIN      | PMAP               22
size     50 | op SIN      | PMAP               23
size     50 | op SIN      | PMAP               53
size     50 | op SIN      | PMAP               50

size    100 | op SIN      | MAP                10
size    100 | op SIN      | MAP                11
size    100 | op SIN      | MAP                11
size    100 | op SIN      | MAP                10
size    100 | op SIN      | PMAP               27
size    100 | op SIN      | PMAP               18
size    100 | op SIN      | PMAP               17
size    100 | op SIN      | PMAP               17

size    500 | op SIN      | MAP                59
size    500 | op SIN      | MAP                47
size    500 | op SIN      | MAP                51
size    500 | op SIN      | MAP                47
size    500 | op SIN      | PMAP               26
size    500 | op SIN      | PMAP               23
size    500 | op SIN      | PMAP               27
size    500 | op SIN      | PMAP               32

size   1000 | op SIN      | MAP                89
size   1000 | op SIN      | MAP                90
size   1000 | op SIN      | MAP                89
size   1000 | op SIN      | MAP                90
size   1000 | op SIN      | PMAP               31
size   1000 | op SIN      | PMAP               31
size   1000 | op SIN      | PMAP               36
size   1000 | op SIN      | PMAP               38

size   5000 | op SIN      | MAP               442
size   5000 | op SIN      | MAP               442
size   5000 | op SIN      | MAP               460
size   5000 | op SIN      | MAP               456
size   5000 | op SIN      | PMAP              128
size   5000 | op SIN      | PMAP              135
size   5000 | op SIN      | PMAP              134
size   5000 | op SIN      | PMAP              151

size  10000 | op SIN      | MAP               837
size  10000 | op SIN      | MAP               889
size  10000 | op SIN      | MAP               841
size  10000 | op SIN      | MAP               878
size  10000 | op SIN      | PMAP              196
size  10000 | op SIN      | PMAP              196
size  10000 | op SIN      | PMAP              198
size  10000 | op SIN      | PMAP              197

size  50000 | op SIN      | MAP              4224
size  50000 | op SIN      | MAP              4210
size  50000 | op SIN      | MAP              4228
size  50000 | op SIN      | MAP              4215
size  50000 | op SIN      | PMAP              947
size  50000 | op SIN      | PMAP              987
size  50000 | op SIN      | PMAP             1061
size  50000 | op SIN      | PMAP              953

size 100000 | op SIN      | MAP              9002
size 100000 | op SIN      | MAP              9025
size 100000 | op SIN      | MAP              9007
size 100000 | op SIN      | MAP              9116
size 100000 | op SIN      | PMAP             1976
size 100000 | op SIN      | PMAP             1989
size 100000 | op SIN      | PMAP             1970
size 100000 | op SIN      | PMAP             2005

size 500000 | op SIN      | MAP             44873
size 500000 | op SIN      | MAP             42728
size 500000 | op SIN      | MAP             45324
size 500000 | op SIN      | MAP             43025
size 500000 | op SIN      | PMAP             9092
size 500000 | op SIN      | PMAP             9131
size 500000 | op SIN      | PMAP             9055
size 500000 | op SIN      | PMAP             9021

size     10 | op +        | REDUCE              0
size     10 | op +        | REDUCE              0
size     10 | op +        | REDUCE              0
size     10 | op +        | REDUCE              1
size     10 | op +        | PREDUCE             5
size     10 | op +        | PREDUCE            14
size     10 | op +        | PREDUCE             6
size     10 | op +        | PREDUCE             5

size     50 | op +        | REDUCE              2
size     50 | op +        | REDUCE              3
size     50 | op +        | REDUCE              2
size     50 | op +        | REDUCE              1
size     50 | op +        | PREDUCE            16
size     50 | op +        | PREDUCE            10
size     50 | op +        | PREDUCE            10
size     50 | op +        | PREDUCE             9

size    100 | op +        | REDUCE              2
size    100 | op +        | REDUCE              3
size    100 | op +        | REDUCE              2
size    100 | op +        | REDUCE              3
size    100 | op +        | PREDUCE            11
size    100 | op +        | PREDUCE            11
size    100 | op +        | PREDUCE            10
size    100 | op +        | PREDUCE            11

size    500 | op +        | REDUCE             11
size    500 | op +        | REDUCE             14
size    500 | op +        | REDUCE             12
size    500 | op +        | REDUCE             13
size    500 | op +        | PREDUCE            12
size    500 | op +        | PREDUCE            11
size    500 | op +        | PREDUCE            11
size    500 | op +        | PREDUCE            11

size   1000 | op +        | REDUCE             24
size   1000 | op +        | REDUCE             24
size   1000 | op +        | REDUCE             23
size   1000 | op +        | REDUCE             24
size   1000 | op +        | PREDUCE            15
size   1000 | op +        | PREDUCE            21
size   1000 | op +        | PREDUCE            16
size   1000 | op +        | PREDUCE            15

size   5000 | op +        | REDUCE            115
size   5000 | op +        | REDUCE            119
size   5000 | op +        | REDUCE            116
size   5000 | op +        | REDUCE            116
size   5000 | op +        | PREDUCE            56
size   5000 | op +        | PREDUCE            57
size   5000 | op +        | PREDUCE            57
size   5000 | op +        | PREDUCE            61

size  10000 | op +        | REDUCE            233
size  10000 | op +        | REDUCE            232
size  10000 | op +        | REDUCE            233
size  10000 | op +        | REDUCE            233
size  10000 | op +        | PREDUCE           105
size  10000 | op +        | PREDUCE           111
size  10000 | op +        | PREDUCE           105
size  10000 | op +        | PREDUCE           102

size  50000 | op +        | REDUCE            990
size  50000 | op +        | REDUCE            993
size  50000 | op +        | REDUCE           1018
size  50000 | op +        | REDUCE            996
size  50000 | op +        | PREDUCE           501
size  50000 | op +        | PREDUCE           508
size  50000 | op +        | PREDUCE           486
size  50000 | op +        | PREDUCE           488

size 100000 | op +        | REDUCE           2025
size 100000 | op +        | REDUCE           2000
size 100000 | op +        | REDUCE           2044
size 100000 | op +        | REDUCE           1997
size 100000 | op +        | PREDUCE           975
size 100000 | op +        | PREDUCE           965
size 100000 | op +        | PREDUCE           941
size 100000 | op +        | PREDUCE           965

size 500000 | op +        | REDUCE          10076
size 500000 | op +        | REDUCE          10094
size 500000 | op +        | REDUCE          11394
size 500000 | op +        | REDUCE          10147
size 500000 | op +        | PREDUCE          4250
size 500000 | op +        | PREDUCE          2834
size 500000 | op +        | PREDUCE          2829
size 500000 | op +        | PREDUCE          5336

size     10 | op <        | SORT                2
size     10 | op <        | SORT                3
size     10 | op <        | SORT                2
size     10 | op <        | SORT                2
size     10 | op <        | PSORT               4
size     10 | op <        | PSORT               4
size     10 | op <        | PSORT               4
size     10 | op <        | PSORT               5

size     50 | op <        | SORT               16
size     50 | op <        | SORT               17
size     50 | op <        | SORT               17
size     50 | op <        | SORT               17
size     50 | op <        | PSORT              14
size     50 | op <        | PSORT              13
size     50 | op <        | PSORT              13
size     50 | op <        | PSORT              11

size    100 | op <        | SORT               41
size    100 | op <        | SORT               42
size    100 | op <        | SORT               40
size    100 | op <        | SORT               40
size    100 | op <        | PSORT              25
size    100 | op <        | PSORT              21
size    100 | op <        | PSORT              28
size    100 | op <        | PSORT              23

size    500 | op <        | SORT              284
size    500 | op <        | SORT              284
size    500 | op <        | SORT              295
size    500 | op <        | SORT              302
size    500 | op <        | PSORT              97
size    500 | op <        | PSORT              96
size    500 | op <        | PSORT              94
size    500 | op <        | PSORT             121

size   1000 | op <        | SORT              684
size   1000 | op <        | SORT              685
size   1000 | op <        | SORT              681
size   1000 | op <        | SORT              685
size   1000 | op <        | PSORT             274
size   1000 | op <        | PSORT             277
size   1000 | op <        | PSORT             288
size   1000 | op <        | PSORT             318

size   5000 | op <        | SORT             3952
size   5000 | op <        | SORT             3948
size   5000 | op <        | SORT             3949
size   5000 | op <        | SORT             4127
size   5000 | op <        | PSORT            1089
size   5000 | op <        | PSORT            1016
size   5000 | op <        | PSORT            2863
size   5000 | op <        | PSORT            1067

size  10000 | op <        | SORT             8605
size  10000 | op <        | SORT             8627
size  10000 | op <        | SORT             8618
size  10000 | op <        | SORT             8625
size  10000 | op <        | PSORT            2710
size  10000 | op <        | PSORT            2362
size  10000 | op <        | PSORT            2382
size  10000 | op <        | PSORT            2623

size  50000 | op <        | SORT            51234
size  50000 | op <        | SORT            52806
size  50000 | op <        | SORT            51456
size  50000 | op <        | SORT            52556
size  50000 | op <        | PSORT           14694
size  50000 | op <        | PSORT           21582
size  50000 | op <        | PSORT           19204
size  50000 | op <        | PSORT           13035

size 100000 | op <        | SORT           112340
size 100000 | op <        | SORT           110923
size 100000 | op <        | SORT           112489
size 100000 | op <        | SORT           110955
size 100000 | op <        | PSORT           41906
size 100000 | op <        | PSORT           44487
size 100000 | op <        | PSORT           44311
size 100000 | op <        | PSORT           42166

size 200000 | op <        | SORT           238384
size 200000 | op <        | SORT           238247
size 200000 | op <        | SORT           238630
size 200000 | op <        | SORT           238458
size 200000 | op <        | PSORT           59908
size 200000 | op <        | PSORT           59434
size 200000 | op <        | PSORT           63248
size 200000 | op <        | PSORT           61640

n      5 | FIB-LET                0
n      5 | FIB-LET                0
n      5 | FIB-LET                0
n      5 | FIB-LET                0
n      5 | FIB-PLET               3
n      5 | FIB-PLET               8
n      5 | FIB-PLET               1
n      5 | FIB-PLET               2
n      5 | FIB-PLET-IF            1
n      5 | FIB-PLET-IF            1
n      5 | FIB-PLET-IF            1
n      5 | FIB-PLET-IF            0

n     10 | FIB-LET                2
n     10 | FIB-LET                2
n     10 | FIB-LET                2
n     10 | FIB-LET                2
n     10 | FIB-PLET               2
n     10 | FIB-PLET               3
n     10 | FIB-PLET               4
n     10 | FIB-PLET               4
n     10 | FIB-PLET-IF            1
n     10 | FIB-PLET-IF            2
n     10 | FIB-PLET-IF            2
n     10 | FIB-PLET-IF            1

n     15 | FIB-LET               13
n     15 | FIB-LET               13
n     15 | FIB-LET               13
n     15 | FIB-LET               13
n     15 | FIB-PLET              10
n     15 | FIB-PLET              10
n     15 | FIB-PLET              10
n     15 | FIB-PLET              11
n     15 | FIB-PLET-IF           16
n     15 | FIB-PLET-IF           16
n     15 | FIB-PLET-IF           15
n     15 | FIB-PLET-IF           16

n     20 | FIB-LET              135
n     20 | FIB-LET              134
n     20 | FIB-LET              134
n     20 | FIB-LET              134
n     20 | FIB-PLET              43
n     20 | FIB-PLET              44
n     20 | FIB-PLET              48
n     20 | FIB-PLET              54
n     20 | FIB-PLET-IF           43
n     20 | FIB-PLET-IF           44
n     20 | FIB-PLET-IF           43
n     20 | FIB-PLET-IF           44

n     25 | FIB-LET             1493
n     25 | FIB-LET             1485
n     25 | FIB-LET             1499
n     25 | FIB-LET             1480
n     25 | FIB-PLET             442
n     25 | FIB-PLET             446
n     25 | FIB-PLET             444
n     25 | FIB-PLET             405
n     25 | FIB-PLET-IF          408
n     25 | FIB-PLET-IF          400
n     25 | FIB-PLET-IF          393
n     25 | FIB-PLET-IF          436

n     30 | FIB-LET            16044
n     30 | FIB-LET            16039
n     30 | FIB-LET            16039
n     30 | FIB-LET            16070
n     30 | FIB-PLET            4743
n     30 | FIB-PLET            4378
n     30 | FIB-PLET            4532
n     30 | FIB-PLET            5762
n     30 | FIB-PLET-IF         4372
n     30 | FIB-PLET-IF         4296
n     30 | FIB-PLET-IF         4356
n     30 | FIB-PLET-IF         4310

n     35 | FIB-LET           178913
n     35 | FIB-LET           180466
n     35 | FIB-LET           178761
n     35 | FIB-LET           178801
n     35 | FIB-PLET           46752
n     35 | FIB-PLET           48785
n     35 | FIB-PLET           49776
n     35 | FIB-PLET           46423
n     35 | FIB-PLET-IF        48273
n     35 | FIB-PLET-IF        48348
n     35 | FIB-PLET-IF        47367
n     35 | FIB-PLET-IF        47368

n      5 | MATRIX-MUL             4
n      5 | MATRIX-MUL             4
n      5 | MATRIX-MUL             4
n      5 | MATRIX-MUL             5
n      5 | PMATRIX-MUL            4
n      5 | PMATRIX-MUL            3
n      5 | PMATRIX-MUL            4
n      5 | PMATRIX-MUL            3

n     10 | MATRIX-MUL            21
n     10 | MATRIX-MUL            21
n     10 | MATRIX-MUL            22
n     10 | MATRIX-MUL            21
n     10 | PMATRIX-MUL            8
n     10 | PMATRIX-MUL           12
n     10 | PMATRIX-MUL            8
n     10 | PMATRIX-MUL           12

n     50 | MATRIX-MUL          2090
n     50 | MATRIX-MUL          2074
n     50 | MATRIX-MUL          2088
n     50 | MATRIX-MUL          2074
n     50 | PMATRIX-MUL          600
n     50 | PMATRIX-MUL          565
n     50 | PMATRIX-MUL          575
n     50 | PMATRIX-MUL          574

n    100 | MATRIX-MUL         16079
n    100 | MATRIX-MUL         16060
n    100 | MATRIX-MUL         15848
n    100 | MATRIX-MUL         15757
n    100 | PMATRIX-MUL         4280
n    100 | PMATRIX-MUL         4278
n    100 | PMATRIX-MUL         4278
n    100 | PMATRIX-MUL         4276

n    200 | MATRIX-MUL        124417
n    200 | MATRIX-MUL        123705
n    200 | MATRIX-MUL        126760
n    200 | MATRIX-MUL        124257
n    200 | PMATRIX-MUL        33465
n    200 | PMATRIX-MUL        33445
n    200 | PMATRIX-MUL        33444
n    200 | PMATRIX-MUL        33478

Clozure / Darwin / 2 cores

size     10 | op SIN      | MAP                19
size     10 | op SIN      | MAP                20
size     10 | op SIN      | MAP                19
size     10 | op SIN      | MAP                20
size     10 | op SIN      | PMAP              108
size     10 | op SIN      | PMAP              108
size     10 | op SIN      | PMAP               95
size     10 | op SIN      | PMAP               89

size     50 | op SIN      | MAP                29
size     50 | op SIN      | MAP                29
size     50 | op SIN      | MAP                29
size     50 | op SIN      | MAP                29
size     50 | op SIN      | PMAP               99
size     50 | op SIN      | PMAP              101
size     50 | op SIN      | PMAP               98
size     50 | op SIN      | PMAP              100

size    100 | op SIN      | MAP                42
size    100 | op SIN      | MAP                42
size    100 | op SIN      | MAP                42
size    100 | op SIN      | MAP                42
size    100 | op SIN      | PMAP              133
size    100 | op SIN      | PMAP              145
size    100 | op SIN      | PMAP              133
size    100 | op SIN      | PMAP              144

size    500 | op SIN      | MAP               143
size    500 | op SIN      | MAP               143
size    500 | op SIN      | MAP               143
size    500 | op SIN      | MAP               142
size    500 | op SIN      | PMAP              185
size    500 | op SIN      | PMAP              190
size    500 | op SIN      | PMAP              198
size    500 | op SIN      | PMAP              187

size   1000 | op SIN      | MAP               269
size   1000 | op SIN      | MAP               270
size   1000 | op SIN      | MAP               270
size   1000 | op SIN      | MAP               270
size   1000 | op SIN      | PMAP              255
size   1000 | op SIN      | PMAP              237
size   1000 | op SIN      | PMAP              254
size   1000 | op SIN      | PMAP              263

size   5000 | op SIN      | MAP              1480
size   5000 | op SIN      | MAP              1539
size   5000 | op SIN      | MAP              1553
size   5000 | op SIN      | MAP              1554
size   5000 | op SIN      | PMAP              786
size   5000 | op SIN      | PMAP              779
size   5000 | op SIN      | PMAP              781
size   5000 | op SIN      | PMAP              797

size  10000 | op SIN      | MAP              2741
size  10000 | op SIN      | MAP              2686
size  10000 | op SIN      | MAP              2761
size  10000 | op SIN      | MAP              2746
size  10000 | op SIN      | PMAP             1449
size  10000 | op SIN      | PMAP             1468
size  10000 | op SIN      | PMAP             1635
size  10000 | op SIN      | PMAP             1479

size  50000 | op SIN      | MAP             18660
size  50000 | op SIN      | MAP             13311
size  50000 | op SIN      | MAP             18647
size  50000 | op SIN      | MAP             13354
size  50000 | op SIN      | PMAP             7002
size  50000 | op SIN      | PMAP             6935
size  50000 | op SIN      | PMAP             9493
size  50000 | op SIN      | PMAP             6949

size 100000 | op SIN      | MAP             35478
size 100000 | op SIN      | MAP             35885
size 100000 | op SIN      | MAP             36514
size 100000 | op SIN      | MAP             34902
size 100000 | op SIN      | PMAP            13699
size 100000 | op SIN      | PMAP            13776
size 100000 | op SIN      | PMAP            13711
size 100000 | op SIN      | PMAP            16364

size 500000 | op SIN      | MAP            279260
size 500000 | op SIN      | MAP            162787
size 500000 | op SIN      | MAP            170616
size 500000 | op SIN      | MAP            191481
size 500000 | op SIN      | PMAP            68104
size 500000 | op SIN      | PMAP            73681
size 500000 | op SIN      | PMAP            68150
size 500000 | op SIN      | PMAP            73888

size     10 | op +        | REDUCE              1
size     10 | op +        | REDUCE              1
size     10 | op +        | REDUCE              1
size     10 | op +        | REDUCE              1
size     10 | op +        | PREDUCE            77
size     10 | op +        | PREDUCE            72
size     10 | op +        | PREDUCE            67
size     10 | op +        | PREDUCE            78

size     50 | op +        | REDUCE              4
size     50 | op +        | REDUCE              4
size     50 | op +        | REDUCE              4
size     50 | op +        | REDUCE              4
size     50 | op +        | PREDUCE            51
size     50 | op +        | PREDUCE            79
size     50 | op +        | PREDUCE            51
size     50 | op +        | PREDUCE            80

size    100 | op +        | REDUCE              7
size    100 | op +        | REDUCE              7
size    100 | op +        | REDUCE              8
size    100 | op +        | REDUCE              8
size    100 | op +        | PREDUCE            64
size    100 | op +        | PREDUCE            71
size    100 | op +        | PREDUCE            64
size    100 | op +        | PREDUCE            71

size    500 | op +        | REDUCE             37
size    500 | op +        | REDUCE             37
size    500 | op +        | REDUCE             37
size    500 | op +        | REDUCE             37
size    500 | op +        | PREDUCE           109
size    500 | op +        | PREDUCE            99
size    500 | op +        | PREDUCE            99
size    500 | op +        | PREDUCE            99

size   1000 | op +        | REDUCE             74
size   1000 | op +        | REDUCE             74
size   1000 | op +        | REDUCE             74
size   1000 | op +        | REDUCE             74
size   1000 | op +        | PREDUCE           128
size   1000 | op +        | PREDUCE           117
size   1000 | op +        | PREDUCE           118
size   1000 | op +        | PREDUCE           126

size   5000 | op +        | REDUCE            368
size   5000 | op +        | REDUCE            408
size   5000 | op +        | REDUCE            368
size   5000 | op +        | REDUCE            368
size   5000 | op +        | PREDUCE           340
size   5000 | op +        | PREDUCE           265
size   5000 | op +        | PREDUCE           247
size   5000 | op +        | PREDUCE           274

size  10000 | op +        | REDUCE            730
size  10000 | op +        | REDUCE            730
size  10000 | op +        | REDUCE            730
size  10000 | op +        | REDUCE            715
size  10000 | op +        | PREDUCE           447
size  10000 | op +        | PREDUCE           434
size  10000 | op +        | PREDUCE           447
size  10000 | op +        | PREDUCE           433

size  50000 | op +        | REDUCE           3674
size  50000 | op +        | REDUCE           3686
size  50000 | op +        | REDUCE           3674
size  50000 | op +        | REDUCE           3684
size  50000 | op +        | PREDUCE          1897
size  50000 | op +        | PREDUCE          1904
size  50000 | op +        | PREDUCE          1896
size  50000 | op +        | PREDUCE          1944

size 100000 | op +        | REDUCE           7368
size 100000 | op +        | REDUCE           7370
size 100000 | op +        | REDUCE           7370
size 100000 | op +        | REDUCE           7368
size 100000 | op +        | PREDUCE          3753
size 100000 | op +        | PREDUCE          3791
size 100000 | op +        | PREDUCE          3743
size 100000 | op +        | PREDUCE          3741

size 500000 | op +        | REDUCE          37027
size 500000 | op +        | REDUCE          36895
size 500000 | op +        | REDUCE          36891
size 500000 | op +        | REDUCE          36884
size 500000 | op +        | PREDUCE         18630
size 500000 | op +        | PREDUCE         18570
size 500000 | op +        | PREDUCE         18685
size 500000 | op +        | PREDUCE         18592

size     10 | op <        | SORT                3
size     10 | op <        | SORT                3
size     10 | op <        | SORT                3
size     10 | op <        | SORT                3
size     10 | op <        | PSORT              99
size     10 | op <        | PSORT              95
size     10 | op <        | PSORT             101
size     10 | op <        | PSORT              95

size     50 | op <        | SORT               24
size     50 | op <        | SORT               23
size     50 | op <        | SORT               23
size     50 | op <        | SORT               24
size     50 | op <        | PSORT             123
size     50 | op <        | PSORT             123
size     50 | op <        | PSORT             123
size     50 | op <        | PSORT             131

size    100 | op <        | SORT               61
size    100 | op <        | SORT               61
size    100 | op <        | SORT               61
size    100 | op <        | SORT               61
size    100 | op <        | PSORT             176
size    100 | op <        | PSORT             187
size    100 | op <        | PSORT             193
size    100 | op <        | PSORT             188

size    500 | op <        | SORT              452
size    500 | op <        | SORT              448
size    500 | op <        | SORT              449
size    500 | op <        | SORT              455
size    500 | op <        | PSORT             362
size    500 | op <        | PSORT             363
size    500 | op <        | PSORT             360
size    500 | op <        | PSORT             362

size   1000 | op <        | SORT             1031
size   1000 | op <        | SORT             1036
size   1000 | op <        | SORT             1032
size   1000 | op <        | SORT             1032
size   1000 | op <        | PSORT             643
size   1000 | op <        | PSORT             648
size   1000 | op <        | PSORT             647
size   1000 | op <        | PSORT             646

size   5000 | op <        | SORT             6928
size   5000 | op <        | SORT             6919
size   5000 | op <        | SORT             6919
size   5000 | op <        | SORT             6934
size   5000 | op <        | PSORT            4167
size   5000 | op <        | PSORT            4166
size   5000 | op <        | PSORT            4177
size   5000 | op <        | PSORT            4173

size  10000 | op <        | SORT            13865
size  10000 | op <        | SORT            13931
size  10000 | op <        | SORT            13860
size  10000 | op <        | SORT            13857
size  10000 | op <        | PSORT            8933
size  10000 | op <        | PSORT            8925
size  10000 | op <        | PSORT            8904
size  10000 | op <        | PSORT            8942

size  50000 | op <        | SORT            84698
size  50000 | op <        | SORT            83833
size  50000 | op <        | SORT            83882
size  50000 | op <        | SORT            83927
size  50000 | op <        | PSORT           54610
size  50000 | op <        | PSORT           54669
size  50000 | op <        | PSORT           54475
size  50000 | op <        | PSORT           54613

size 100000 | op <        | SORT           179542
size 100000 | op <        | SORT           179620
size 100000 | op <        | SORT           179544
size 100000 | op <        | SORT           179628
size 100000 | op <        | PSORT          127452
size 100000 | op <        | PSORT          127176
size 100000 | op <        | PSORT          127589
size 100000 | op <        | PSORT          126974

size 200000 | op <        | SORT           373940
size 200000 | op <        | SORT           373704
size 200000 | op <        | SORT           373814
size 200000 | op <        | SORT           373912
size 200000 | op <        | PSORT          198871
size 200000 | op <        | PSORT          199304
size 200000 | op <        | PSORT          198842
size 200000 | op <        | PSORT          198844

n      5 | FIB-LET                0
n      5 | FIB-LET                1
n      5 | FIB-LET                0
n      5 | FIB-LET                0
n      5 | FIB-PLET             104
n      5 | FIB-PLET             109
n      5 | FIB-PLET              97
n      5 | FIB-PLET             100
n      5 | FIB-PLET-IF            0
n      5 | FIB-PLET-IF            0
n      5 | FIB-PLET-IF            0
n      5 | FIB-PLET-IF            1

n     10 | FIB-LET                2
n     10 | FIB-LET                3
n     10 | FIB-LET                2
n     10 | FIB-LET                2
n     10 | FIB-PLET              97
n     10 | FIB-PLET              97
n     10 | FIB-PLET              98
n     10 | FIB-PLET              98
n     10 | FIB-PLET-IF            3
n     10 | FIB-PLET-IF            3
n     10 | FIB-PLET-IF            3
n     10 | FIB-PLET-IF            3

n     15 | FIB-LET               25
n     15 | FIB-LET               25
n     15 | FIB-LET               26
n     15 | FIB-LET               26
n     15 | FIB-PLET             259
n     15 | FIB-PLET             184
n     15 | FIB-PLET             220
n     15 | FIB-PLET              75
n     15 | FIB-PLET-IF           27
n     15 | FIB-PLET-IF           27
n     15 | FIB-PLET-IF           27
n     15 | FIB-PLET-IF           31

n     20 | FIB-LET              277
n     20 | FIB-LET              277
n     20 | FIB-LET              277
n     20 | FIB-LET              277
n     20 | FIB-PLET             569
n     20 | FIB-PLET             458
n     20 | FIB-PLET             894
n     20 | FIB-PLET             408
n     20 | FIB-PLET-IF          210
n     20 | FIB-PLET-IF          208
n     20 | FIB-PLET-IF          211
n     20 | FIB-PLET-IF          208

n     25 | FIB-LET             3061
n     25 | FIB-LET             3058
n     25 | FIB-LET             3059
n     25 | FIB-LET             3062
n     25 | FIB-PLET            2101
n     25 | FIB-PLET            2728
n     25 | FIB-PLET            2205
n     25 | FIB-PLET            1989
n     25 | FIB-PLET-IF         1760
n     25 | FIB-PLET-IF         1925
n     25 | FIB-PLET-IF         1832
n     25 | FIB-PLET-IF         1797

n     30 | FIB-LET            34010
n     30 | FIB-LET            33998
n     30 | FIB-LET            33929
n     30 | FIB-LET            34031
n     30 | FIB-PLET           19112
n     30 | FIB-PLET           19656
n     30 | FIB-PLET           19118
n     30 | FIB-PLET           22719
n     30 | FIB-PLET-IF        19280
n     30 | FIB-PLET-IF        22369
n     30 | FIB-PLET-IF        19268
n     30 | FIB-PLET-IF        18161

n     35 | FIB-LET           376509
n     35 | FIB-LET           375270
n     35 | FIB-LET           375297
n     35 | FIB-LET           375402
n     35 | FIB-PLET          207037
n     35 | FIB-PLET          209237
n     35 | FIB-PLET          195259
n     35 | FIB-PLET          204691
n     35 | FIB-PLET-IF       183718
n     35 | FIB-PLET-IF       192542
n     35 | FIB-PLET-IF       185317
n     35 | FIB-PLET-IF       185194

n      5 | MATRIX-MUL             8
n      5 | MATRIX-MUL             8
n      5 | MATRIX-MUL             8
n      5 | MATRIX-MUL             9
n      5 | PMATRIX-MUL           77
n      5 | PMATRIX-MUL           88
n      5 | PMATRIX-MUL           96
n      5 | PMATRIX-MUL           96

n     10 | MATRIX-MUL            50
n     10 | MATRIX-MUL            50
n     10 | MATRIX-MUL            50
n     10 | MATRIX-MUL            51
n     10 | PMATRIX-MUL          118
n     10 | PMATRIX-MUL           90
n     10 | PMATRIX-MUL          119
n     10 | PMATRIX-MUL          114

n     50 | MATRIX-MUL          4767
n     50 | MATRIX-MUL          4844
n     50 | MATRIX-MUL          4733
n     50 | MATRIX-MUL          4832
n     50 | PMATRIX-MUL         2570
n     50 | PMATRIX-MUL         2471
n     50 | PMATRIX-MUL         6237
n     50 | PMATRIX-MUL         2454

n    100 | MATRIX-MUL         39091
n    100 | MATRIX-MUL         36697
n    100 | MATRIX-MUL         36823
n    100 | MATRIX-MUL         36829
n    100 | PMATRIX-MUL        18725
n    100 | PMATRIX-MUL        21860
n    100 | PMATRIX-MUL        18701
n    100 | PMATRIX-MUL        18679

n    200 | MATRIX-MUL        286528
n    200 | MATRIX-MUL        288732
n    200 | MATRIX-MUL        286697
n    200 | MATRIX-MUL        288674
n    200 | PMATRIX-MUL       166732
n    200 | PMATRIX-MUL       150359
n    200 | PMATRIX-MUL       155815
n    200 | PMATRIX-MUL       154714

API

The lparallel API is divided into five packages:

lparallel.kernel
lparallel.promise
lparallel.cognate
lparallel.ptree
lparallel.defpun

For convenience, the exported symbols of each package are also exported by the lparallel package.

The lparallel.queue package provides a queue for inter-thread communication. It is not included in the lparallel package.

Kernel

[special variable] *debug-tasks-p*

If true (the default), the debugger is invoked when an error goes unhandled inside a task, i.e. when the handlers established by `task-handler-bind’ (if any) do not handle it.

If false, unhandled errors from tasks are automatically transferred to their parent thread (and/or any dependent threads) via the `transfer-error’ restart. This is for convenience — sometimes you wish to avoid N debugger popups arising from N errors in N worker threads.

For local control over debugger invocation, bind a task handler:

(task-handler-bind ((error #’invoke-debugger)) …)

(task-handler-bind ((error #’invoke-transfer-error)) …)

[special variable] *kernel*

The current kernel, or nil.

[special variable] *kernel-spin-count*

Default value of the `spin-count’ argument to `make-kernel’.

[special variable] *task-category*

See `kill-tasks’. Default value is `:default’.

[special variable] *task-priority*

When bound to `:low’, the kernel schedules submitted tasks at low priority. Default value is `:default’.

[function] broadcast-task function &rest args

Wait for current and pending tasks to complete, if any, then simultaneously execute the given task inside each worker. Wait until these tasks finish, then return the results in a vector.

Calling `broadcast-task’ from inside a worker is an error.

[function] cancel-timeout timeout timeout-result

Attempt to cancel a timeout. If successful, the channel passed to `submit-timeout’ will receive `timeout-result’.

At most one call to `cancel-timeout’ will succeed; others will be ignored. If the timeout has expired on its own then `cancel-timeout’ will have no effect.

`cancel-timeout’ is not available in ABCL.

`submit-timeout’ and `cancel-timeout’ are deprecated; use the new `:timeout’ option in `try-receive-result’.

[function] check-kernel

Ensures the value of `*kernel*’ is a kernel instance. Provides the MAKE-KERNEL and STORE-VALUE restarts. Returns `*kernel*’.

[function] end-kernel &key wait

Sets `*kernel*’ to nil and ends all workers gracefully.

`end-kernel’ should not be used as a substitute for properly waiting on tasks with `receive-result’ or otherwise.

If `wait’ is nil (the default) then `end-kernel’ returns immediately. Workers are waited upon by a separate shutdown manager thread.

If `wait’ is non-nil then `end-kernel’ blocks until all workers are finished. No shutdown manager thread is created.

A list of the implementation-defined worker thread objects is returned. If `wait’ is nil then the shutdown manager thread is also returned as the first element in the list.

Note that creating and destroying kernels is relatively expensive. A kernel typically exists for lifetime of the Lisp process. Having more than one kernel is fine — simply use `let’ to bind a kernel instance to `*kernel*’ when you need it. Use `kill-tasks’ to terminate deadlocked or infinite looping tasks.

[function] invoke-transfer-error error

Equivalent to (invoke-restart ‘transfer-error error).

This is a convenience function for use in `task-handler-bind’.

[function] kernel-bindings

Return the bindings passed to `make-kernel’.

[function] kernel-context

Return the context passed to `make-kernel’.

[function] kernel-name

Return the name passed to `make-kernel’.

[function] kernel-worker-count

Return the number of workers in the current kernel.

[function] kernel-worker-index

If called from inside a worker, return the worker’s assigned index, ranging from 0 to one less than (kernel-worker-count).

If not called from inside a worker, return nil.

[function] kill-tasks task-category &key dry-run

This is an expensive function which should only be used in exceptional circumstances.

Every task has an associated task category. When a task is submitted, it is assigned the category of `*task-category*’ which has a default value of `:default’.

`kill-tasks’ interrupts running tasks whose category is `eql’ to `task-category’. The corresponding worker threads are killed and replaced. Pending tasks are not affected.

If you don’t know what to pass for `task-category’ then you should probably pass `:default’, though this may kill more tasks than you wish. Binding `*task-category*’ around `submit-task’ enables targeted task killing.

If `dry-run’ is nil, the function returns the number of tasks killed.

If `dry-run’ is non-nil then no tasks are killed. In this case the return value is the number of tasks that would have been killed if `dry-run’ were nil.

`kill-tasks’ is not available in ABCL.

[function] make-channel &rest args

Create a channel for submitting and receiving tasks. The current value of `*kernel*’ is stored for use in `submit-task’.

By default there is no limit on the channel capacity. Passing a `fixed-capacity’ keyword argument limits the capacity to the value passed.

Note that a fixed capacity channel may cause a deadlocked kernel if `receive-result’ is not called a sufficient number of times.

[function] make-kernel worker-count &key name bindings context spin-count use-caller

Create a kernel with `worker-count’ number of worker threads.

`name’ is a string identifier for this kernel which is reported by `print-object’. Worker threads will also be given this name, shown in `bordeaux-threads:all-threads’.

`bindings’ is an alist for establishing thread-local variables inside worker threads. By default workers will have *standard-output* and *error-output* bindings.

Dynamic context for each worker may be established with the function `context’. The argument passed to `context’ is a function which must be funcalled. It begins the worker loop and will not return until the worker exits. The default value of `context’ is #’funcall. The special variables in `bindings’ are available inside the `context’ function.

When a worker discovers that no tasks are available, `spin-count’ is the number of task-searching iterations done by the worker before sleeping.

If `use-caller’ is true (default is false) then in certain situations the calling thread may be enlisted to steal work from worker threads. This is an optimization strategy that currently applies only during the execution of functions defined by `defpun’ and `defpun/type’. Typically in this case the number of workers will be one less than the number of cores/CPUs.

A kernel will not be garbage collected until `end-kernel’ is called.

[function] receive-result channel

Remove a result from `channel’. If nothing is available the call will block until a result is received.

[function] submit-task channel function &rest args

Submit a task through `channel’ to the kernel stored in `channel’.

[function] submit-timeout channel timeout-seconds timeout-result

Effectively equivalent to

(submit-task channel (lambda () (sleep timeout-seconds) timeout-result))

The difference is that `submit-timeout’ does not occupy a worker thread.

A timeout object is returned, which may be passed to `cancel-timeout’.

`submit-timeout’ and `cancel-timeout’ are deprecated; use the new `:timeout’ option in `try-receive-result’.

[function] task-categories-running

Return a vector containing the task category currently running for each worker.

[macro] task-handler-bind clauses &body body

Like `handler-bind’ but handles conditions signaled inside tasks that were created in `body’.

[function] try-receive-result channel &key timeout

If `channel’ has a result then remove it and return (values result t).

If no result is available and `timeout’ is given, then wait up to `timeout’ seconds for a result.

If the channel is empty and the timeout has expired, or if the channel is empty and no timeout was given, return (values nil nil).

Providing a nil or non-positive value of `timeout’ is equivalent to providing no timeout.

Promises

See Promises for an introduction.

The following symbols are exported by the lparallel.promise package as well as by the convenience package lparallel .


[function] chain object

Create a chain. A chain links objects together by relaying `force’ and `fulfilledp’ calls.

[macro] delay &body body

Create a delay. A delay is a promise which is fulfilled when `force’ is called upon it.

[function] force object

If `object’ is a promise and the promise is fulfilled, return the fulfilled value (possibly multiple values). If the promise is unfulfilled then the call blocks until the promise is fulfilled.

If `object’ is a chain, call `force’ on the chained object.

If `object’ is not a promise and not a chain, return the identical object passed.

Note if `force’ is called on an unfulfilled future then the future is fulfilled by the caller of `force’.

[macro] fulfill object &body body

Attempt to give `object’ a value.

If `object’ is a promise which is not fulfilled and not currently being fulfilled, then the implicit progn `body’ will be executed and the promise will store the result. In this case `fulfill’ returns true.

If `object’ is a promise that is either already fulfilled or actively being fulfilled, then `body’ will not be executed and `fulfill’ returns false.

If `object’ is a chain, call `fulfill’ on the chained object.

If `object’ is not a promise and not a chain then false is returned immediately, with `body’ being ignored.

[function] fulfilledp object

If `object’ is a promise, return a boolean indicating whether the promise is fulfilled.

If `object’ is a chain, call `fulfilledp’ on the chained object.

If `object’ is not a promise and not a chain, return true.

[macro] future &body body

Create a future. A future is a promise which is fulfilled in parallel by the implicit progn `body’.

[function] promise

Create a promise. A promise is a receptacle for a result which is unknown at the time it is created.

[macro] speculate &body body

Create a speculation. A speculation is a low-priority future.

Cognates

See Cognates for an introduction.

The following symbols are exported by the lparallel.cognate package as well as by the convenience package lparallel .


[macro] pand &rest forms

Parallel version of `and’. Forms in `forms’ may be executed in parallel, though not necessarily at the same time. If all forms evaluate to true, then the result of any form may be returned.

[function] pcount item sequence &key from-end start end key test test-not parts

Parallel version of `count’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] pcount-if predicate sequence &key from-end start end key parts

Parallel version of `count-if’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] pcount-if-not predicate sequence &rest args &key from-end start end key parts

Parallel version of `count-if-not’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[macro] pdotimes (var count &optional result parts) &body body

Parallel version of `dotimes’.

The `parts’ option divides the integer range into `parts’ number of parts. Default is (kernel-worker-count).

Unlike `dotimes’, `pdotimes’ does not define an implicit block named nil.

[function] pevery predicate &rest sequences

Parallel version of `every’. Calls to `predicate’ are done in parallel, though not necessarily at the same time. Behavior is otherwise indistinguishable from `every’.

Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pfind item sequence &rest args &key from-end test test-not start end key parts

Parallel version of `pfind’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] pfind-if predicate sequence &rest args &key from-end start end key parts

Parallel version of `pfind-if’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] pfind-if-not predicate sequence &rest args &key from-end start end key parts

Parallel version of `pfind-if-not’.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[macro] pfuncall function &rest args

Parallel version of `funcall’. Arguments in `args’ may be executed in parallel, though not necessarily at the same time.

[macro] plet bindings &body body

The syntax of `plet’ matches that of `let’.

plet ({var-no-init | (var [init-form]) | ((var1 var2 …) [init-form])}*) declaration* form*

For each (var init-form) pair, a future is created which executes `init-form’. Inside `body’, `var’ is a symbol macro which expands to a `force’ form for the corresponding future.

Likewise, each ((var1 var2 …) init-form) pair creates a future where `var1′, `var2′,… are bound to the respective multiple return values of `init-form’.

Each `var-no-init’ is bound to nil and each variable without a corresponding `init-form’ is bound to nil (no future is created).

Type declarations for vars are recognized by `plet’ and incorporated into the final expansion. The semantics of these declarations are the same as those of a regular `let’ form.

`plet’ is subject to optimization inside `defpun’.

[macro] plet-if predicate bindings &body body

The syntax of `plet-if’ matches that of `plet’ except for the addition of the `predicate’ form.

If `predicate’ evaluates to true, the behavior is the same as `plet’.

If `predicate’ evaluates to false, the behavior is the same as `slet’.

`plet-if’ is subject to optimization inside `defpun’.

[function] pmap result-type function &rest sequences

Parallel version of `map’. Keyword arguments `parts’ and `size’ are also accepted.

The `parts’ option divides each sequence into `parts’ number of parts. Default is (kernel-worker-count).

The `size’ option limits the number of elements mapped to `size’. When given, no `length’ calls are made on the sequence(s) passed.

Warning: `size’ must be less than or equal to the length of the smallest sequence passed. It is unspecified what happens when that condition is not met.

[function] pmap-into result-sequence function &rest sequences

Parallel version of `map-into’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmap-reduce map-function reduce-function sequence &rest args &key start end initial-value parts recurse

Equivalent to (preduce reduce-function sequence :key map-function …).

[function] pmapc function &rest lists

Parallel version of `mapc’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmapcan function &rest lists

Parallel version of `mapcan’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmapcar function &rest sequences

Parallel version of `mapcar’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

Unlike `mapcar’, `pmapcar’ also accepts vectors.

[function] pmapcon function &rest lists

Parallel version of `mapcon’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmapl function &rest lists

Parallel version of `mapl’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmaplist function &rest lists

Parallel version of `maplist’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pmaplist-into result-list function &rest lists

Like `pmaplist’ but results are stored in `result-list’. Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pnotany predicate &rest sequences

Parallel version of `notany’. Calls to `predicate’ are done in parallel, though not necessarily at the same time. Behavior is otherwise indistinguishable from `notany’.

Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] pnotevery predicate &rest sequences

Parallel version of `notevery’. Calls to `predicate’ are done in parallel, though not necessarily at the same time. Behavior is otherwise indistinguishable from `notevery’.

Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[macro] por &rest forms

Parallel version of `or’. Forms in `forms’ may be executed in parallel, though not necessarily at the same time. Any form which evaluates to non-nil may be returned.

[function] preduce function sequence &rest args &key key from-end start end initial-value parts recurse

Parallel version of `reduce’.

`preduce’ subdivides the input sequence into `parts’ number of parts and, in parallel, calls `reduce’ on each part. The partial results are then reduced again, either by `reduce’ (the default) or, if `recurse’ is non-nil, by `preduce’.

`parts’ defaults to (kernel-worker-count).

`key’ is thrown out while reducing the partial results. It applies to the first pass only.

`start’ and `end’ have the same meaning as in `reduce’.

`from-end’ means “from the end of each part”.

`initial-value’ means “initial value of each part”.

[function] preduce-partial function sequence &rest args &key key from-end start end initial-value parts

Like `preduce’ but only does a single reducing pass.

The length of `sequence’ must not be zero.

Returns the partial results as a vector.

[function] premove item sequence &rest args &key test test-not from-end start end key parts

Parallel version of `remove’. Note the `count’ option is not supported.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] premove-if test sequence &rest args &key from-end start end key parts

Parallel version of `remove-if’. Note the `count’ option is not supported.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] premove-if-not test sequence &rest args &key from-end start end key parts

Parallel version of `remove-if-not’. Note the `count’ option is not supported.

The `parts’ option divides `sequence’ into `parts’ number of parts. Default is (kernel-worker-count).

[function] psome predicate &rest sequences

Parallel version of `some’. Calls to `predicate’ are done in parallel, though not necessarily at the same time. Behavior is otherwise indistinguishable from `some’ except that any non-nil predicate comparison result may be returned.

Keyword arguments `parts’ and `size’ are also accepted (see `pmap’).

[function] psort sequence predicate &key key granularity &allow-other-keys

Parallel version of `sort’.

If `granularity’ is provided then parallel tasks are created only for segments larger than `granularity’. This may or may not result in better performance.

At present `psort’ is only parallelized for vectors; other types are given to `cl:sort’.

[macro] slet bindings &body body

`slet’ (serial let) is the non-parallel counterpart to `plet’.

The syntax of `slet’ matches that of `plet’, which includes the ability to bind multiple values.

Ptrees

See Ptrees for an introduction.

The following symbols are exported by the lparallel.ptree package as well as by the convenience package lparallel .


[special variable] *ptree-node-kernel*

When non-nil, `*kernel*’ is bound to this value during the call of a node function.

[function] call-ptree id ptree

Return the computation result of the node with identifier `id’ in `ptree’.

If the node is uncomputed, compute the result.

If the node is already computed, return the computed result.

[function] check-ptree ptree

Verify that all nodes have been defined with an associated function. If not, `ptree-undefined-function-error’ is signaled.

[function] clear-ptree ptree

Clear all node results in `ptree’, restoring the tree to its uncomputed state.

[function] clear-ptree-errors ptree

Clear all error results in `ptree’, allowing the computation to resume from its latest pre-error state.

[function] make-ptree

Create a ptree instance.

[macro] ptree defs &body body

Create a ptree using `flet’ syntax.

ptree ((node-name child-names function-body)*) form*

Each `node-name’ form corresponds to the definition of a ptree node.

`node-name’ is the name of the node being defined (a symbol).

`child-names’ is a list of the names of child nodes (symbols).

The function associated with the node being defined is

`(lambda ,child-names ,@function-body)

`child-names’ cannot contain lambda list keywords.

For each `node-name’, a symbol macro is defined which expands to a `call-ptree’ form for that node.

[function] ptree-computed-p id ptree

Return true if the node with identifier `id’ in `ptree’ has finished computing, otherwise return false.

[function] ptree-fn id args function ptree

Define a ptree node with identifier `id’, which is some unique object suitable for `eql’ comparison such as symbol.

The ids of its child nodes are elements of the list `args’.

`function’ is the function associated with this node. The arguments passed to `function’ are the respective results of the child node computations.

`ptree’ is the ptree instance in which the node is being defined.

defpun

See defpun for an introduction.

The following symbols are exported by the lparallel.defpun package as well as by the convenience package lparallel .


[macro] declaim-defpun &rest names

See `defpun’.

[macro] defpun name params &body body

`defpun’ defines a function which is specially geared for fine-grained parallelism. If you have many small tasks which bog down the system, `defpun’ may help.

The syntax of `defpun’ matches that of `defun’. The difference is that `plet’ and `plet-if’ take on new meaning inside `defpun’. The symbols in the binding positions of `plet’ and `plet-if’ should be viewed as lazily evaluated immutable references.

Inside a `defpun’ form the name of the function being defined is a macrolet, as are the names of other functions which were defined by `defpun’. Thus using #’ on them is an error. Calls to functions defined by `defpun’ entail more overhead when the caller lies outside a `defpun’ form.

A `defpun’ function must exist before it is referenced inside another `defpun’ function. If this is not possible–for example if func1 and func2 reference each other–then use `declaim-defpun’ to specify intent:

(declaim-defpun func1 func2)

[macro] defpun/type name params arg-types return-type &body body

Typed version of `defpun’.

`arg-types’ is an unevaluated list of argument types.

`return-type’ is an unevaluated form of the return type, possibly indicating multiple values as in (values fixnum float).

(As a technical point, if `return-type’ contains no lambda list keywords then the return type given to ftype will be additionally constrained to match the number of return values specified.)

[macro] plet bindings &body body

The syntax of `plet’ matches that of `let’.

plet ({var-no-init | (var [init-form])}*) declaration* form*

For each (var init-form) pair, a future is created which executes `init-form’. Inside `body’, `var’ is a symbol macro which expands to a `force’ form for the corresponding future.

Each `var-no-init’ is bound to nil and each `var’ without `init-form’ is bound to nil (no future is created).

Type declarations for vars are recognized by `plet’ and incorporated into the final expansion. The semantics of these declarations are the same as those of a regular `let’ form.

`plet’ is subject to optimization inside `defpun’.

[macro] plet-if predicate bindings &body body

The syntax of `plet-if’ matches that of `let’ except for the addition of the `predicate’ form.

If `predicate’ evaluates to true, the behavior is the same as `plet’.

If `predicate’ evaluates to false, the behavior is the same as `let’.

`plet-if’ is subject to optimization inside `defpun’.

Queues

The following symbols are exported by the lparallel.queue package. They are not included in the lparallel package.


[function] make-queue &key fixed-capacity initial-contents

Create a queue.

The queue contents may be initialized with the keyword argument `initial-contents’.

By default there is no limit on the queue capacity. Passing a `fixed-capacity’ keyword argument limits the capacity to the value passed. `push-queue’ will block for a full fixed-capacity queue.

[function] peek-queue queue

If `queue’ is non-empty, return (values element t) where `element’ is the frontmost element of `queue’.

If `queue’ is empty, return (values nil nil).

[function] pop-queue queue

Remove the frontmost element from `queue’ and return it.

If `queue’ is empty, block until an element is available.

[function] push-queue object queue

Push `object’ onto the back of `queue’.

[function] queue-count queue

Return the number of elements in `queue’.

[function] queue-empty-p queue

Return true if `queue’ is empty, otherwise return false.

[function] queue-full-p queue

Return true if `queue’ is full, otherwise return false.

[function] try-pop-queue queue &key timeout

If `queue’ is non-empty, remove the frontmost element from `queue’ and return (values element t) where `element’ is the element removed.

If `queue’ is empty and `timeout’ is given, then wait up to `timeout’ seconds for the queue to become non-empty.

If `queue’ is empty and the timeout has expired, or if `queue’ is empty and no `timeout’ was given, return (values nil nil).

Providing a nil or non-positive value of `timeout’ is equivalent to providing no timeout.

[macro] with-locked-queue queue &body body

Execute `body’ with the queue lock held. Use the `/no-lock’ functions inside `body’.