Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #32 Huge speed up when creating copies of JsonTrees and memory usage drop. #36

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
37 changes: 23 additions & 14 deletions src/jsonpak/builder.nim
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ proc initFromJson*(dst: var string; tree: JsonTree; n: NodePos) =
if n.kind == opcodeNull:
dst = ""
else:
dst = n.str
dst = n.anyStr

proc initFromJson*(dst: var bool; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JBool})
Expand All @@ -29,25 +29,31 @@ proc initFromJson*(dst: var JsonTree; tree: JsonTree; n: NodePos) =

proc initFromJson*[T: SomeInteger](dst: var T; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JInt})
when T is BiggestUInt:
dst = parseBiggestUInt n.str
elif T is BiggestInt:
dst = parseBiggestInt n.str
elif T is SomeSignedInt:
dst = T(parseInt n.str)
if n.isShort:
dst = cast[T](n.operand)
else:
dst = T(parseUInt n.str)
when T is BiggestUInt:
dst = parseBiggestUInt n.str
elif T is BiggestInt:
dst = parseBiggestInt n.str
elif T is SomeSignedInt:
dst = T(parseInt n.str)
else:
dst = T(parseUInt n.str)

proc initFromJson*[T: SomeFloat](dst: var T; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JInt, JFloat})
if n.kind == opcodeFloat:
dst = T(parseFloat n.str)
dst = T(parseFloat n.anyStr)
else:
dst = T(parseBiggestInt n.str)
if n.isShort:
dst = T(cast[int64](n.operand))
else:
dst = T(parseBiggestInt n.str)

proc initFromJson*[T: enum](dst: var T; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JString})
dst = parseEnum[T](n.str)
dst = parseEnum[T](n.anyStr)

proc initFromJson*[T](dst: var seq[T]; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JArray})
Expand All @@ -66,8 +72,9 @@ proc initFromJson*[S, T](dst: var array[S, T]; tree: JsonTree; n: NodePos) =

proc initFromJson*[T](dst: var (Table[string, T]|OrderedTable[string, T]); tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JObject})
var buf = ""
for x in keys(tree, n):
initFromJson(mgetOrPut(dst, x.str, default(T)), tree, x.firstSon)
initFromJson(mgetOrPut(dst, x.anyStrBuffer, default(T)), tree, x.firstSon)

proc initFromJson*[T](dst: var ref T; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JObject, JNull})
Expand All @@ -87,9 +94,10 @@ proc initFromJson*[T](dst: var Option[T]; tree: JsonTree; n: NodePos) =

proc initFromJson*[T: object|tuple](dst: var T; tree: JsonTree; n: NodePos) =
verifyJsonKind(tree, n, {JObject})
var buf = ""
for x in keys(tree, n):
for k, v in dst.fieldPairs:
if x.str == k:
if x.anyStrBuffer == k:
initFromJson(v, tree, x.firstSon)
break # emulate elif

