Skip to content

Commit e893629

Browse files
committed
Remove the Op API
1 parent 1a0970f commit e893629

File tree

9 files changed

+20
-617
lines changed

9 files changed

+20
-617
lines changed

README.md

Lines changed: 11 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,17 @@ val x : int Loc.t = <abstr>
120120
One can then manipulate the locations individually:
121121

122122
```ocaml
123-
# Loc.set a 6
123+
# Loc.set a 10
124124
- : unit = ()
125125
126126
# Loc.get a
127-
- : int = 6
128-
```
129-
130-
Attempt primitive operations over multiple locations:
127+
- : int = 10
131128
132-
```ocaml
133-
# Op.atomically [
134-
Op.make_cas a 6 10;
135-
Op.make_cas b 0 52
136-
]
129+
# Loc.compare_and_set b 0 52
137130
- : bool = true
131+
132+
# Loc.get b
133+
- : int = 52
138134
```
139135

140136
Block waiting for changes to locations:
@@ -170,16 +166,12 @@ And now we have it:
170166
The API of **Kcas** is divided into submodules. The main modules are
171167

172168
- [`Loc`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Loc/index.html),
173-
providing an abstraction of _shared memory locations_,
169+
providing an abstraction of _shared memory locations_, and
174170

175171
- [`Xt`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Xt/index.html),
176-
providing _explicit transaction log passing_ over shared memory locations, and
177-
178-
- [`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html),
179-
providing an interface for _primitive operations_ over multiple shared memory
180-
locations.
172+
providing _explicit transaction log passing_ over shared memory locations.
181173

182-
The following sections discuss each of the above in turn.
174+
The following sections discuss both of the above in turn.
183175

184176
### Creating and manipulating individual shared memory locations
185177

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

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

1052-
### Programming with primitive operations
1053-
1054-
In addition to the transactional interface, **Kcas** also provides the
1055-
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html)
1056-
interface for performing a list of primitive operations. To program with
1057-
primitive operations one simply makes a list of CAS operations using
1058-
[`make_cas`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-make_cas)
1059-
and then attempts them using
1060-
[`atomically`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html#val-atomically).
1061-
Typically that needs to be done inside a loop of some kind as such an attempt
1062-
can naturally fail.
1063-
1064-
Let's first
1065-
[`make`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Loc/index.html#val-make)
1066-
two locations representing stacks:
1067-
1068-
```ocaml
1069-
# let stack_a = Loc.make [19]
1070-
and stack_b = Loc.make [76]
1071-
val stack_a : int list Loc.t = <abstr>
1072-
val stack_b : int list Loc.t = <abstr>
1073-
```
1074-
1075-
Here is a function that can atomically move an element from given `source` stack
1076-
to the given `target` stack:
1077-
1078-
```ocaml
1079-
# let rec move ?(backoff = Backoff.default)
1080-
source
1081-
target =
1082-
match Loc.get source with
1083-
| [] -> raise Exit
1084-
| (elem::rest) as old_source ->
1085-
let old_target = Loc.get target in
1086-
let ops = [
1087-
Op.make_cas source old_source rest;
1088-
Op.make_cas target old_target (elem::old_target)
1089-
] in
1090-
if not (Op.atomically ops) then
1091-
let backoff = Backoff.once backoff in
1092-
move ~backoff source target
1093-
val move : ?backoff:Backoff.t -> 'a list Loc.t -> 'a list Loc.t -> unit =
1094-
<fun>
1095-
```
1096-
1097-
Note that we also used the
1098-
[`Backoff`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Backoff/index.html)
1099-
module provided by **Kcas** above.
1100-
1101-
Now we can simply call `move`:
1102-
1103-
```ocaml
1104-
# move stack_a stack_b
1105-
- : unit = ()
1106-
1107-
# Loc.get stack_a
1108-
- : int list = []
1109-
1110-
# Loc.get stack_b
1111-
- : int list = [19; 76]
1112-
```
1113-
1114-
As one can see, the API provided by
1115-
[`Op`](https://ocaml-multicore.github.io/kcas/doc/kcas/Kcas/Op/index.html) is
1116-
quite low-level and is not intended for application level programming.
1117-
11181040
## Designing lock-free algorithms with k-CAS
11191041

11201042
The key benefit of k-CAS, or k-CAS-n-CMP, and transactions in particular, is

bench/bench_op.ml

Lines changed: 0 additions & 50 deletions
This file was deleted.

bench/main.ml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
let benchmarks =
22
[
33
("Kcas Loc", Bench_loc.run_suite);
4-
("Kcas Op", Bench_op.run_suite);
54
("Kcas Xt", Bench_xt.run_suite);
65
("Kcas parallel CMP", Bench_parallel_cmp.run_suite);
76
("Kcas_data Hashtbl", Bench_hashtbl.run_suite);

src/kcas/kcas.ml

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ let[@inline] determine_for_owner which root =
373373
fenceless_get (root_as_atomic which) == R After
374374

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

379378
let[@inline] make_node loc state lt gt =
@@ -640,68 +639,6 @@ module Loc = struct
640639
let fenceless_get loc = eval (fenceless_get (as_atomic loc))
641640
end
642641

643-
let[@inline] insert cas loc state =
644-
let x = loc.id in
645-
match cas with
646-
| Node { loc = a; lt = T Leaf; _ } when x < a.id ->
647-
Node { loc; state; lt = T Leaf; gt = T cas; awaiters = [] }
648-
| Node { loc = a; gt = T Leaf; _ } when a.id < x ->
649-
Node { loc; state; lt = T cas; gt = T Leaf; awaiters = [] }
650-
| _ -> begin
651-
match splay ~hit_parent:false x (T cas) with
652-
| _, T (Node _), _ -> overlap ()
653-
| lt, T Leaf, gt -> Node { loc; state; lt; gt; awaiters = [] }
654-
end
655-
656-
module Op = struct
657-
type t = CAS : 'a Loc.t * 'a * 'a -> t
658-
659-
let[@inline] make_cas loc before after = CAS (loc, before, after)
660-
let[@inline] make_cmp loc expected = CAS (loc, expected, expected)
661-
662-
let[@inline] is_on_loc op loc =
663-
match op with CAS (loc', _, _) -> Obj.magic loc' == loc
664-
665-
let[@inline] get_id = function CAS (loc, _, _) -> loc.id
666-
667-
let atomic = function
668-
| CAS (loc, before, after) ->
669-
if before == after then Loc.get loc == before
670-
else Loc.compare_and_set loc before after
671-
672-
let atomically ?(mode = Mode.lock_free) = function
673-
| [] -> true
674-
| [ op ] -> atomic op
675-
| first :: rest ->
676-
let which = Undetermined { root = R mode } in
677-
let rec run cas = function
678-
| [] -> determine_for_owner which cas
679-
| CAS (loc, before, after) :: rest ->
680-
if before == after && is_obstruction_free which loc then
681-
(* Fenceless is safe as there are fences in [determine]. *)
682-
let state = fenceless_get (as_atomic loc) in
683-
before == eval state && run (insert cas loc state) rest
684-
else
685-
run
686-
(insert cas loc
687-
{ before; after; which = W which; awaiters = [] })
688-
rest
689-
in
690-
let (CAS (loc, before, after)) = first in
691-
if before == after && is_obstruction_free which loc then
692-
(* Fenceless is safe as there are fences in [determine]. *)
693-
let state = fenceless_get (as_atomic loc) in
694-
before == eval state
695-
&& run
696-
(Node { loc; state; lt = T Leaf; gt = T Leaf; awaiters = [] })
697-
rest
698-
else
699-
let state = { before; after; which = W which; awaiters = [] } in
700-
run
701-
(Node { loc; state; lt = T Leaf; gt = T Leaf; awaiters = [] })
702-
rest
703-
end
704-
705642
module Xt = struct
706643
(* NOTE: You can adjust comment blocks below to select whether or not to use
707644
an unsafe cast to avoid a level of indirection due to [Atomic.t]. *)

0 commit comments

Comments
 (0)