sharedtables.nim 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Shared table support for Nim. Use plain old non GC'ed keys and values or
  10. ## you'll be in trouble. Uses a single lock to protect the table, lockfree
  11. ## implementations welcome but if lock contention is so high that you need a
  12. ## lockfree hash table, you're doing it wrong.
  13. ##
  14. ## Unstable API.
  15. import
  16. hashes, math, locks
  17. type
  18. KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B]
  19. KeyValuePairSeq[A, B] = ptr UncheckedArray[KeyValuePair[A, B]]
  20. SharedTable*[A, B] = object ## generic hash SharedTable
  21. data: KeyValuePairSeq[A, B]
  22. counter, dataLen: int
  23. lock: Lock
  24. template maxHash(t): untyped = t.dataLen-1
  25. include tableimpl
  26. template st_maybeRehashPutImpl(enlarge) {.dirty.} =
  27. if mustRehash(t):
  28. enlarge(t)
  29. index = rawGetKnownHC(t, key, hc)
  30. index = -1 - index # important to transform for mgetOrPutImpl
  31. rawInsert(t, t.data, key, val, hc, index)
  32. inc(t.counter)
  33. proc enlarge[A, B](t: var SharedTable[A, B]) =
  34. let oldSize = t.dataLen
  35. let size = oldSize * growthFactor
  36. var n = cast[KeyValuePairSeq[A, B]](allocShared0(
  37. sizeof(KeyValuePair[A, B]) * size))
  38. t.dataLen = size
  39. swap(t.data, n)
  40. for i in 0..<oldSize:
  41. let eh = n[i].hcode
  42. if isFilled(eh):
  43. var j: Hash = eh and maxHash(t)
  44. while isFilled(t.data[j].hcode):
  45. j = nextTry(j, maxHash(t))
  46. rawInsert(t, t.data, n[i].key, n[i].val, eh, j)
  47. deallocShared(n)
  48. template withLock(t, x: untyped) =
  49. acquire(t.lock)
  50. x
  51. release(t.lock)
  52. template withValue*[A, B](t: var SharedTable[A, B], key: A,
  53. value, body: untyped) =
  54. ## Retrieves the value at `t[key]`.
  55. ## `value` can be modified in the scope of the `withValue` call.
  56. runnableExamples:
  57. var table: SharedTable[string, string]
  58. init(table)
  59. table["a"] = "x"
  60. table["b"] = "y"
  61. table["c"] = "z"
  62. table.withValue("a", value):
  63. assert value[] == "x"
  64. table.withValue("b", value):
  65. value[] = "modified"
  66. table.withValue("b", value):
  67. assert value[] == "modified"
  68. table.withValue("nonexistent", value):
  69. assert false # not called
  70. acquire(t.lock)
  71. try:
  72. var hc: Hash
  73. var index = rawGet(t, key, hc)
  74. let hasKey = index >= 0
  75. if hasKey:
  76. var value {.inject.} = addr(t.data[index].val)
  77. body
  78. finally:
  79. release(t.lock)
  80. template withValue*[A, B](t: var SharedTable[A, B], key: A,
  81. value, body1, body2: untyped) =
  82. ## Retrieves the value at `t[key]`.
  83. ## `value` can be modified in the scope of the `withValue` call.
  84. runnableExamples:
  85. var table: SharedTable[string, string]
  86. init(table)
  87. table["a"] = "x"
  88. table["b"] = "y"
  89. table["c"] = "z"
  90. table.withValue("a", value):
  91. value[] = "m"
  92. var flag = false
  93. table.withValue("d", value):
  94. discard value
  95. doAssert false
  96. do: # if "d" notin table
  97. flag = true
  98. if flag:
  99. table["d"] = "n"
  100. assert table.mget("a") == "m"
  101. assert table.mget("d") == "n"
  102. acquire(t.lock)
  103. try:
  104. var hc: Hash
  105. var index = rawGet(t, key, hc)
  106. let hasKey = index >= 0
  107. if hasKey:
  108. var value {.inject.} = addr(t.data[index].val)
  109. body1
  110. else:
  111. body2
  112. finally:
  113. release(t.lock)
  114. proc mget*[A, B](t: var SharedTable[A, B], key: A): var B =
  115. ## Retrieves the value at `t[key]`. The value can be modified.
  116. ## If `key` is not in `t`, the `KeyError` exception is raised.
  117. withLock t:
  118. var hc: Hash
  119. var index = rawGet(t, key, hc)
  120. let hasKey = index >= 0
  121. if hasKey: result = t.data[index].val
  122. if not hasKey:
  123. when compiles($key):
  124. raise newException(KeyError, "key not found: " & $key)
  125. else:
  126. raise newException(KeyError, "key not found")
  127. proc mgetOrPut*[A, B](t: var SharedTable[A, B], key: A, val: B): var B =
  128. ## Retrieves value at `t[key]` or puts `val` if not present, either way
  129. ## returning a value which can be modified. **Note**: This is inherently
  130. ## unsafe in the context of multi-threading since it returns a pointer
  131. ## to `B`.
  132. withLock t:
  133. mgetOrPutImpl(enlarge)
  134. proc hasKeyOrPut*[A, B](t: var SharedTable[A, B], key: A, val: B): bool =
  135. ## Returns true if `key` is in the table, otherwise inserts `value`.
  136. withLock t:
  137. hasKeyOrPutImpl(enlarge)
  138. template tabMakeEmpty(i) = t.data[i].hcode = 0
  139. template tabCellEmpty(i) = isEmpty(t.data[i].hcode)
  140. template tabCellHash(i) = t.data[i].hcode
  141. proc withKey*[A, B](t: var SharedTable[A, B], key: A,
  142. mapper: proc(key: A, val: var B, pairExists: var bool)) =
  143. ## Computes a new mapping for the `key` with the specified `mapper`
  144. ## procedure.
  145. ##
  146. ## The `mapper` takes 3 arguments:
  147. ##
  148. ## 1. `key` - the current key, if it exists, or the key passed to
  149. ## `withKey` otherwise;
  150. ## 2. `val` - the current value, if the key exists, or default value
  151. ## of the type otherwise;
  152. ## 3. `pairExists` - `true` if the key exists, `false` otherwise.
  153. ##
  154. ## The `mapper` can can modify `val` and `pairExists` values to change
  155. ## the mapping of the key or delete it from the table.
  156. ## When adding a value, make sure to set `pairExists` to `true` along
  157. ## with modifying the `val`.
  158. ##
  159. ## The operation is performed atomically and other operations on the table
  160. ## will be blocked while the `mapper` is invoked, so it should be short and
  161. ## simple.
  162. ##
  163. ## Example usage:
  164. ##
  165. ## .. code-block:: nim
  166. ##
  167. ## # If value exists, decrement it.
  168. ## # If it becomes zero or less, delete the key
  169. ## t.withKey(1'i64) do (k: int64, v: var int, pairExists: var bool):
  170. ## if pairExists:
  171. ## dec v
  172. ## if v <= 0:
  173. ## pairExists = false
  174. withLock t:
  175. var hc: Hash
  176. var index = rawGet(t, key, hc)
  177. var pairExists = index >= 0
  178. if pairExists:
  179. mapper(t.data[index].key, t.data[index].val, pairExists)
  180. if not pairExists:
  181. delImplIdx(t, index, tabMakeEmpty, tabCellEmpty, tabCellHash)
  182. else:
  183. var val: B
  184. mapper(key, val, pairExists)
  185. if pairExists:
  186. st_maybeRehashPutImpl(enlarge)
  187. proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) =
  188. ## Puts a (key, value)-pair into `t`.
  189. withLock t:
  190. putImpl(enlarge)
  191. proc add*[A, B](t: var SharedTable[A, B], key: A, val: B) =
  192. ## Puts a new (key, value)-pair into `t` even if `t[key]` already exists.
  193. ## This can introduce duplicate keys into the table!
  194. withLock t:
  195. addImpl(enlarge)
  196. proc del*[A, B](t: var SharedTable[A, B], key: A) =
  197. ## Deletes `key` from hash table `t`.
  198. withLock t:
  199. delImpl(tabMakeEmpty, tabCellEmpty, tabCellHash)
  200. proc len*[A, B](t: var SharedTable[A, B]): int =
  201. ## Number of elements in `t`.
  202. withLock t:
  203. result = t.counter
  204. proc init*[A, B](t: var SharedTable[A, B], initialSize = 32) =
  205. ## Creates a new hash table that is empty.
  206. ##
  207. ## This proc must be called before any other usage of `t`.
  208. let initialSize = slotsNeeded(initialSize)
  209. t.counter = 0
  210. t.dataLen = initialSize
  211. t.data = cast[KeyValuePairSeq[A, B]](allocShared0(
  212. sizeof(KeyValuePair[A, B]) * initialSize))
  213. initLock t.lock
  214. proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) =
  215. deallocShared(t.data)
  216. deinitLock t.lock