Skip to content

Commit

Permalink
Remove the Op API
Browse files Browse the repository at this point in the history
  • Loading branch information
polytypic committed Sep 15, 2023
1 parent 3af1f71 commit bb5ec1a
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 448 deletions.
100 changes: 11 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,17 @@ val x : int Loc.t = <abstr>
One can then manipulate the locations individually:

```ocaml
# Loc.set a 6
# Loc.set a 10
- : unit = ()
# Loc.get a
- : int = 6
```

Attempt primitive operations over multiple locations:
- : int = 10
```ocaml
# Op.atomically [
Op.make_cas a 6 10;
Op.make_cas b 0 52
]
# Loc.compare_and_set b 0 52
- : bool = true
# Loc.get b
- : int = 52
```

Block waiting for changes to locations:
Expand Down Expand Up @@ -170,16 +166,12 @@ And now we have it:
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_,
providing an abstraction of _shared memory locations_, and

- [`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.
providing _explicit transaction log passing_ over shared memory locations.

The following sections discuss each of the above in turn.
The following sections discuss both of the above in turn.

### Creating and manipulating individual shared memory locations

Expand All @@ -192,9 +184,7 @@ In other words, an application that uses
[`Atomic`](https://v2.ocaml.org/api/Atomic.html), but then needs to perform
atomic operations over multiple atomic locations, could theoretically just
rebind `module Atomic = Loc` and then use the
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html),
and/or
[`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html) APIs
[`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html) API
to perform operations over multiple locations. This should not be done
just-in-case, however, as, even though **Kcas** is efficient, it does naturally
have higher overhead than the Stdlib
Expand Down Expand Up @@ -449,10 +439,8 @@ val a_queue : int queue = {front = <abstr>; back = <abstr>}
#### Composing transactions

The main benefit of the
The main feature of the
[`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html) API
over the
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html) API
is that transactions are composable. In fact, we already wrote transactions that
recorded multiple primitive shared memory accesses to the explicitly passed
transaction log. Nothing prevents us from writing transactions calling other
Expand Down Expand Up @@ -1049,72 +1037,6 @@ val a_cache : (int, string) cache =
As an exercise, implement an operation to `remove` associations from a cache and
an operation to change the capacity of the cache.

### Programming with primitive operations

In addition to the transactional interface, **Kcas** also provides the
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html)
interface for performing a list of primitive operations. 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
44 changes: 0 additions & 44 deletions bench/bench_op.ml

This file was deleted.

1 change: 0 additions & 1 deletion bench/main.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
let benchmarks =
[
("Kcas Op", Bench_op.run_suite);
("Kcas Xt", Bench_xt.run_suite);
("Kcas parallel CMP", Bench_parallel_cmp.run_suite);
("Kcas_data Hashtbl", Bench_hashtbl.run_suite);
Expand Down
61 changes: 3 additions & 58 deletions src/kcas/kcas.ml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,6 @@ let[@inline] determine_for_owner casn cass =
fenceless_get casn == After

let[@inline never] impossible () = failwith "impossible"
let[@inline never] overlap () = failwith "kcas: location overlap"
let[@inline never] invalid_retry () = failwith "kcas: invalid use of retry"

type splay = Miss : splay | Hit : 'a loc * 'a state -> splay
Expand Down Expand Up @@ -615,63 +614,6 @@ module Loc = struct
let fenceless_get loc = eval (fenceless_get (as_atomic loc))
end

let[@inline] insert cass loc state =
let x = loc.id in
match cass with
| CASN { loc = a; lt = NIL; _ } when x < a.id ->
CASN { loc; state; lt = NIL; gt = cass; awaiters = [] }
| CASN { loc = a; gt = NIL; _ } when a.id < x ->
CASN { loc; state; lt = cass; gt = NIL; awaiters = [] }
| _ -> begin
match splay ~hit_parent:false x cass with
| _, Hit _, _ -> overlap ()
| lt, Miss, gt -> CASN { loc; state; lt; gt; awaiters = [] }
end

module Op = struct
type t = CAS : 'a Loc.t * 'a * 'a -> t

let[@inline] make_cas loc before after = CAS (loc, before, after)
let[@inline] make_cmp loc expected = CAS (loc, expected, expected)

let[@inline] is_on_loc op loc =
match op with CAS (loc', _, _) -> Obj.magic loc' == loc

let[@inline] get_id = function CAS (loc, _, _) -> loc.id

let atomic = function
| CAS (loc, before, after) ->
if before == after then Loc.get loc == before
else Loc.compare_and_set loc before after

let atomically ?(mode = Mode.lock_free) = function
| [] -> true
| [ op ] -> atomic op
| first :: rest ->
let casn = Atomic.make (mode :> status) in
let rec run cass = function
| [] -> determine_for_owner casn cass
| CAS (loc, before, after) :: rest ->
if before == after && is_obstruction_free casn loc then
(* Fenceless is safe as there are fences in [determine]. *)
let state = fenceless_get (as_atomic loc) in
before == eval state && run (insert cass loc state) rest
else
run
(insert cass loc { before; after; casn; awaiters = [] })
rest
in
let (CAS (loc, before, after)) = first in
if before == after && is_obstruction_free casn loc then
(* Fenceless is safe as there are fences in [determine]. *)
let state = fenceless_get (as_atomic loc) in
before == eval state
&& run (CASN { loc; state; lt = NIL; gt = NIL; awaiters = [] }) rest
else
let state = { before; after; casn; awaiters = [] } in
run (CASN { loc; state; lt = NIL; gt = NIL; awaiters = [] }) rest
end

module Xt = struct
(* NOTE: You can adjust comment blocks below to select whether or not to use
an unsafe cast to avoid a level of indirection due to [Atomic.t]. *)
Expand Down Expand Up @@ -791,6 +733,9 @@ module Xt = struct
unsafe_update ~xt loc (fun actual ->
if actual == before then after else actual)

let compare_and_set ~xt loc before after =
compare_and_swap ~xt loc before after == before

let exchange ~xt loc after = unsafe_update ~xt loc (fun _ -> after)
let fetch_and_add ~xt loc n = unsafe_update ~xt loc (( + ) n)
let incr ~xt loc = unsafe_update ~xt loc inc |> ignore
Expand Down
Loading

0 comments on commit bb5ec1a

Please sign in to comment.