|
| 1 | +(* Copyright (c) 2024 Carine Morel |
| 2 | +
|
| 3 | + Permission to use, copy, modify, and/or distribute this software for any |
| 4 | + purpose with or without fee is hereby granted, provided that the above |
| 5 | + copyright notice and this permission notice appear in all copies. |
| 6 | +
|
| 7 | + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| 8 | + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| 9 | + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, |
| 10 | + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM |
| 11 | + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR |
| 12 | + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| 13 | + PERFORMANCE OF THIS SOFTWARE. *) |
| 14 | + |
| 15 | +(* Based on the skiplist implementation written by Vesa Karvonen. *) |
| 16 | + |
| 17 | +module Atomic = Multicore_magic.Transparent_atomic |
| 18 | + |
| 19 | +type ('k, 'v, _) node = |
| 20 | + | Null : ('k, 'v, [> `Null ]) node |
| 21 | + | Node : { |
| 22 | + key : 'k; |
| 23 | + value : 'v; |
| 24 | + next : ('k, 'v) links; |
| 25 | + mutable incr : Size.once; |
| 26 | + } |
| 27 | + -> ('k, 'v, [> `Node ]) node |
| 28 | + | Mark : { |
| 29 | + node : ('k, 'v, [< `Null | `Node ]) node; |
| 30 | + decr : Size.once; |
| 31 | + } |
| 32 | + -> ('k, 'v, [> `Mark ]) node |
| 33 | + |
| 34 | +and ('k, 'v) link = |
| 35 | + | Link : ('k, 'v, [< `Null | `Node | `Mark ]) node -> ('k, 'v) link |
| 36 | +[@@unboxed] |
| 37 | + |
| 38 | +and ('k, 'v) links = ('k, 'v) link Atomic.t array |
| 39 | + |
| 40 | +type 'k compare = 'k -> 'k -> int |
| 41 | +(* Encoding the [compare] function using an algebraic type would allow the |
| 42 | + overhead of calling a closure to be avoided for selected primitive types like |
| 43 | + [int]. *) |
| 44 | + |
| 45 | +type ('k, 'v) t = { compare : 'k compare; root : ('k, 'v) links; size : Size.t } |
| 46 | + |
| 47 | +(* *) |
| 48 | + |
| 49 | +(** [get_random_height max_height] gives a random value [n] in the range from |
| 50 | + [1] to [max_height] with the desired distribution such that [n] is twice as |
| 51 | + likely as [n + 1]. *) |
| 52 | +let rec get_random_height max_height = |
| 53 | + let m = (1 lsl max_height) - 1 in |
| 54 | + let x = Random.bits () land m in |
| 55 | + if x = 1 then |
| 56 | + (* We reject [1] to get the desired distribution. *) |
| 57 | + get_random_height max_height |
| 58 | + else |
| 59 | + (* We do a binary search for the highest 1 bit. Techniques in |
| 60 | +
|
| 61 | + Using de Bruijn Sequences to Index a 1 in a Computer Word |
| 62 | + by Leiserson, Prokop, and Randall |
| 63 | +
|
| 64 | + could perhaps speed this up a bit, but this is likely not performance |
| 65 | + critical. *) |
| 66 | + let n = 0 in |
| 67 | + let n, x = if 0xFFFF < x then (n + 0x10, x lsr 0x10) else (n, x) in |
| 68 | + let n, x = if 0x00FF < x then (n + 0x08, x lsr 0x08) else (n, x) in |
| 69 | + let n, x = if 0x000F < x then (n + 0x04, x lsr 0x04) else (n, x) in |
| 70 | + let n, x = if 0x0003 < x then (n + 0x02, x lsr 0x02) else (n, x) in |
| 71 | + let n, _ = if 0x0001 < x then (n + 0x01, x lsr 0x01) else (n, x) in |
| 72 | + max_height - n |
| 73 | + |
| 74 | +(* *) |
| 75 | + |
| 76 | +let[@inline] is_marked = function |
| 77 | + | Link (Mark _) -> true |
| 78 | + | Link (Null | Node _) -> false |
| 79 | + |
| 80 | +(* *) |
| 81 | + |
| 82 | +(** [find_path t key preds succs lowest] search fo the position after all the |
| 83 | + nodes with key [key], updating [preds] and [succs] and removing nodes with |
| 84 | + marked references along the way, and always descending down to [lowest] |
| 85 | + level. The boolean return value is only meaningful when [lowest] is given as |
| 86 | + [0]. *) |
| 87 | +let rec find_path t key preds succs lowest = |
| 88 | + let prev = t.root in |
| 89 | + let level = Array.length prev - 1 in |
| 90 | + let prev_at_level = Array.unsafe_get prev level in |
| 91 | + find_path_rec t key prev prev_at_level preds succs level lowest |
| 92 | + (Atomic.get prev_at_level) |
| 93 | + |
| 94 | +and find_path_rec t key prev prev_at_level preds succs level lowest = function |
| 95 | + | Link Null -> |
| 96 | + if level < Array.length preds then begin |
| 97 | + Array.unsafe_set preds level prev_at_level; |
| 98 | + Array.unsafe_set succs level Null |
| 99 | + end; |
| 100 | + if lowest < level then |
| 101 | + let level = level - 1 in |
| 102 | + let prev_at_level = Array.unsafe_get prev level in |
| 103 | + find_path_rec t key prev prev_at_level preds succs level lowest |
| 104 | + (Atomic.get prev_at_level) |
| 105 | + | Link (Node r as curr) -> begin |
| 106 | + let next_at_level = Array.unsafe_get r.next level in |
| 107 | + match Atomic.get next_at_level with |
| 108 | + | Link (Null | Node _) as next -> |
| 109 | + let c = t.compare key r.key in |
| 110 | + |
| 111 | + if 0 <= c then |
| 112 | + (* key >= r.key *) |
| 113 | + find_path_rec t key r.next next_at_level preds succs level lowest |
| 114 | + next |
| 115 | + else begin |
| 116 | + if level < Array.length preds then begin |
| 117 | + Array.unsafe_set preds level (Array.unsafe_get prev level); |
| 118 | + Array.unsafe_set succs level curr |
| 119 | + end; |
| 120 | + |
| 121 | + if lowest < level then |
| 122 | + let level = level - 1 in |
| 123 | + let prev_at_level = Array.unsafe_get prev level in |
| 124 | + find_path_rec t key prev prev_at_level preds succs level lowest |
| 125 | + (Atomic.get prev_at_level) |
| 126 | + else begin |
| 127 | + if level = 0 then begin |
| 128 | + if r.incr != Size.used_once then begin |
| 129 | + Size.update_once t.size r.incr; |
| 130 | + r.incr <- Size.used_once |
| 131 | + end; |
| 132 | + () (* Return *) |
| 133 | + end |
| 134 | + end |
| 135 | + end |
| 136 | + | Link (Mark r) -> |
| 137 | + (* The [curr_node] is being removed from the skiplist and we help with |
| 138 | + that. *) |
| 139 | + if level = 0 then Size.update_once t.size r.decr; |
| 140 | + find_path_rec t key prev prev_at_level preds succs level lowest |
| 141 | + (let after = Link r.node in |
| 142 | + if Atomic.compare_and_set prev_at_level (Link curr) after then |
| 143 | + after |
| 144 | + else Atomic.get prev_at_level) |
| 145 | + end |
| 146 | + | Link (Mark _) -> |
| 147 | + (* The node corresponding to [prev] is being removed from the skiplist. |
| 148 | + This means we might no longer have an up-to-date view of the skiplist |
| 149 | + and so we must restart the search. *) |
| 150 | + find_path t key preds succs lowest |
| 151 | + |
| 152 | +(* *) |
| 153 | + |
| 154 | +(** [find_node t key] tries to find the first node with the specified [key], |
| 155 | + removing nodes with marked references along the way, and stopping as soon as |
| 156 | + the node is found. *) |
| 157 | +let rec find_node t ?timestamp key = |
| 158 | + let prev = t.root in |
| 159 | + let level = Array.length prev - 1 in |
| 160 | + let prev_at_level = Array.unsafe_get prev level in |
| 161 | + find_node_rec t ~timestamp key prev prev_at_level level |
| 162 | + (Atomic.get prev_at_level) |
| 163 | + |
| 164 | +and find_node_rec t ~timestamp key prev prev_at_level level : |
| 165 | + _ -> (_, _, [< `Null | `Node ]) node = function |
| 166 | + | Link Null -> |
| 167 | + if 0 < level then |
| 168 | + let level = level - 1 in |
| 169 | + let prev_at_level = Array.unsafe_get prev level in |
| 170 | + find_node_rec t ~timestamp key prev prev_at_level level |
| 171 | + (Atomic.get prev_at_level) |
| 172 | + else Null |
| 173 | + | Link (Node r as curr) -> begin |
| 174 | + let next_at_level = Array.unsafe_get r.next level in |
| 175 | + match Atomic.get next_at_level with |
| 176 | + | Link (Null | Node _) as next -> |
| 177 | + let c = t.compare key r.key in |
| 178 | + if 0 < c then |
| 179 | + find_node_rec t ~timestamp key r.next next_at_level level next |
| 180 | + else if 0 < level then |
| 181 | + let level = level - 1 in |
| 182 | + let prev_at_level = Array.unsafe_get prev level in |
| 183 | + find_node_rec t ~timestamp key prev prev_at_level level |
| 184 | + (Atomic.get prev_at_level) |
| 185 | + else if c = 0 then begin |
| 186 | + if r.incr != Size.used_once then begin |
| 187 | + Size.update_once t.size r.incr; |
| 188 | + r.incr <- Size.used_once |
| 189 | + end; |
| 190 | + curr |
| 191 | + end |
| 192 | + else Null |
| 193 | + | Link (Mark r) -> |
| 194 | + if level = 0 then Size.update_once t.size r.decr; |
| 195 | + find_node_rec t ~timestamp key prev prev_at_level level |
| 196 | + (let after = Link r.node in |
| 197 | + if Atomic.compare_and_set prev_at_level (Link curr) after then |
| 198 | + after |
| 199 | + else Atomic.get prev_at_level) |
| 200 | + end |
| 201 | + | Link (Mark _) -> find_node t key |
| 202 | + |
| 203 | +(* *) |
| 204 | + |
| 205 | +let create ?(max_height = 10) ~compare () = |
| 206 | + (* The upper limit of [30] comes from [Random.bits ()] as well as from |
| 207 | + limitations with 32-bit implementations. It should not be a problem in |
| 208 | + practice. *) |
| 209 | + if max_height < 1 || 30 < max_height then |
| 210 | + invalid_arg "Skiplist: max_height must be in the range [1, 30]"; |
| 211 | + let root = Array.init max_height @@ fun _ -> Atomic.make (Link Null) in |
| 212 | + let size = Size.create () in |
| 213 | + { compare; root; size } |
| 214 | + |
| 215 | +let max_height_of t = Array.length t.root |
| 216 | + |
| 217 | +(* *) |
| 218 | + |
| 219 | +let rec add t key value preds succs = |
| 220 | + find_path t key preds succs 0; |
| 221 | + |
| 222 | + let (Node r as node : (_, _, [ `Node ]) node) = |
| 223 | + let next = Array.map (fun succ -> Atomic.make (Link succ)) succs in |
| 224 | + let incr = Size.new_once t.size Size.incr in |
| 225 | + Node { key; value; incr; next } |
| 226 | + in |
| 227 | + if |
| 228 | + let succ = Link (Array.unsafe_get succs 0) in |
| 229 | + Atomic.compare_and_set (Array.unsafe_get preds 0) succ (Link node) |
| 230 | + then begin |
| 231 | + if r.incr != Size.used_once then begin |
| 232 | + Size.update_once t.size r.incr; |
| 233 | + r.incr <- Size.used_once |
| 234 | + end; |
| 235 | + |
| 236 | + (* The node is now considered as added to the skiplist. *) |
| 237 | + let rec update_levels level : unit = |
| 238 | + if Array.length r.next = level then begin |
| 239 | + if is_marked (Atomic.get (Array.unsafe_get r.next (level - 1))) then begin |
| 240 | + (* The node we finished adding has been removed concurrently. To |
| 241 | + ensure that no references we added to the node remain, we call |
| 242 | + [find_node] which will remove nodes with marked references along |
| 243 | + the way. *) |
| 244 | + find_node t key |> ignore |
| 245 | + end |
| 246 | + end |
| 247 | + else if |
| 248 | + let succ = Link (Array.unsafe_get succs level) in |
| 249 | + Atomic.compare_and_set (Array.unsafe_get preds level) succ (Link node) |
| 250 | + then update_levels (level + 1) |
| 251 | + else |
| 252 | + let _found = find_path t key preds succs level in |
| 253 | + let rec update_nexts level' = |
| 254 | + if level' < level then update_levels level |
| 255 | + else |
| 256 | + let next = Array.unsafe_get r.next level' in |
| 257 | + match Atomic.get next with |
| 258 | + | Link (Null | Node _) as before -> |
| 259 | + let succ = Link (Array.unsafe_get succs level') in |
| 260 | + if before != succ then |
| 261 | + (* It is possible for a concurrent remove operation to have |
| 262 | + marked the link. *) |
| 263 | + if Atomic.compare_and_set next before succ then |
| 264 | + update_nexts (level' - 1) |
| 265 | + else update_levels level |
| 266 | + else update_nexts (level' - 1) |
| 267 | + | Link (Mark _) -> |
| 268 | + (* The node we were trying to add has been removed concurrently. |
| 269 | + To ensure that no references we added to the node remain, we |
| 270 | + call [find_node] which will remove nodes with marked |
| 271 | + references along the way. *) |
| 272 | + find_path t key preds succs level |
| 273 | + in |
| 274 | + update_nexts (Array.length r.next - 1) |
| 275 | + in |
| 276 | + update_levels 1 |
| 277 | + end |
| 278 | + else add t key value preds succs |
| 279 | + |
| 280 | +let add t key value : unit = |
| 281 | + let height = get_random_height (Array.length t.root) in |
| 282 | + let preds = |
| 283 | + (* Init with [Obj.magic ()] is safe as the array is fully overwritten by |
| 284 | + [find_path] called at the start of the recursive [try_add]. *) |
| 285 | + Array.make height (Obj.magic ()) |
| 286 | + in |
| 287 | + let succs = Array.make height Null in |
| 288 | + add t key value preds succs |
| 289 | + |
| 290 | +(* *) |
| 291 | + |
| 292 | +let length t = Size.get t.size |
| 293 | + |
| 294 | +(* *) |
| 295 | + |
| 296 | +let rec find_min t : (_, _, [< `Node | `Null ]) node = |
| 297 | + let root = t.root in |
| 298 | + let root_at_level0 = Array.unsafe_get root 0 in |
| 299 | + match Atomic.get root_at_level0 with |
| 300 | + | Link (Mark _) -> assert false |
| 301 | + | Link Null -> Null |
| 302 | + | Link (Node r) as curr_link -> ( |
| 303 | + let next_at_level_0 = Array.unsafe_get r.next 0 in |
| 304 | + match Atomic.get next_at_level_0 with |
| 305 | + | Link (Null | Node _) -> Node r |
| 306 | + | Link (Mark next_marked) -> |
| 307 | + Size.update_once t.size next_marked.decr; |
| 308 | + if |
| 309 | + Atomic.compare_and_set root_at_level0 curr_link |
| 310 | + (Link next_marked.node) |
| 311 | + then find_min t |
| 312 | + else find_min t) |
| 313 | + |
| 314 | +let rec try_remove t key next level link = function |
| 315 | + | Link (Mark r) -> |
| 316 | + if level = 0 then begin |
| 317 | + Size.update_once t.size r.decr; |
| 318 | + false |
| 319 | + end |
| 320 | + else |
| 321 | + let level = level - 1 in |
| 322 | + let link = Array.unsafe_get next level in |
| 323 | + try_remove t key next level link (Atomic.get link) |
| 324 | + | Link ((Null | Node _) as succ) -> |
| 325 | + let decr = |
| 326 | + if level = 0 then Size.new_once t.size Size.decr else Size.used_once |
| 327 | + in |
| 328 | + let marked_succ = Mark { node = succ; decr } in |
| 329 | + if Atomic.compare_and_set link (Link succ) (Link marked_succ) then |
| 330 | + if level = 0 then |
| 331 | + let _node = find_node t key in |
| 332 | + true |
| 333 | + else |
| 334 | + let level = level - 1 in |
| 335 | + let link = Array.unsafe_get next level in |
| 336 | + try_remove t key next level link (Atomic.get link) |
| 337 | + else try_remove t key next level link (Atomic.get link) |
| 338 | + |
| 339 | +let remove_min_opt t = |
| 340 | + let rec loop t = |
| 341 | + match find_min t with |
| 342 | + | Null -> None |
| 343 | + | Node { next; key; value; _ } -> |
| 344 | + let level = Array.length next - 1 in |
| 345 | + let link = Array.unsafe_get next level in |
| 346 | + if try_remove t key next level link (Atomic.get link) then |
| 347 | + Some (key, value) |
| 348 | + else loop t |
| 349 | + in |
| 350 | + loop t |
0 commit comments