BTree: restructured for memory layout efficiency
This commit is contained in:
parent
93ce8b06e1
commit
927e4099f5
1 changed files with 73 additions and 67 deletions
|
|
@ -8,19 +8,20 @@ const
|
|||
type
|
||||
Key = string
|
||||
Val = string
|
||||
Entry = ref object
|
||||
key: Key
|
||||
val: Val # external nodes only
|
||||
next: Node # internal nodes only; helper field to iterate over array entries
|
||||
Node = ref object
|
||||
m: int
|
||||
children: array[M, Entry]
|
||||
keys: array[M, Key]
|
||||
case isInternal: bool
|
||||
of false:
|
||||
vals: array[M, Val]
|
||||
of true:
|
||||
links: array[M, Node]
|
||||
BTree = ref object
|
||||
root: Node
|
||||
height: int ## height
|
||||
n: int ## number of key-value pairs
|
||||
|
||||
proc newBTree(): BTree = BTree(root: Node(m: 0))
|
||||
proc newBTree(): BTree = BTree(root: Node(m: 0, isInternal: false))
|
||||
|
||||
proc less(a, b: Key): bool = cmp(a, b) < 0
|
||||
|
||||
|
|
@ -28,63 +29,61 @@ proc eq(a, b: Key): bool = cmp(a, b) == 0
|
|||
|
||||
proc search(x: Node, key: Key, ht: int): Val =
|
||||
if ht == 0:
|
||||
# external node
|
||||
assert(not x.isInternal)
|
||||
for j in 0 ..< x.m:
|
||||
if eq(key, x.children[j].key): return x.children[j].val
|
||||
if eq(key, x.keys[j]): return x.vals[j]
|
||||
else:
|
||||
# internal node
|
||||
assert(x.isInternal)
|
||||
for j in 0 ..< x.m:
|
||||
if j+1 == x.m or less(key, x.children[j+1].key):
|
||||
return search(x.children[j].next, key, ht-1)
|
||||
return nil
|
||||
|
||||
proc candidates(x: Node, key: Key, ht: int): Node =
|
||||
if ht == 0:
|
||||
return x
|
||||
else:
|
||||
# internal node
|
||||
for j in 0 ..< x.m:
|
||||
if j+1 == x.m or less(key, x.children[j+1].key):
|
||||
return candidates(x.children[j].next, key, ht-1)
|
||||
return nil
|
||||
|
||||
iterator allValues(t: BTree; key: Key): Val =
|
||||
let x = candidates(t.root, key, t.height)
|
||||
if x != nil:
|
||||
for j in 0 ..< x.m:
|
||||
if eq(key, x.children[j].key): yield x.children[j].val
|
||||
if j+1 == x.m or less(key, x.keys[j+1]):
|
||||
return search(x.links[j], key, ht-1)
|
||||
|
||||
proc get(t: BTree; key: Key): Val = search(t.root, key, t.height)
|
||||
|
||||
proc split(h: Node): Node =
|
||||
## split node in half
|
||||
result = Node(m: Mhalf)
|
||||
result = Node(m: Mhalf, isInternal: h.isInternal)
|
||||
h.m = Mhalf
|
||||
for j in 0 ..< Mhalf:
|
||||
result.children[j] = h.children[Mhalf + j]
|
||||
result.keys[j] = h.keys[Mhalf + j]
|
||||
if h.isInternal:
|
||||
for j in 0 ..< Mhalf:
|
||||
result.links[j] = h.links[Mhalf + j]
|
||||
else:
|
||||
for j in 0 ..< Mhalf:
|
||||
shallowCopy(result.vals[j], h.vals[Mhalf + j])
|
||||
|
||||
proc insert(h: Node, key: Key, val: Val, ht: int): Node =
|
||||
var t = Entry(key: key, val: val, next: nil)
|
||||
#var t = Entry(key: key, val: val, next: nil)
|
||||
var newKey = key
|
||||
var j = 0
|
||||
if ht == 0:
|
||||
# external node:
|
||||
assert(not h.isInternal)
|
||||
while j < h.m:
|
||||
if less(key, h.children[j].key): break
|
||||
if less(key, h.keys[j]): break
|
||||
inc j
|
||||
for i in countdown(h.m, j+1):
|
||||
shallowCopy(h.vals[i], h.vals[i-1])
|
||||
h.vals[j] = val
|
||||
else:
|
||||
# internal node
|
||||
assert h.isInternal
|
||||
var newLink: Node = nil
|
||||
while j < h.m:
|
||||
if j+1 == h.m or less(key, h.children[j+1].key):
|
||||
let u = insert(h.children[j].next, key, val, ht-1)
|
||||
if j+1 == h.m or less(key, h.keys[j+1]):
|
||||
let u = insert(h.links[j], key, val, ht-1)
|
||||
inc j
|
||||
if u == nil: return nil
|
||||
t.key = u.children[0].key
|
||||
t.next = u
|
||||
newKey = u.keys[0]
|
||||
newLink = u
|
||||
break
|
||||
inc j
|
||||
for i in countdown(h.m, j+1):
|
||||
h.children[i] = h.children[i-1]
|
||||
h.children[j] = t
|
||||
h.links[i] = h.links[i-1]
|
||||
h.links[j] = newLink
|
||||
|
||||
for i in countdown(h.m, j+1):
|
||||
h.keys[i] = h.keys[i-1]
|
||||
h.keys[j] = newKey
|
||||
inc h.m
|
||||
return if h.m < M: nil else: split(h)
|
||||
|
||||
|
|
@ -94,21 +93,25 @@ proc put(b: BTree; key: Key; val: Val) =
|
|||
if u == nil: return
|
||||
|
||||
# need to split root
|
||||
let t = Node(m: 2)
|
||||
t.children[0] = Entry(key: b.root.children[0].key, val: nil, next: b.root)
|
||||
t.children[1] = Entry(key: u.children[0].key, val: nil, next: u)
|
||||
let t = Node(m: 2, isInternal: true)
|
||||
t.keys[0] = b.root.keys[0]
|
||||
t.links[0] = b.root
|
||||
t.keys[1] = u.keys[0]
|
||||
t.links[1] = u
|
||||
b.root = t
|
||||
inc b.height
|
||||
|
||||
proc toString(h: Node, ht: int, indent: string; result: var string) =
|
||||
if ht == 0:
|
||||
assert(not h.isInternal)
|
||||
for j in 0..<h.m:
|
||||
result.add(indent)
|
||||
result.add($h.children[j].key & " " & $h.children[j].val & "\n")
|
||||
result.add($h.keys[j] & " " & $h.vals[j] & "\n")
|
||||
else:
|
||||
assert(h.isInternal)
|
||||
for j in 0..<h.m:
|
||||
if j > 0: result.add(indent & "(" & $h.children[j].key & ")\n")
|
||||
toString(h.children[j].next, ht-1, indent & " ", result)
|
||||
if j > 0: result.add(indent & "(" & $h.keys[j] & ")\n")
|
||||
toString(h.links[j], ht-1, indent & " ", result)
|
||||
|
||||
proc `$`(b: BTree): string =
|
||||
result = ""
|
||||
|
|
@ -116,8 +119,8 @@ proc `$`(b: BTree): string =
|
|||
|
||||
proc main =
|
||||
var st = newBTree()
|
||||
st.put("www.cs.princeton.edu", "128.112.136.12")
|
||||
st.put("www.cs.princeton.edu", "128.112.136.11")
|
||||
st.put("www.cs.princeton.edu", "abc")
|
||||
st.put("www.cs.princeton.edu", "xyz")
|
||||
st.put("www.princeton.edu", "128.112.128.15")
|
||||
st.put("www.yale.edu", "130.132.143.21")
|
||||
st.put("www.simpsons.com", "209.052.165.60")
|
||||
|
|
@ -134,23 +137,26 @@ proc main =
|
|||
st.put("www.weather.com", "63.111.66.11")
|
||||
st.put("www.yahoo.com", "216.109.118.65")
|
||||
|
||||
echo("cs.princeton.edu: ", st.get("www.cs.princeton.edu"))
|
||||
echo("hardvardsucks.com: ", st.get("www.harvardsucks.com"))
|
||||
echo("simpsons.com: ", st.get("www.simpsons.com"))
|
||||
echo("apple.com: ", st.get("www.apple.com"))
|
||||
echo("ebay.com: ", st.get("www.ebay.com"))
|
||||
echo("dell.com: ", st.get("www.dell.com"))
|
||||
echo()
|
||||
echo("size: ", st.n)
|
||||
echo("height: ", st.height)
|
||||
echo(st)
|
||||
assert st.get("www.cs.princeton.edu") == "abc"
|
||||
assert st.get("www.harvardsucks.com") == nil
|
||||
|
||||
var dups = newBTree()
|
||||
for i in 0..20:
|
||||
dups.put("testme", $i)
|
||||
echo dups
|
||||
for v in allValues(dups, "testme"):
|
||||
echo v
|
||||
echo dups.get("testme")
|
||||
assert st.get("www.simpsons.com") == "209.052.165.60"
|
||||
assert st.get("www.apple.com") == "17.112.152.32"
|
||||
assert st.get("www.ebay.com") == "66.135.192.87"
|
||||
assert st.get("www.dell.com") == "143.166.224.230"
|
||||
assert(st.n == 17)
|
||||
assert(st.height == 2)
|
||||
|
||||
when true:
|
||||
var b2 = newBTree()
|
||||
const iters = 10_000
|
||||
for i in 1..iters:
|
||||
b2.put($i, $(iters - i))
|
||||
for i in 1..iters:
|
||||
let x = b2.get($i)
|
||||
if x != $(iters - i):
|
||||
echo "got ", x, ", but expected ", iters - i
|
||||
echo b2.n
|
||||
echo b2.height
|
||||
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue