Skip to content

Commit 4dc9fbf

Browse files
committed
More relaxed reading of size estimate
Reading the size is still potentially expensive.
1 parent 1291de8 commit 4dc9fbf

File tree

3 files changed

+37
-20
lines changed

3 files changed

+37
-20
lines changed

lib/picos_aux.htbl/picos_aux_htbl.ml

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,20 @@ let[@inline never] try_resize t r new_capacity ~clear =
281281
true
282282
end
283283

284-
let rec adjust_estimated_size t r mask delta result =
284+
(** This only gives an "estimate" of the size, which can be off by one or more
285+
and even be negative, so this must be used with care. *)
286+
let[@inline] non_linearizable_size r =
287+
let accum = ref 0 in
288+
let non_linearizable_size = r.non_linearizable_size in
289+
for i = 0 to Array.length non_linearizable_size - 1 do
290+
(* [fenceless_get] is fine here, because we are not even trying to get a
291+
precise linearizable size, i.e. it is fine to read past values. *)
292+
accum :=
293+
!accum + Atomic.fenceless_get (Array.unsafe_get non_linearizable_size i)
294+
done;
295+
!accum
296+
297+
let rec adjust_size t r mask delta result =
285298
let i = Multicore_magic.instantaneous_domain_index () in
286299
let n = Array.length r.non_linearizable_size in
287300
if i < n then begin
@@ -295,20 +308,7 @@ let rec adjust_estimated_size t r mask delta result =
295308
&& Int64.to_int (Random.bits64 ()) land mask = 0
296309
&& Atomic.get t == r
297310
then begin
298-
(* This only gives an "estimate" of the size, which can be off by one or
299-
more and even be negative, so this must be used with care. *)
300-
let estimated_size r =
301-
let cs = r.non_linearizable_size in
302-
let n = Array.length cs - 1 in
303-
let rec estimated_size cs n sum =
304-
let n = n - 1 in
305-
if 0 <= n then
306-
estimated_size cs n (sum + Atomic.get (Array.unsafe_get cs n))
307-
else sum
308-
in
309-
estimated_size cs n (Atomic.get (Array.unsafe_get cs n))
310-
in
311-
let estimated_size = estimated_size r in
311+
let estimated_size = non_linearizable_size r in
312312
let capacity = Atomic_array.length r.buckets in
313313
if capacity < estimated_size && capacity < r.max_buckets then
314314
try_resize t r (capacity + capacity) ~clear:false |> ignore
@@ -330,7 +330,7 @@ let rec adjust_estimated_size t r mask delta result =
330330
in
331331
let new_r = { r with non_linearizable_size = new_cs } in
332332
let r = if Atomic.compare_and_set t r new_r then new_r else Atomic.get t in
333-
adjust_estimated_size t r mask delta result
333+
adjust_size t r mask delta result
334334

335335
(* *)
336336

@@ -396,14 +396,14 @@ let rec try_add t key value backoff =
396396
| B Nil ->
397397
let after = Cons { key; value; rest = Nil } in
398398
if Atomic_array.unsafe_compare_and_set r.buckets i (B Nil) (B after) then
399-
adjust_estimated_size t r mask 1 true
399+
adjust_size t r mask 1 true
400400
else try_add t key value (Backoff.once backoff)
401401
| B (Cons _ as before) ->
402402
if exists r.equal key before then false
403403
else
404404
let after = Cons { key; value; rest = before } in
405405
if Atomic_array.unsafe_compare_and_set r.buckets i (B before) (B after)
406-
then adjust_estimated_size t r mask 1 true
406+
then adjust_size t r mask 1 true
407407
else try_add t key value (Backoff.once backoff)
408408
| B (Resize _) ->
409409
let _ = finish t (Atomic.get t) in
@@ -515,7 +515,7 @@ let rec try_dissoc : type v c r. (_, v) t -> _ -> c -> (v, c, r) op -> _ -> r =
515515
| Exists -> true
516516
| Return -> cons_r.value
517517
in
518-
adjust_estimated_size t r mask (-1) res
518+
adjust_size t r mask (-1) res
519519
else try_dissoc t key present op (Backoff.once backoff)
520520
else not_found op
521521
else
@@ -544,7 +544,7 @@ let rec try_dissoc : type v c r. (_, v) t -> _ -> c -> (v, c, r) op -> _ -> r =
544544
| Exists -> true
545545
| Return -> assoc r.equal key cons_r.rest
546546
in
547-
adjust_estimated_size t r mask (-1) res
547+
adjust_size t r mask (-1) res
548548
else try_dissoc t key present op (Backoff.once backoff)
549549
| exception Not_found -> not_found op
550550
end
@@ -626,6 +626,10 @@ let find_random_exn t =
626626

627627
(* *)
628628

629+
let non_linearizable_length t = non_linearizable_size (Atomic.get t)
630+
631+
(* *)
632+
629633
let[@inline] try_add t key value = try_add t key value Backoff.default
630634

631635
(* *)

lib/picos_aux.htbl/picos_aux_htbl.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,12 @@ val find_random_exn : ('k, 'v) t -> 'k
287287
remove the entire binding from the hash table and avoid leaks. This time we
288288
also use a backoff mechanism, because, unlike with the lock-free bag, we
289289
don't use randomized keys. *)
290+
291+
(**/**)
292+
293+
val non_linearizable_length : ('k, 'v) t -> int
294+
(** [non_linearizable_length htbl] reads the current length of the hash table in
295+
a non-linearizable manner. Under contention the result will not be precise
296+
and may even be negative.
297+
298+
⚠️ This is exposed for debugging purposes only. *)

test/test_htbl.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ let () =
88
let t = Htbl.create () in
99
assert (Htbl.try_add t "Basics" 101);
1010
assert (Htbl.try_add t "Answer" 42);
11+
assert (Htbl.non_linearizable_length t = 2);
1112
assert (101 = Htbl.remove_exn t "Basics");
1213
assert (not (Htbl.try_remove t "Basics"));
14+
assert (Htbl.non_linearizable_length t = 1);
1315
assert (Htbl.remove_all t |> List.of_seq = [ ("Answer", 42) ]);
16+
assert (Htbl.non_linearizable_length t = 0);
1417
assert (Htbl.to_seq t |> List.of_seq = []);
1518
[ "One"; "Two"; "Three" ]
1619
|> List.iteri (fun v k -> assert (Htbl.try_add t k v));
20+
assert (Htbl.non_linearizable_length t = 3);
1721
assert (
1822
Htbl.to_seq t |> List.of_seq
1923
|> List.sort (fun l r -> String.compare (fst l) (fst r))

0 commit comments

Comments
 (0)