Skip to content

Commit 4ec6605

Browse files
committed
LRU-cache example
1 parent 3b855c6 commit 4ec6605

File tree

6 files changed

+178
-16
lines changed

6 files changed

+178
-16
lines changed

README.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -969,10 +969,11 @@ type ('k, 'v) cache = {
969969
To create a cache we just create the data structures:
970970

971971
```ocaml
972-
# let cache ?hashed_type capacity =
973-
{ space = Loc.make capacity;
974-
table = Hashtbl.create ?hashed_type ();
975-
order = Dllist.create () }
972+
# let cache ?hashed_type capacity = {
973+
space = Loc.make capacity;
974+
table = Hashtbl.create ?hashed_type ();
975+
order = Dllist.create ()
976+
}
976977
val cache : ?hashed_type:'a Hashtbl.hashed_type -> int -> ('a, 'b) cache =
977978
<fun>
978979
```
@@ -997,30 +998,30 @@ val get_opt : xt:'a Xt.t -> ('b, 'c) cache -> 'b -> 'c option = <fun>
997998
that, as explained previously, moves the node corresponding to the accessed
998999
association to the left end of the list.
9991000

1000-
To introduce associations we provide the `set` operation
1001+
To introduce associations we provide the `set_blocking` operation
10011002

10021003
```ocaml
1003-
# let set ~xt {table; order; space; _} key value =
1004+
# let set_blocking ~xt {table; order; space; _} key value =
10041005
let node =
10051006
match Hashtbl.Xt.find_opt ~xt table key with
10061007
| None ->
10071008
if 0 = Xt.update ~xt space (fun n -> Int.max 0 (n-1)) then
1008-
Dllist.Xt.take_opt_r ~xt order
1009-
|> Option.iter (Hashtbl.Xt.remove ~xt table);
1009+
Dllist.Xt.take_blocking_r ~xt order
1010+
|> Hashtbl.Xt.remove ~xt table;
10101011
Dllist.Xt.add_l ~xt key order
10111012
| Some (node, _) ->
10121013
Dllist.Xt.move_l ~xt node order;
10131014
node
10141015
in
10151016
Hashtbl.Xt.replace ~xt table key (node, value)
1016-
val set : xt:'a Xt.t -> ('b, 'c) cache -> 'b -> 'c -> unit = <fun>
1017+
val set_blocking : xt:'a Xt.t -> ('b, 'c) cache -> 'b -> 'c -> unit = <fun>
10171018
```
10181019

10191020
that, like `get_opt`, either moves or adds the node of the accessed association
10201021
to the left end of the list. In case a new association is added, the space is
1021-
decremented. If there was no space, an association is first removed. As
1022-
described previously, the association to remove is determined by removing the
1023-
rightmost element from the list.
1022+
decremented. If there was no space, an association is first removed, which will
1023+
block in case capacity is 0. As described previously, the association to remove
1024+
is determined by removing the rightmost element from the list.
10241025

10251026
We can then test that the cache works as expected:
10261027

@@ -1029,16 +1030,16 @@ We can then test that the cache works as expected:
10291030
val a_cache : (int, string) cache =
10301031
{space = <abstr>; table = <abstr>; order = <abstr>}
10311032
1032-
# Xt.commit { tx = set a_cache 101 "basics" }
1033+
# Xt.commit { tx = set_blocking a_cache 101 "basics" }
10331034
- : unit = ()
10341035
1035-
# Xt.commit { tx = set a_cache 42 "answer" }
1036+
# Xt.commit { tx = set_blocking a_cache 42 "answer" }
10361037
- : unit = ()
10371038
10381039
# Xt.commit { tx = get_opt a_cache 101 }
10391040
- : string option = Some "basics"
10401041
1041-
# Xt.commit { tx = set a_cache 2023 "year" }
1042+
# Xt.commit { tx = set_blocking a_cache 2023 "year" }
10421043
- : unit = ()
10431044
10441045
# Xt.commit { tx = get_opt a_cache 42 }

test/kcas_data/dune

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
(tests
2-
(names dllist_test hashtbl_test mvar_test queue_test stack_test xt_test)
2+
(names
3+
dllist_test
4+
hashtbl_test
5+
lru_cache_example
6+
mvar_test
7+
queue_test
8+
stack_test
9+
xt_test)
310
(libraries alcotest kcas kcas_data domain_shims)
411
(package kcas_data))

test/kcas_data/lru_cache.ml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
open Kcas
2+
open Kcas_data
3+
4+
type ('k, 'v) t = {
5+
space : int Loc.t;
6+
table : ('k, 'k Dllist.node * 'v) Hashtbl.t;
7+
order : 'k Dllist.t;
8+
}
9+
10+
let check_capacity capacity =
11+
if capacity < 0 then invalid_arg "Lru_cache: capacity must be non-negative"
12+
13+
let create ?hashed_type capacity =
14+
check_capacity capacity;
15+
{
16+
space = Loc.make capacity;
17+
table = Hashtbl.create ?hashed_type ();
18+
order = Dllist.create ();
19+
}
20+
21+
module Xt = struct
22+
let capacity_of ~xt c = Xt.get ~xt c.space + Hashtbl.Xt.length ~xt c.table
23+
24+
let set_capacity ~xt c new_capacity =
25+
check_capacity new_capacity;
26+
let old_length = Hashtbl.Xt.length ~xt c.table in
27+
let old_space = Xt.get ~xt c.space in
28+
let old_capacity = old_space + old_length in
29+
for _ = 1 to old_length - new_capacity do
30+
Dllist.Xt.take_blocking_r ~xt c.order |> Hashtbl.Xt.remove ~xt c.table
31+
done;
32+
Xt.set ~xt c.space (Int.max 0 (old_space + new_capacity - old_capacity))
33+
34+
let get_opt ~xt c key =
35+
Hashtbl.Xt.find_opt ~xt c.table key
36+
|> Option.map @@ fun (node, datum) ->
37+
Dllist.Xt.move_l ~xt node c.order;
38+
datum
39+
40+
let set_blocking ~xt c key datum =
41+
let node =
42+
match Hashtbl.Xt.find_opt ~xt c.table key with
43+
| None ->
44+
if 0 = Xt.update ~xt c.space (fun n -> Int.max 0 (n - 1)) then
45+
Dllist.Xt.take_blocking_r ~xt c.order
46+
|> Hashtbl.Xt.remove ~xt c.table;
47+
Dllist.Xt.add_l ~xt key c.order
48+
| Some (node, _) ->
49+
Dllist.Xt.move_l ~xt node c.order;
50+
node
51+
in
52+
Hashtbl.Xt.replace ~xt c.table key (node, datum)
53+
54+
let remove ~xt c key =
55+
Hashtbl.Xt.find_opt ~xt c.table key
56+
|> Option.iter @@ fun (node, _) ->
57+
Hashtbl.Xt.remove ~xt c.table key;
58+
Dllist.Xt.remove ~xt node;
59+
Xt.incr ~xt c.space
60+
end
61+
62+
let capacity_of c = Kcas.Xt.commit { tx = Xt.capacity_of c }
63+
let set_capacity c n = Kcas.Xt.commit { tx = Xt.set_capacity c n }
64+
let get_opt c k = Kcas.Xt.commit { tx = Xt.get_opt c k }
65+
66+
let set_blocking ?timeoutf c k v =
67+
Kcas.Xt.commit ?timeoutf { tx = Xt.set_blocking c k v }
68+
69+
let remove c k = Kcas.Xt.commit { tx = Xt.remove c k }

test/kcas_data/lru_cache.mli

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
open Kcas
2+
open Kcas_data
3+
4+
type ('k, 'v) t
5+
6+
val create : ?hashed_type:'k Hashtbl.hashed_type -> int -> ('k, 'v) t
7+
8+
module Xt :
9+
Lru_cache_intf.Ops
10+
with type ('k, 'v) t := ('k, 'v) t
11+
with type ('x, 'fn) fn := xt:'x Xt.t -> 'fn
12+
with type ('x, 'fn) blocking_fn := xt:'x Xt.t -> 'fn
13+
14+
include
15+
Lru_cache_intf.Ops
16+
with type ('k, 'v) t := ('k, 'v) t
17+
with type ('x, 'fn) fn := 'fn
18+
with type ('x, 'fn) blocking_fn := ?timeoutf:float -> 'fn
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
open Kcas
2+
3+
module Lru_cache = struct
4+
include Lru_cache
5+
6+
module Xt = struct
7+
include Xt
8+
9+
let get ~xt c key = Kcas.Xt.to_blocking ~xt (get_opt c key)
10+
11+
let get_if ~xt c key predicate =
12+
let snap = Kcas.Xt.snapshot ~xt in
13+
let datum = get ~xt c key in
14+
if predicate datum then datum else Retry.later (Kcas.Xt.rollback ~xt snap)
15+
16+
let try_set ~xt c key datum =
17+
match set_blocking ~xt c key datum with
18+
| () -> true
19+
| exception Retry.Later -> false
20+
end
21+
22+
let get ?timeoutf c k = Kcas.Xt.commit ?timeoutf { tx = Xt.get c k }
23+
let get_if ?timeoutf c k p = Kcas.Xt.commit ?timeoutf { tx = Xt.get_if c k p }
24+
let try_set c k d = Kcas.Xt.commit { tx = Xt.try_set c k d }
25+
end
26+
27+
let () =
28+
let c = Lru_cache.create 10 in
29+
let domain =
30+
Domain.spawn @@ fun () ->
31+
let tx ~xt = Lru_cache.Xt.get ~xt c "a" + Lru_cache.Xt.get ~xt c "b" in
32+
Xt.commit { tx }
33+
in
34+
Lru_cache.set_blocking c "b" 30;
35+
Lru_cache.set_blocking c "a" 12;
36+
assert (Domain.join domain = 42);
37+
()
38+
39+
let () =
40+
let c = Lru_cache.create 10 in
41+
assert (Lru_cache.try_set c "a" 1);
42+
Lru_cache.set_blocking c "c" 2;
43+
assert (Lru_cache.capacity_of c = 10);
44+
assert (Lru_cache.get_opt c "b" = None);
45+
assert (Lru_cache.get c "a" = 1);
46+
Lru_cache.set_capacity c 3;
47+
assert (Lru_cache.get c "c" = 2);
48+
Lru_cache.set_capacity c 1;
49+
assert (Lru_cache.capacity_of c = 1);
50+
assert (Lru_cache.get_opt c "a" = None);
51+
assert (Lru_cache.get_if c "c" (( <> ) 0) = 2);
52+
Lru_cache.remove c "c";
53+
assert (Lru_cache.get_opt c "c" = None);
54+
()
55+
56+
let () = Printf.printf "LRU Cache OK!\n%!"

test/kcas_data/lru_cache_intf.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module type Ops = sig
2+
type ('k, 'v) t
3+
type ('x, 'fn) fn
4+
type ('x, 'fn) blocking_fn
5+
6+
val capacity_of : ('x, ('k, 'v) t -> int) fn
7+
val set_capacity : ('x, ('k, 'v) t -> int -> unit) fn
8+
val get_opt : ('x, ('k, 'v) t -> 'k -> 'v option) fn
9+
val set_blocking : ('x, ('k, 'v) t -> 'k -> 'v -> unit) blocking_fn
10+
val remove : ('x, ('k, 'v) t -> 'k -> unit) fn
11+
end

0 commit comments

Comments
 (0)