lib/parallel provides a fork-join interface for parallelism in multi-core OxCaml.
To expose an opportunity for parallelism, user code calls a fork_join function, such as:
(** [fork_join2 t f g] runs [f] and [g] as parallel tasks and returns their results. If
either task raises, this operation will reraise the leftmost exception after both
tasks have completed or raised.
[f] and [g] are [shareable], so can capture both [shared] and [uncontended]
references. This allows the tasks to read (but not mutate) state from the environment.
[f] is also [forkable], so cannot capture capsule passwords. *)
val fork_join2
: t @ local
-> (t @ local -> 'a) @ forkable local once shareable
-> (t @ local -> 'b) @ once shareable
-> #('a * 'b)The two functions passed to fork_join2 will (potentially) be executed in parallel.
After both functions return, fork_join2 returns a tuple containing both results.
Fork-joins may be arbitrarily nested and do not require creating a thread or fiber
for each task.
The type Parallel.t represents an implementation of parallelism.
To receive a Parallel.t, a parallel computation must be submitted to a
separate scheduler library. Schedulers provide the following function:
(** [with_parallel ?max_workers f] creates a scheduler that uses up to [max_workers]
worker threads, spawns [f] into it, and blocks the current thread until [f] is done
executing. Returns the result of [f]. *)
val with_parallel
: ?max_workers:int (* Default: [Multicore.max_domains ()] *)
-> (Parallel_kernel.t @ local -> 'a) @ once
-> 'aCalling with_parallel provides your parallel computation with a local Parallel.t
that represents the ability to run parallel tasks on this scheduler.
The currently available schedulers are:
-
Parallel.Sequential, which runs all tasks on the domain that created it. -
Parallel_scheduler, which creates a pool of worker domains that pull tasks from per-domain work-stealing dequeues.
The work-stealing scheduler integrates with lib/concurrent to provide non-blocking
operations. More documentation coming soon.
lib/parallel makes use of modes to prohibit data races at compile time.
The primary restriction on user programs is that they must provide
portable functions to fork_join. In brief, portable functions do not
close over unprotected mutable state. Refer to the
data-race-freedom documentation
for further detail.
let rec fib parallel n =
match n with
| 0 | 1 -> 1
| n ->
let #(a, b) =
Parallel.fork_join2
parallel
(fun parallel -> fib parallel (n - 1))
(fun parallel -> fib parallel (n - 2))
in
a + b
;;
let fib_sequential n = printf "%d" (fib Parallel.sequential n)
let fib_parallel n =
Parallel_scheduler.with_parallel (fun parallel -> printf "%d" (fib parallel n))
;;