Expand Down Expand Up @@ -118,6 +126,7 @@ iterator pairs*[T](tree: JsonTree; path: JsonPtr; t: typedesc[T]): (lent string,
raisePathError(path.string)
assert n.kind == opcodeObject
var item = default(T)
var buf = ""
for x in keys(tree, n):
initFromJson(item, tree, x.firstSon)
yield (x.str, item)
yield (x.anyStrBuffer, item)
40 changes: 25 additions & 15 deletions src/jsonpak/dollar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,26 @@ type
Action = enum
actionElem, actionKeyVal, actionPop, actionEnd

proc currentAndNext(it: var JsonIter, tree: JsonTree): (NodePos, LitId, Action) =
proc currentAndNext(it: var JsonIter, tree: JsonTree): (NodePos, Action) =
if it.pos < it.tosEnd:
if it.tos.kind == opcodeArray:
result = (NodePos it.pos, LitId(0), actionElem)
result = (NodePos it.pos, actionElem)
else:
let litId = (NodePos it.pos).litId
result = (firstSon(NodePos it.pos), litId, actionKeyVal)
result = (firstSon(NodePos it.pos), actionKeyVal)
inc it.pos
nextChild tree, it.pos
elif it.stack.len > 0:
result = (it.tos, LitId(0), actionPop)
result = (it.tos, actionPop)
let tmp = it.stack.pop()
it.tos = tmp[0].NodePos
it.pos = tmp[1]
it.tosEnd = it.tos.tosEnd
else:
result = (nilNodeId, LitId(0), actionEnd)
result = (nilNodeId, actionEnd)

proc toUgly*(result: var string, tree: JsonTree, n: NodePos) =
privateAccess(JsonTree)
template key: string = tree.atoms[keyId]
var buf = ""
case n.kind
of opcodeArray, opcodeObject:
if n.kind == opcodeArray:
Expand All @@ -55,7 +54,7 @@ proc toUgly*(result: var string, tree: JsonTree, n: NodePos) =
var it = initJsonIter(tree, n)
var pendingComma = false
while true:
let (child, keyId, action) = currentAndNext(it, tree)
let (child, action) = currentAndNext(it, tree)
case action
of actionPop:
if child.kind == opcodeArray:
Expand All @@ -69,7 +68,7 @@ proc toUgly*(result: var string, tree: JsonTree, n: NodePos) =
result.add ","
pendingComma = false
if action == actionKeyVal:
key.escapeJson(result)
escapeJson(anyStrBuffer(NodePos child.int-1), result)
result.add ":"
case child.kind
of opcodeArray:
Expand All @@ -80,11 +79,17 @@ proc toUgly*(result: var string, tree: JsonTree, n: NodePos) =
result.add "{"
it.push child
pendingComma = false
of opcodeInt, opcodeFloat:
result.add child.str
of opcodeInt:
if child.isShort:
result.addInt cast[int64](child.operand)
else:
result.add child.str
pendingComma = true
of opcodeFloat:
result.add child.anyStrBuffer
pendingComma = true
of opcodeString:
escapeJson(child.str, result)
escapeJson(child.anyStrBuffer, result)
pendingComma = true
of opcodeBool:
result.add(if child.bval: "true" else: "false")
Expand All @@ -98,9 +103,14 @@ proc toUgly*(result: var string, tree: JsonTree, n: NodePos) =
else:
result.add "}"
of opcodeString:
escapeJson(n.str, result)
of opcodeInt, opcodeFloat:
result.add n.str
escapeJson(n.anyStrBuffer, result)
of opcodeInt:
if n.isShort:
result.addInt cast[int64](n.operand)
else:
result.add n.str
of opcodeFloat:
result.add n.anyStrBuffer
of opcodeBool:
result.add(if n.bval: "true" else: "false")
of opcodeNull:
Expand Down
5 changes: 4 additions & 1 deletion src/jsonpak/mapper.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ proc toJson*(s: string; tree: var JsonTree) =
storeAtom(tree, opcodeString, s)

proc toJson*[T: SomeInteger](n: T; tree: var JsonTree) =
storeAtom(tree, opcodeInt, $n)
if inShortIntRange(n):
storeShortInt(tree, n)
else:
storeAtom(tree, opcodeInt, $n)

proc toJson*[T: SomeFloat](n: T; tree: var JsonTree) =
storeAtom(tree, opcodeFloat, $n)
Expand Down
8 changes: 6 additions & 2 deletions src/jsonpak/parser.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import std/[parsejson, streams], private/[jsontree, jsonnode]
import std/[parsejson, streams, strutils], private/[jsontree, jsonnode]
export JsonParsingError

proc parseJsonAtom(tree: var JsonTree; p: var JsonParser) =
Expand All @@ -7,7 +7,11 @@ proc parseJsonAtom(tree: var JsonTree; p: var JsonParser) =
storeAtom(tree, opcodeString, p.a)
discard getTok(p)
of tkInt:
storeAtom(tree, opcodeInt, p.a)
let n = parseInt(p.a)
if inShortIntRange(n):
storeShortInt(tree, n)
else:
storeAtom(tree, opcodeInt, p.a)
discard getTok(p)
of tkFloat:
storeAtom(tree, opcodeFloat, p.a)
Expand Down
57 changes: 41 additions & 16 deletions src/jsonpak/private/jsonnode.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type
Node* = distinct uint32
Node* = distinct uint64
JsonNodeKind* = enum ## possible JSON node types
JNull,
JBool,
Expand All @@ -10,24 +10,49 @@ type
JArray

const
opcodeBits = 3'u32
opcodeBits = 4
payloadBits = sizeof(uint64)*8 - opcodeBits
shortBit = 0b0000_1000

opcodeNull* = uint32 JNull
opcodeBool* = uint32 JBool
opcodeFalse* = opcodeBool
opcodeTrue* = opcodeBool or 0b0000_1000
opcodeInt* = uint32 JInt
opcodeFloat* = uint32 JFloat
opcodeString* = uint32 JString
opcodeObject* = uint32 JObject
opcodeArray* = uint32 JArray
shortLenMask = (1 shl opcodeBits) - 1

opcodeMask = (1'u32 shl opcodeBits) - 1'u32
shortIntLow = -(1 shl payloadBits)
shortIntHigh = (1 shl payloadBits) - 1

template kind*(n: Node): uint32 = n.uint32 and opcodeMask
template operand*(n: Node): uint32 = n.uint32 shr opcodeBits.uint32
opcodeNull* = uint64 JNull
opcodeBool* = uint64 JBool
opcodeFalse* = opcodeBool
opcodeTrue* = opcodeBool or shortBit
opcodeInt* = uint64 JInt
opcodeFloat* = uint64 JFloat
opcodeString* = uint64 JString
opcodeObject* = uint64 JObject
opcodeArray* = uint64 JArray

template toNode*(kind, operand: uint32): Node =
Node(operand shl opcodeBits.uint32 or kind)
opcodeMask = (1 shl (opcodeBits - 1)) - 1

proc `==`*(a, b: Node): bool {.borrow.}

template kind*(n: Node): uint64 = n.uint64 and opcodeMask
template operand*(n: Node): uint64 = n.uint64 shr opcodeBits.uint64
template isShort*(n: Node): bool = (n.uint64 and shortBit) != 0
template shortLen*(n: Node): int = int(n.uint64 shr opcodeBits.uint64 and shortLenMask)

template toShortNode*(kind, operand: uint64): Node =
Node(operand shl opcodeBits.uint64 or kind or shortBit.uint64)

template toNode*(kind, operand: uint64): Node =
Node(operand shl opcodeBits.uint64 or kind)

template get*(n: Node; i: int): char = char(n.operand shr (i * 8 + opcodeBits) and 0xff)
template set*(p: uint64; i: int; c: char) = p = p or (c.uint64 shl (i * 8 + opcodeBits))
template setShortLen*(p: uint64; n: int) = p = p or n.uint64

template inShortIntRange*(x: int64): bool = x >= shortIntLow and x <= shortIntHigh
template inShortStrRange*(x: string): bool = x.len <= payloadBits div 8

proc toPayload*(data: string): uint64 =
result = 0
for i in 0 ..< data.len:
set(result, i, data[i])
setShortLen(result, data.len)
52 changes: 39 additions & 13 deletions src/jsonpak/private/jsontree.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ proc nextChild*(tree: JsonTree; pos: var int) {.inline.} =
else:
inc pos

proc toAtomNode*(tree: var JsonTree; kind: uint32, str: string): Node {.inline.} =
toNode(kind, uint32 getOrIncl(tree.atoms, str))
proc toAtomNode*(tree: var JsonTree; kind: uint64, str: string): Node {.inline.} =
toNode(kind, uint64 getOrIncl(tree.atoms, str))

type
NodePos* = distinct int
Expand Down Expand Up @@ -76,35 +76,61 @@ proc parentImpl*(tree: JsonTree; n: NodePos): NodePos =

template parent*(n: NodePos): NodePos = parentImpl(tree, n)

template kind*(n: NodePos): uint32 = tree.nodes[n.int].kind
template kind*(n: NodePos): uint64 = tree.nodes[n.int].kind
template litId*(n: NodePos): LitId = LitId operand(tree.nodes[n.int])
template operand*(n: NodePos): uint32 = tree.nodes[n.int].operand

template operand*(n: NodePos): uint64 = tree.nodes[n.int].operand
template str*(n: NodePos): string = tree.atoms[litId(n)]
template bval*(n: NodePos): bool = n.operand == 1

template isShort*(n: NodePos): bool = tree.nodes[n.int].isShort
template shortLen*(n: NodePos): int = tree.nodes[n.int].shortLen
template get*(n: NodePos; i: int): char = get(tree.nodes[n.int], i)

template copyShortStrToBuffer*(data: string, n: NodePos) =
data.setLen(n.shortLen)
for i in 0 ..< data.len:
data[i] = get(n, i)

template shortStr*(n: NodePos): string =
var data = newString(n.shortLen)
for i in 0 ..< data.len:
data[i] = get(n, i)
data

template anyStr*(n: NodePos): untyped =
(if n.isShort: n.shortStr else: n.str)

template anyStrBuffer*(x: NodePos): untyped =
(if n.isShort: (copyShortStrToBuffer(buf, n); buf) else: n.str)

type
PatchPos* = distinct int32

proc `<`*(a, b: PatchPos): bool {.borrow.}
proc `<=`*(a, b: PatchPos): bool {.borrow.}
proc `==`*(a, b: PatchPos): bool {.borrow.}

proc prepare*(tree: var JsonTree; kind: uint32): PatchPos =
proc prepare*(tree: var JsonTree; kind: uint64): PatchPos =
result = PatchPos tree.nodes.len
tree.nodes.add Node kind

proc patch*(tree: var JsonTree; pos: PatchPos) =
let pos = pos.int
assert tree.nodes[pos].kind > opcodeString
let distance = uint32(tree.nodes.len - pos)
tree.nodes[pos] = toNode(tree.nodes[pos].uint32, distance)
let distance = uint64(tree.nodes.len - pos)
tree.nodes[pos] = toNode(tree.nodes[pos].uint64, distance)

proc storeEmpty*(tree: var JsonTree; kind: uint64) {.inline.} =
tree.nodes.add toNode(kind, 1)

proc storeAtom*(tree: var JsonTree; kind: uint32) {.inline.} =
proc storeAtom*(tree: var JsonTree; kind: uint64) {.inline.} =
tree.nodes.add Node(kind)

proc storeAtom*(tree: var JsonTree; kind: uint32; data: string) {.inline.} =
tree.nodes.add toAtomNode(tree, kind, data)
proc storeShortInt*[T: SomeInteger](tree: var JsonTree; data: T) {.inline.} =
tree.nodes.add toShortNode(opcodeInt, cast[uint64](data))

proc storeEmpty*(tree: var JsonTree; kind: uint32) {.inline.} =
tree.nodes.add toNode(kind, 1)
proc storeAtom*(tree: var JsonTree; kind: uint64; data: string) {.inline.} =
if inShortStrRange(data):
tree.nodes.add toShortNode(kind, toPayload(data))
else:
tree.nodes.add toAtomNode(tree, kind, data)
Loading