Skip to content

Commit feb8d11

Browse files
SGrondintalex5
authored andcommitted
Add README documentation for Eio.Executor_pool
1 parent 82dcca7 commit feb8d11

File tree

2 files changed

+115
-12
lines changed

2 files changed

+115
-12
lines changed

README.md

+83-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Eio replaces existing concurrency libraries such as Lwt
3535
* [Running processes](#running-processes)
3636
* [Time](#time)
3737
* [Multicore Support](#multicore-support)
38+
* [Domain Manager](#domain-manager)
39+
* [Executor Pool](#executor-pool)
3840
* [Synchronisation Tools](#synchronisation-tools)
3941
* [Promises](#promises)
4042
* [Example: Concurrent Cache](#example-concurrent-cache)
@@ -936,7 +938,12 @@ The mock backend provides a mock clock that advances automatically where there i
936938

937939
OCaml allows a program to create multiple *domains* in which to run code, allowing multiple CPUs to be used at once.
938940
Fibers are scheduled cooperatively within a single domain, but fibers in different domains run in parallel.
939-
This is useful to perform CPU-intensive operations quickly.
941+
This is useful to perform CPU-intensive operations quickly
942+
(though extra care needs to be taken when using multiple cores; see the [Multicore Guide](./doc/multicore.md) for details).
943+
944+
### Domain Manager
945+
946+
[Eio.Domain_manager][] provides a basic API for spawning domains.
940947
For example, let's say we have a CPU intensive task:
941948

942949
```ocaml
@@ -950,7 +957,7 @@ let sum_to n =
950957
!total
951958
```
952959

953-
We can use [Eio.Domain_manager][] to run this in a separate domain:
960+
We can use the domain manager to run this in a separate domain:
954961

955962
```ocaml
956963
let main ~domain_mgr =
@@ -992,8 +999,78 @@ Notes:
992999
- `Domain_manager.run` waits for the domain to finish, but it allows other fibers to run while waiting.
9931000
This is why we use `Fiber.both` to create multiple fibers.
9941001

995-
For more information, see the [Multicore Guide](./doc/multicore.md).
1002+
### Executor Pool
1003+
1004+
An [Eio.Executor_pool][] distributes jobs among a pool of domain workers.
1005+
Domains are reused and can execute multiple jobs concurrently.
1006+
1007+
Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
1008+
The `~weight` represents the expected proportion of a CPU core that the job will take up.
1009+
Jobs are queued up if they cannot be started immediately due to all domain workers being busy (`>= 1.0`).
1010+
1011+
This is the recommended way of leveraging OCaml 5's multicore capabilities.
1012+
1013+
Usually you will only want one pool for an entire application, so the pool is typically created when the application starts:
1014+
1015+
<!-- $MDX skip -->
1016+
```ocaml
1017+
let () =
1018+
Eio_main.run @@ fun env ->
1019+
Switch.run @@ fun sw ->
1020+
let pool =
1021+
Eio.Executor_pool.create
1022+
~sw (Eio.Stdenv.domain_mgr env)
1023+
~domain_count:4
1024+
in
1025+
main ~pool
1026+
```
1027+
1028+
The pool starts its domain workers immediately upon creation.
1029+
1030+
The pool will not block our switch `sw` from completing;
1031+
when the switch finishes, all domain workers and running jobs are cancelled.
1032+
1033+
`~domain_count` is the number of domain workers to create.
1034+
The total number of domains should not exceed `Domain.recommended_domain_count` or the number of cores on your system.
1035+
1036+
We can run the previous example using an Executor Pool like this:
1037+
1038+
```ocaml
1039+
let main ~domain_mgr =
1040+
Switch.run @@ fun sw ->
1041+
let pool =
1042+
Eio.Executor_pool.create ~sw domain_mgr ~domain_count:4
1043+
in
1044+
let test n =
1045+
traceln "sum 1..%d = %d" n
1046+
(Eio.Executor_pool.submit_exn pool ~weight:1.0
1047+
(fun () -> sum_to n))
1048+
in
1049+
Fiber.both
1050+
(fun () -> test 100000)
1051+
(fun () -> test 50000)
1052+
```
1053+
1054+
<!-- $MDX non-deterministic=output -->
1055+
```ocaml
1056+
# Eio_main.run @@ fun env ->
1057+
main ~domain_mgr:(Eio.Stdenv.domain_mgr env);;
1058+
+Starting CPU-intensive task...
1059+
+Starting CPU-intensive task...
1060+
+Finished
1061+
+sum 1..50000 = 1250025000
1062+
+Finished
1063+
+sum 1..100000 = 5000050000
1064+
- : unit = ()
1065+
```
1066+
`~weight` is the anticipated proportion of a CPU core used by the job.
1067+
In other words, the fraction of time actively spent executing OCaml code, not just waiting for I/O or system calls.
1068+
In the above code snippet we use `~weight:1.0` because the job is entirely CPU-bound: it never waits for I/O or other syscalls.
1069+
`~weight` must be `>= 0.0` and `<= 1.0`.
1070+
Example: given an IO-bound job that averages 2% of one CPU core, pass `~weight:0.02`.
9961071

1072+
Each domain worker starts new jobs until the total `~weight` of its running jobs reaches `1.0`.
1073+
9971074
## Synchronisation Tools
9981075

9991076
Eio provides several sub-modules for communicating between fibers,
@@ -1245,6 +1322,8 @@ The `Fiber.check ()` checks whether the worker itself has been cancelled, and ex
12451322
It's not actually necessary in this case,
12461323
because if we continue instead then the following `Stream.take` will perform the check anyway.
12471324

1325+
Note: in a real system, you would probably use [Eio.Executor_pool][] for this rather than making your own pool.
1326+
12481327
### Mutexes and Semaphores
12491328

12501329
Eio also provides `Mutex` and `Semaphore` sub-modules.
@@ -1809,6 +1888,7 @@ Some background about the effects system can be found in:
18091888
[Eio.Path]: https://ocaml-multicore.github.io/eio/eio/Eio/Path/index.html
18101889
[Eio.Time]: https://ocaml-multicore.github.io/eio/eio/Eio/Time/index.html
18111890
[Eio.Domain_manager]: https://ocaml-multicore.github.io/eio/eio/Eio/Domain_manager/index.html
1891+
[Eio.Executor_pool]: https://ocaml-multicore.github.io/eio/eio/Eio/Executor_pool/index.html
18121892
[Eio.Promise]: https://ocaml-multicore.github.io/eio/eio/Eio/Promise/index.html
18131893
[Eio.Stream]: https://ocaml-multicore.github.io/eio/eio/Eio/Stream/index.html
18141894
[Eio_posix]: https://ocaml-multicore.github.io/eio/eio_posix/Eio_posix/index.html

doc/multicore.md

+32-9
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44

55
* [Introduction](#introduction)
66
* [Problems with Multicore Programming](#problems-with-multicore-programming)
7-
* [Optimisation 1: Caching](#optimisation-1-caching)
8-
* [Optimisation 2: Out-of-Order Execution](#optimisation-2-out-of-order-execution)
9-
* [Optimisation 3: Compiler Optimisations](#optimisation-3-compiler-optimisations)
10-
* [Optimisation 4: Multiple Cores](#optimisation-4-multiple-cores)
7+
* [Optimisation 1: Caching](#optimisation-1-caching)
8+
* [Optimisation 2: Out-of-Order Execution](#optimisation-2-out-of-order-execution)
9+
* [Optimisation 3: Compiler Optimisations](#optimisation-3-compiler-optimisations)
10+
* [Optimisation 4: Multiple Cores](#optimisation-4-multiple-cores)
1111
* [The OCaml Memory Model](#the-ocaml-memory-model)
12-
* [Atomic Locations](#atomic-locations)
13-
* [Initialisation](#initialisation)
14-
* [Guidelines](#guidelines)
12+
* [Atomic Locations](#atomic-locations)
13+
* [Initialisation](#initialisation)
14+
* [Safety Guidelines](#safety-guidelines)
15+
* [Performance Guidelines](#performance-guidelines)
1516
* [Further Reading](#further-reading)
1617

1718
<!-- vim-markdown-toc -->
1819

1920
## Introduction
2021

21-
OCaml 5.00 adds support for using multiple CPU cores in a single OCaml process.
22+
OCaml 5.0 adds support for using multiple CPU cores in a single OCaml process.
2223
An OCaml process is made up of one or more *domains*, and
2324
the operating system can run each domain on a different core, so that they run in parallel.
2425
This can make programs run much faster, but also introduces new ways for programs to go wrong.
@@ -446,7 +447,7 @@ So it will always see a correct list:
446447
- : unit = ()
447448
```
448449

449-
## Guidelines
450+
## Safety Guidelines
450451

451452
It's important to understand the above to avoid writing incorrect code,
452453
but there are several general principles that avoid most problems:
@@ -502,6 +503,28 @@ Finally, note that OCaml remains type-safe even with multiple domains.
502503
For example, accessing a `Queue` in parallel from multiple domains may result in a corrupted queue,
503504
but it won't cause a segfault.
504505

506+
## Performance Guidelines
507+
508+
The following recommendations will help you extract as much performance as possible from your hardware:
509+
510+
- There's a certain overhead associated with placing execution onto another domain,
511+
but that overhead will be paid off quickly if your job takes at least a few milliseconds to complete.
512+
Jobs that complete under 2-5ms may not be worth running on a separate domain.
513+
- Similarly, jobs that are 100% I/O-bound may not be worth running on a separate domain.
514+
The small initial overhead is simply never recouped.
515+
- If your program never hits 100% CPU usage, it's unlikely that parallelizing it will improve performance.
516+
- Try to avoid reading or writing to memory that's modified by other domains after the start of your job.
517+
Ideally, your jobs shouldn't need to interact with other domains' "working data".
518+
Aim to make your jobs as independent as possible.
519+
If unavoidable, the [Saturn](https://github.com/ocaml-multicore/saturn) library offers a collection of efficient threadsafe data structures.
520+
- It's often easier to design code to be multithreading friendly from the start
521+
(by making longer, independent jobs) than by refactoring existing code.
522+
- There's a cost associated with creating a domain, so try to use the same domains for longer periods of time.
523+
`Eio.Executor_pool` takes care of this automatically.
524+
- Obviously, reuse the same executor pool whenever possible! Don't recreate it over and over.
525+
- Having a large number of domains active at the same time imposes additional overhead on
526+
both the OS scheduler and the OCaml runtime, even if those domains are idle.
527+
505528
## Further Reading
506529

507530
- [OCaml Memory Model][] describes the full details of the memory model.

0 commit comments

Comments
 (0)