154 lines
5.8 KiB
Markdown
154 lines
5.8 KiB
Markdown
# Taskpools
|
|
|
|
This implements a lightweight, energy-efficient, easily auditable multithreaded taskpools.
|
|
|
|
This taskpools will be used in a highly security-sensitive blockchain application
|
|
targeted at resource-restricted devices hence desirable properties are:
|
|
|
|
- Ease of auditing and maintenance.
|
|
- Formally verified synchronization primitives are highly-sought after.
|
|
- Otherwise primitives are implemented from papers or ported from proven codebases
|
|
that can serve as reference for auditors.
|
|
- Resource-efficient. Threads spindown to save power, low memory use.
|
|
- Decent performance and scalability. The CPU should spent its time processing user workloads
|
|
and not dealing with threadpool contention, latencies and overheads.
|
|
|
|
## Example usage
|
|
|
|
```Nim
|
|
# Demo of API using a very inefficient π approcimation algorithm.
|
|
|
|
import
|
|
std/[strutils, math, cpuinfo],
|
|
taskpools
|
|
|
|
# From https://github.com/nim-lang/Nim/blob/v1.6.2/tests/parallel/tpi.nim
|
|
# Leibniz Formula https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
|
|
proc term(k: int): float =
|
|
if k mod 2 == 1:
|
|
-4'f / float(2*k + 1)
|
|
else:
|
|
4'f / float(2*k + 1)
|
|
|
|
proc piApprox(tp: Taskpool, n: int): float =
|
|
var pendingFuts = newSeq[FlowVar[float]](n)
|
|
for k in 0 ..< pendingFuts.len:
|
|
pendingFuts[k] = tp.spawn term(k) # Schedule a task on the threadpool a return a handle to retrieve the result.
|
|
for k in 0 ..< pendingFuts.len:
|
|
result += sync pendingFuts[k] # Block until the result is available.
|
|
|
|
proc main() =
|
|
var n = 1_000_000
|
|
var nthreads = countProcessors()
|
|
|
|
var tp = Taskpool.new(num_threads = nthreads) # Default to the number of hardware threads.
|
|
|
|
echo formatFloat(tp.piApprox(n))
|
|
|
|
tp.syncAll() # Block until all pending tasks are processed (implied in tp.shutdown())
|
|
tp.shutdown()
|
|
|
|
# Compile with nim c -r -d:release --threads:on --outdir:build example.nim
|
|
main()
|
|
```
|
|
|
|
## API
|
|
|
|
The API follows the spec proposed here https://github.com/nim-lang/RFCs/issues/347#task-parallelism-api
|
|
|
|
The following types and procedures are exposed:
|
|
|
|
- Taskpool:
|
|
- ```Nim
|
|
type Taskpool* = ptr object
|
|
## A taskpool schedules procedures to be executed in parallel
|
|
```
|
|
- ```Nim
|
|
proc new(T: type Taskpool, numThreads = countProcessor()): T
|
|
## Initialize a threadpool that manages `numThreads` threads.
|
|
## Default to the number of logical processors available.
|
|
```
|
|
- ```Nim
|
|
proc syncAll*(pool: Taskpool) =
|
|
## Blocks until all pending tasks are completed.
|
|
##
|
|
## This MUST only be called from
|
|
## the root thread that created the taskpool
|
|
```
|
|
- ```Nim
|
|
proc shutdown*(tp: var TaskPool) =
|
|
## Wait until all tasks are completed and then shutdown the taskpool.
|
|
##
|
|
## This MUST only be called from
|
|
## the root scope that created the taskpool.
|
|
```
|
|
- ```Nim
|
|
macro spawn*(tp: TaskPool, fnCall: typed): untyped =
|
|
## Spawns the input function call asynchronously, potentially on another thread of execution.
|
|
##
|
|
## If the function calls returns a result, spawn will wrap it in a Flowvar.
|
|
## You can use `sync` to block the current thread and extract the asynchronous result from the flowvar.
|
|
## You can use `isReady` to check if result is available and if subsequent
|
|
## `spawn` returns immediately.
|
|
##
|
|
## Tasks are processed approximately in Last-In-First-Out (LIFO) order
|
|
```
|
|
In practice the signature is one of the following
|
|
```Nim
|
|
proc spawn*(tp: TaskPool, fnCall(args) -> T): Flowvar[T]
|
|
proc spawn*(tp: TaskPool, fnCall(args) -> void): void
|
|
```
|
|
- Flowvar, a handle on an asynchronous computation scheduled on the threadpool
|
|
- ```Nim
|
|
type Flowvar*[T] = object
|
|
## A Flowvar is a placeholder for a future result that may be computed in parallel
|
|
```
|
|
- ```Nim
|
|
func isSpawned*(fv: Flowvar): bool =
|
|
## Returns true if a flowvar is spawned
|
|
## This may be useful for recursive algorithms that
|
|
## may or may not spawn a flowvar depending on a condition.
|
|
## This is similar to Option or Maybe types
|
|
```
|
|
- ```Nim
|
|
func isReady*[T](fv: Flowvar[T]): bool =
|
|
## Returns true if the result of a Flowvar is ready.
|
|
## In that case `sync` will not block.
|
|
## Otherwise the current will block to help on all the pending tasks
|
|
## until the Flowvar is ready.
|
|
```
|
|
- ```Nim
|
|
proc sync*[T](fv: sink Flowvar[T]): T =
|
|
## Blocks the current thread until the flowvar is available
|
|
## and returned.
|
|
## The thread is not idle and will complete pending tasks.
|
|
```
|
|
|
|
### Non-goals
|
|
|
|
The following are non-goals:
|
|
|
|
- Supporting GC-ed types with Nim default GC (sequences and strings). Using no GC or --gc:arc, --gc:orc or --gc:boehm (any GC that doesn't have thread-local heaps).
|
|
- Having async-awaitable tasks
|
|
- Running on environments without dynamic memory allocation
|
|
- High-Performance Computing specificities (distribution on many machines or GPUs or machines with 200+ cores or multi-sockets)
|
|
|
|
### Comparison with Weave
|
|
|
|
Compared to [Weave](https://github.com/mratsim/weave), here are the tradeoffs:
|
|
- Taskpools only provide spawn/sync (task parallelism).\
|
|
There is no (extremely) optimized parallel for (data parallelism)\
|
|
or precise in/out dependencies (events / dataflow parallelism).
|
|
- Weave can handle trillions of small tasks that require only 10µs per task. (Load Balancing overhead)
|
|
- Weave maintains an adaptive memory pool to reduce memory allocation overhead,
|
|
Taskpools allocations are as-needed. (Scheduler overhead)
|
|
|
|
## License
|
|
|
|
Licensed and distributed under either of
|
|
|
|
* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
|
|
* Apache License, Version 2.0, ([LICENSE-APACHEv2](LICENSE-APACHEv2) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
at your option. This file may not be copied, modified, or distributed except according to those terms.
|