-
Notifications
You must be signed in to change notification settings - Fork 10
/
cache.ml
399 lines (346 loc) · 11.8 KB
/
cache.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
open Prelude
module StdHashtbl = Hashtbl
open ExtLib
open Printf
module type Lock = sig
type t
val create : unit -> t
val locked : t -> (unit -> 'a) -> 'a
end
module NoLock = struct
type t = unit
let create () = ()
let locked () f = f ()
end
module TimeLimited2(E: Set.OrderedType)
(Lock: sig type t val create : unit -> t val locked : t -> (unit -> 'a) -> 'a end) = struct
type time = Int64.t
let fixed f = 10000. *. f |> Int64.of_float
let current () = Unix.gettimeofday () |> fixed
module Value = struct
type t = E.t * time
let compare (v1,_) (v2,_) = E.compare v1 v2
end
module M = Set.Make(Value)
type t = { limit : time; mutable next : time; lock : Lock.t; mutable m : M.t; }
let private_purge t =
let cur = current () in
if cur >= t.next then
begin
t.next <- Int64.add t.limit cur;
t.m <- M.filter (fun (_,t) -> t > cur) t.m
end
let create limit = { limit = fixed limit; next = 0L; lock = Lock.create (); m = M.empty }
let add t x =
let expire = Int64.add t.limit (current ()) in
(* FIXME replace not add *)
Lock.locked t.lock (fun () -> private_purge t; t.m <- M.add (x, expire) t.m)
let get t v =
(* lock is not needed *)
Lock.locked t.lock (fun () -> M.find_opt (v, t.limit) t.m)
let count t = Lock.locked t.lock (fun () -> M.cardinal t.m)
let iter t f = Lock.locked t.lock (fun () -> M.iter (fun (x,_) -> f x) t.m)
end
module Count = struct
open Hashtbl
type 'a t = ('a, int ref) Hashtbl.t
let create () : 'a t = create 16
let clear = Hashtbl.clear
let entry t x = match find t x with r -> r | exception Not_found -> let r = ref 0 in Hashtbl.add t x r; r
let plus t x n = entry t x += n
let minus t x n = entry t x -= n
let of_enum e = let h = create () in Enum.iter (fun (k,n) -> plus h k n) e; h
let of_list l = of_enum @@ List.enum l
let add t x = plus t x 1
let del t x = minus t x 1
let enum t = enum t |> Enum.map (fun (k,n) -> k, !n)
let iter t f = iter (fun k n -> f k !n) t
let fold t f acc = Hashtbl.fold (fun k n acc -> f k !n acc) t acc
let count t k = match Hashtbl.find t k with n -> !n | exception Not_found -> 0
let count_all t = Hashtbl.fold (fun _ n acc -> acc + !n) t 0
let size = Hashtbl.length
let show t ?(sep=" ") f = enum t |>
List.of_enum |> List.sort ~cmp:(Action.compare_by fst) |>
List.map (fun (x,n) -> sprintf "%S: %u" (f x) n) |>
String.concat sep
let show_sorted t ?limit ?(sep="\n") f = enum t |>
List.of_enum |> List.sort ~cmp:(flip @@ Action.compare_by snd) |>
(match limit with None -> id | Some n -> List.take n) |>
List.map (fun (x,n) -> sprintf "%6d : %S" n (f x)) |>
String.concat sep
let stats t ?(cmp=compare) f =
if Hashtbl.length t = 0 then
"<empty>"
else
let a = Array.of_enum (enum t) in
let total = Array.fold_left (fun t (_,n) -> t + n) 0 a in
let half = total / 2 in
let cmp (x,_) (y,_) = cmp x y in
Array.sort cmp a;
let med = ref None in
let (mi,ma,_) = Array.fold_left begin fun (mi,ma,sum) x ->
let sum = sum + snd x in
if !med = None && half <= sum then med := Some x;
let mi = if snd x < snd mi then x else mi in
let ma = if snd x > snd ma then x else ma in
mi, ma, sum
end ((fst a.(0), max_int), (fst a.(0),min_int), 0) a
in
let show (x,n) = sprintf "%S (%d)" (f x) n in
sprintf "total %d median %s min %s max %s"
total (match !med with None -> "?" | Some x -> show x) (show mi) (show ma)
let distrib t =
if Hashtbl.length t = 0 then
[||]
else
let a = Array.of_enum (enum t) in
let total = Array.fold_left (fun t (_,n) -> t + n) 0 a in
let limits = Array.init 10 (fun i -> total * (i + 1) / 10) in
let cmp (x,_) (y,_) = compare (x:float) y in
Array.sort cmp a;
let distrib = limits |> Array.map begin fun limit ->
let (v,_) = Array.fold_left begin fun (found,sum) (v,n) ->
let sum = sum + n in
if found = None && limit <= sum then Some v, sum else (found,sum)
end (None,0) a in
match v with
| None -> nan
| Some v -> v
end
in
distrib
let show_distrib ?(sep="\n") t =
distrib t |> Array.mapi (fun i v -> sprintf "%d%% <= %f" ((i + 1) * 10) v) |> Array.to_list |> String.concat sep
let report t ?limit ?cmp ?(sep="\n") f =
let data = show_sorted t ?limit ~sep f in
let stats = stats t ?cmp f in
stats^sep^data
let names (t : 'a t) = List.of_enum @@ Hashtbl.keys t
end
(*
Generationnal LRU cache.
Elements are store in a first fifo, and get evicted in order.
If an element is reused while in the first fifo, it is promoted to a second fifo, from which elements are also evicted in order.
Hits from the second fifo puts back the element in the back of this fifo.
The goal is to avoid low hit rate due to large workload with some regularly used elements which would get evicted from the LRU
before being reused
*)
module LRU (Keys : StdHashtbl.HashedType) = struct
module Hashtbl = StdHashtbl.Make(Keys)
module Queue = struct
exception Empty
type 'a elem = 'a Dllist.node_t
type 'a t = 'a elem option ref
let create () = ref None
let unwrap = Dllist.get
let is_singleton list = Dllist.next list == list
let drop elem =
match is_singleton elem with
| true -> None
| false -> Some (Dllist.rev_drop elem)
let append t value =
match !t with
| None -> t := Some (value)
| Some queue ->
Dllist.splice value (Dllist.next queue);
Dllist.splice queue value
let push t value =
let node = Dllist.create value in
append t node;
node
let pop t =
match !t with
| None -> raise Empty
| Some queue ->
t := drop queue;
queue
let remove t elem =
match !t with
| None -> ()
| Some queue when elem == queue ->
t := drop queue
| Some _ ->
Dllist.remove elem
end
type 'v entry = {
key : Hashtbl.key;
mutable value : 'v;
mutable queue : [`Lru | `Lfu ];
}
type 'v t = {
table : 'v entry Queue.elem Hashtbl.t;
mutable lru_avaibl : int;
mutable lfu_avaibl : int;
lru : 'v entry Queue.t;
lfu : 'v entry Queue.t;
mutable hit : int;
mutable miss : int;
}
let create size =
assert (size > 0);
{
table = Hashtbl.create size;
lru = Queue.create ();
lfu = Queue.create ();
hit = 0;
miss = 0;
lru_avaibl = size;
lfu_avaibl = size;
}
let size cache = Hashtbl.length cache.table
let iter f cache = Hashtbl.iter (fun key value -> f key (Queue.unwrap value).value) cache.table
let miss cache = cache.miss
let hit cache = cache.hit
let replace cache key value =
try
let entry = Hashtbl.find cache.table key |> Queue.unwrap in
entry.value <- value
with Not_found -> ()
let get_evicted cache key =
try
let node = Hashtbl.find cache.table key in
let entry = Queue.unwrap node in
cache.hit <- cache.hit + 1;
(* first remove the entry from the current queue *)
begin match entry.queue with
| `Lru ->
(* if the node is in the lru queuen it will be moved to the lfu queue *)
cache.lru_avaibl <- cache.lru_avaibl + 1;
entry.queue <- `Lfu;
Queue.remove cache.lru node;
| `Lfu ->
cache.lfu_avaibl <- cache.lfu_avaibl + 1;
Queue.remove cache.lfu node
end;
(* If the queue is full, drop one entry *)
let evicted =
if cache.lfu_avaibl <= 0 then begin
let evicted = Queue.unwrap (Queue.pop cache.lfu) in
Hashtbl.remove cache.table evicted.key;
Some (evicted.key, evicted.value)
end else begin
cache.lfu_avaibl <- cache.lfu_avaibl - 1;
None
end
in
Queue.append cache.lfu node;
entry.value, evicted
with Not_found -> cache.miss <- cache.miss + 1; raise Not_found
let find cache key =
let entry = Queue.unwrap @@ Hashtbl.find cache.table key in
entry.value
let get cache key =
fst @@ get_evicted cache key
let mem cache key = Hashtbl.mem cache.table key
let lru_free cache = cache.lru_avaibl
let lfu_free cache = cache.lfu_avaibl
let put_evicted cache key value =
try
let node = Hashtbl.find cache.table key |> Queue.unwrap in
node.value <- value;
None
with Not_found ->
let evicted =
if cache.lru_avaibl = 0 then begin
let evicted = Queue.unwrap (Queue.pop cache.lru) in
Hashtbl.remove cache.table evicted.key;
Some (evicted.key, evicted.value)
end else begin
cache.lru_avaibl <- cache.lru_avaibl - 1;
None
end
in
let node = Queue.push cache.lru { key; value; queue = `Lru } in
Hashtbl.add cache.table key node;
evicted
let put cache key value = put_evicted cache key value |> ignore
let remove cache key =
try
let node = Hashtbl.find cache.table key in
Hashtbl.remove cache.table key;
match (Queue.unwrap node).queue with
| `Lru ->
cache.lru_avaibl <- cache.lru_avaibl + 1;
Queue.remove cache.lru node
| `Lfu ->
cache.lfu_avaibl <- cache.lfu_avaibl + 1;
Queue.remove cache.lfu node
with Not_found -> ()
end
module Group = struct
type ('a,'b) t = ('b,'a list) Hashtbl.t * ('a -> 'b)
let by f = Hashtbl.create 32, f
let add (h,f) x = let k = f x in try Hashtbl.replace h k (x :: Hashtbl.find h k) with Not_found -> Hashtbl.add h k [x]
let get (h,_) k = try Hashtbl.find h k with Not_found -> []
let iter (h,_) k = Hashtbl.iter k h
let keys (h,_) = Hashtbl.keys h
end
let group_fst e =
let h = Hashtbl.create 10 in
Enum.iter (fun (k,v) -> Hashtbl.replace h k (try v :: Hashtbl.find h k with Not_found -> [v])) e;
Hashtbl.enum h
module Assoc = struct
type ('a,'b) t = ('a,'b) Hashtbl.t
let create () = Hashtbl.create 32
let add h k v =
assert (false = Hashtbl.mem h k);
Hashtbl.add h k v
let get = Hashtbl.find
let try_get = Hashtbl.find_option
let del h k =
try
let v = Hashtbl.find h k in
Hashtbl.remove h k; v
with
Not_found -> assert false
let remove h k =
assert (true = Hashtbl.mem h k);
Hashtbl.remove h k
let size = Hashtbl.length
let fold = Hashtbl.fold
end
module Lists = struct
type ('a,'b) t = ('a,'b list) Hashtbl.t
let create () = Hashtbl.create 16
let get h k = try Hashtbl.find h k with Not_found -> []
let set = Hashtbl.replace
let add h k v = Hashtbl.replace h k (v::get h k)
let enum = Hashtbl.enum
let clear = Hashtbl.clear
let count_keys = Hashtbl.length
let count_all h = Hashtbl.fold (fun _ l acc -> acc + List.length l) h 0
end
class ['a] cache (cb : ('a list -> unit)) ~limit =
object(self)
val mutable l = []
method name = "cache"
method add x =
l <- x :: l;
if List.length l >= limit then
begin
cb l;
self#clear
end
method get = l
method dump = cb l; l <- []
method clear = l <- []
method to_list = l
method size = List.length l
end
type 'a reused = { cache : 'a Stack.t; create : (unit -> 'a); reset : ('a -> unit); }
let reuse create reset = { cache = Stack.create (); create; reset; }
let use t = if Stack.is_empty t.cache then t.create () else Stack.pop t.cache
let recycle t x = t.reset x; Stack.push x t.cache
module ReuseLocked(L : Lock)(T : sig type t val create : unit -> t val reset : t -> unit end) : sig
type t = T.t
val get : unit -> t
val release : t -> unit
end = struct
type t = T.t
type cache = { cache : t Stack.t; lock : L.t }
let cache = { cache = Stack.create (); lock = L.create () }
let get' () = if Stack.is_empty cache.cache then T.create () else Stack.pop cache.cache
let get () = L.locked cache.lock get'
let release x = L.locked cache.lock (fun () -> T.reset x; Stack.push x cache.cache)
end
module Reuse = ReuseLocked(NoLock)