Skip to content

Commit

Permalink
De-emphasize the Op API in documentation
Browse files Browse the repository at this point in the history
The preferred API to use kcas in most cases is the `Xt` API.
  • Loading branch information
polytypic committed Apr 28, 2023
1 parent c936fdb commit 668b388
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 114 deletions.
142 changes: 71 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ is distributed under the [ISC license](LICENSE.md).
- [A quick tour](#a-quick-tour)
- [Introduction](#introduction)
- [Creating and manipulating individual shared memory locations](#creating-and-manipulating-individual-shared-memory-locations)
- [Programming with primitive operations](#programming-with-primitive-operations)
- [Programming with transactions](#programming-with-transactions)
- [A transactional lock-free stack](#a-transactional-lock-free-stack)
- [A transactional lock-free queue](#a-transactional-lock-free-queue)
- [Composing transactions](#composing-transactions)
- [Blocking transactions](#blocking-transactions)
- [A transactional lock-free leftist heap](#a-transactional-lock-free-leftist-heap)
- [A composable Michael-Scott style queue](#a-composable-michael-scott-style-queue)
- [Programming with primitive operations](#programming-with-primitive-operations)
- [Designing lock-free algorithms with k-CAS](#designing-lock-free-algorithms-with-k-cas)
- [Minimize accesses](#minimize-accesses)
- [Prefer compound accesses](#prefer-compound-accesses)
Expand Down Expand Up @@ -140,12 +140,12 @@ The API of **kcas** is divided into submodules. The main modules are
- [`Loc`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Loc/index.html),
providing an abstraction of _shared memory locations_,

- [`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html),
providing _explicit transaction log passing_ over shared memory locations, and

- [`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html),
providing an interface for _primitive operations_ over multiple shared memory
locations,

- [`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html),
providing _explicit transaction log passing_ over shared memory locations.
locations.

The following sections discuss each of the above in turn.

Expand All @@ -168,72 +168,6 @@ just-in-case, however, as, even though **kcas** is efficient, it does naturally
have higher overhead than the Stdlib
[`Atomic`](https://v2.ocaml.org/api/Atomic.html).

### Programming with primitive operations

The [`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html)
module is probably most suitable when using **kcas** as a means to design and
implement new lock-free algorithms.

To program with primitive operations one simply makes a list of CAS operations
using
[`make_cas`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-make_cas)
and then attempts them using
[`atomically`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-atomically).
Typically that needs to be done inside a loop of some kind as such an attempt
can naturally fail.

Let's first
[`make`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Loc/index.html#val-make)
two locations representing stacks:

```ocaml
# let stack_a = Loc.make [19]
and stack_b = Loc.make [76]
val stack_a : int list Loc.t = <abstr>
val stack_b : int list Loc.t = <abstr>
```

Here is a function that can atomically move an element from given `source` stack
to the given `target` stack:

```ocaml
# let rec move ?(backoff = Backoff.default)
source
target =
match Loc.get source with
| [] -> raise Exit
| (elem::rest) as old_source ->
let old_target = Loc.get target in
let ops = [
Op.make_cas source old_source rest;
Op.make_cas target old_target (elem::old_target)
] in
if not (Op.atomically ops) then
let backoff = Backoff.once backoff in
move ~backoff source target
val move : ?backoff:Backoff.t -> 'a list Loc.t -> 'a list Loc.t -> unit =
<fun>
```

Note that we also used the
[`Backoff`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Backoff/index.html)
module provided by **kcas** above.

Now we can simply call `move`:

```ocaml
# move stack_a stack_b
- : unit = ()
# Loc.get stack_a
- : int list = []
# Loc.get stack_b
- : int list = [19; 76]
```

As one can see, the API provided by
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html) is
quite low-level and is not intended for application level programming.

### Programming with transactions

The [`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html)
Expand Down Expand Up @@ -911,6 +845,72 @@ is possible to end up with both _A_ and _B_ non-empty. This kind of
that it has been given a name: _write skew_. As an exercise, write out the
sequence of atomic accesses that leads to that result.

### Programming with primitive operations

The [`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html)
module is probably most suitable when using **kcas** as a means to design and
implement new lock-free algorithms.

To program with primitive operations one simply makes a list of CAS operations
using
[`make_cas`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-make_cas)
and then attempts them using
[`atomically`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-atomically).
Typically that needs to be done inside a loop of some kind as such an attempt
can naturally fail.

Let's first
[`make`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Loc/index.html#val-make)
two locations representing stacks:

```ocaml
# let stack_a = Loc.make [19]
and stack_b = Loc.make [76]
val stack_a : int list Loc.t = <abstr>
val stack_b : int list Loc.t = <abstr>
```

Here is a function that can atomically move an element from given `source` stack
to the given `target` stack:

```ocaml
# let rec move ?(backoff = Backoff.default)
source
target =
match Loc.get source with
| [] -> raise Exit
| (elem::rest) as old_source ->
let old_target = Loc.get target in
let ops = [
Op.make_cas source old_source rest;
Op.make_cas target old_target (elem::old_target)
] in
if not (Op.atomically ops) then
let backoff = Backoff.once backoff in
move ~backoff source target
val move : ?backoff:Backoff.t -> 'a list Loc.t -> 'a list Loc.t -> unit =
<fun>
```

Note that we also used the
[`Backoff`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Backoff/index.html)
module provided by **kcas** above.

Now we can simply call `move`:

```ocaml
# move stack_a stack_b
- : unit = ()
# Loc.get stack_a
- : int list = []
# Loc.get stack_b
- : int list = [19; 76]
```

As one can see, the API provided by
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html) is
quite low-level and is not intended for application level programming.

## Designing lock-free algorithms with k-CAS

The key benefit of k-CAS, or k-CAS-n-CMP, and transactions in particular, is
Expand Down
93 changes: 50 additions & 43 deletions src/kcas.mli
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ end
Multiple shared memory locations can be manipulated atomically using either
- {!Op}, to specify a list of primitive operations to perform, or
- {!Xt}, to explicitly pass a transaction log to record accesses.
- {!Xt}, to explicitly pass a transaction log to record accesses, or
- {!Op}, to specify a list of primitive operations to perform.
Atomic operations over multiple shared memory locations are performed in two
or three phases:
Expand All @@ -138,47 +138,8 @@ end
Each phase may fail. In particular, in the first phase, as no changes to
shared memory have yet been attempted, it is safe, for example, to raise
exceptions to signal failure. Failure on the third phase raises
{!Mode.Interference}. *)

(** Operations on shared memory locations. *)
module Op : sig
type t
(** Type of operations on shared memory locations. *)

val make_cas : 'a Loc.t -> 'a -> 'a -> t
(** [make_cas r before after] is an operation that attempts to set the shared
memory location [r] to the [after] value and succeeds if the current
content of [r] is the [before] value. *)

val make_cmp : 'a Loc.t -> 'a -> t
(** [make_cmp r expected] is an operation that succeeds if the current value
of the shared memory location [r] is the [expected] value. *)

val get_id : t -> int
(** [get_id op] returns the unique id of the shared memory reference targeted
by the [op]eration. *)

val is_on_loc : t -> 'a Loc.t -> bool
(** [is_on_loc op r] determines whether the target of [op] is the shared
memory location [r]. *)

val atomic : t -> bool
(** [atomic op] attempts to perform the given operation atomically. Returns
[true] on success and [false] on failure. *)

val atomically : ?mode:Mode.t -> t list -> bool
(** [atomically ops] attempts to perform the given operations atomically. If
used in {!Mode.obstruction_free} may raise {!Mode.Interference}.
Otherwise returns [true] on success and [false] on failure. The default
for [atomically] is {!Mode.lock_free}.
The algorithm requires provided operations to follow a global total order.
To eliminate a class of bugs, the operations are sorted automatically. If
the operations are given in either ascending or descending order of the
targeted shared memory location ids, then sorting is done in linear time
[O(n)] and does not increase the time complexity of the algorithm.
Otherwise sorting may take linearithmic time [O(n*log(n))]. *)
end
{!Mode.Interference}, which is typically automatically handled by
{!Xt.commit}. *)

(** {2 Composable transactions on multiple locations}
Expand Down Expand Up @@ -315,3 +276,49 @@ module Xt : sig
switches to {!Mode.lock_free}. Note that [commit] never raises the
{!Mode.Interference} exception. *)
end

(** {2 Multi-word compare-and-set operations}
The {!Op} module provides a multi-word compare-and-set (MCAS) interface for
manipulating multiple locations atomically. This is a low-level interface
not intended for most users. *)

(** Multi-word compare-and-set operations on shared memory locations. *)
module Op : sig
type t
(** Type of operations on shared memory locations. *)

val make_cas : 'a Loc.t -> 'a -> 'a -> t
(** [make_cas r before after] is an operation that attempts to set the shared
memory location [r] to the [after] value and succeeds if the current
content of [r] is the [before] value. *)

val make_cmp : 'a Loc.t -> 'a -> t
(** [make_cmp r expected] is an operation that succeeds if the current value
of the shared memory location [r] is the [expected] value. *)

val get_id : t -> int
(** [get_id op] returns the unique id of the shared memory reference targeted
by the [op]eration. *)

val is_on_loc : t -> 'a Loc.t -> bool
(** [is_on_loc op r] determines whether the target of [op] is the shared
memory location [r]. *)

val atomic : t -> bool
(** [atomic op] attempts to perform the given operation atomically. Returns
[true] on success and [false] on failure. *)

val atomically : ?mode:Mode.t -> t list -> bool
(** [atomically ops] attempts to perform the given operations atomically. If
used in {!Mode.obstruction_free} may raise {!Mode.Interference}.
Otherwise returns [true] on success and [false] on failure. The default
for [atomically] is {!Mode.lock_free}.
The algorithm requires provided operations to follow a global total order.
To eliminate a class of bugs, the operations are sorted automatically. If
the operations are given in either ascending or descending order of the
targeted shared memory location ids, then sorting is done in linear time
[O(n)] and does not increase the time complexity of the algorithm.
Otherwise sorting may take linearithmic time [O(n*log(n))]. *)
end

0 comments on commit 668b388

Please sign in to comment.