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: release, repository
- bordeaux-threads: release, repository
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’.