perf(json): 23.2 - a parsed tree can be let go (Value.free / Json.free: its nodes and lists, not its strings), a string is parsed in one allocation instead of one per character, the parser's cursor is freed, and gltf_load frees the last file's tree and the text it parsed
300 parses of a 446 KB glTF: 2118 MB before, 528 MB with the one-allocation string, 94 MB freed. A headless Maroon Lake at play: 2334 -> 2195 MB by footprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
537d3a0ab3
commit
9539f7bcec
3 changed files with 42 additions and 2 deletions
|
|
@ -478,10 +478,12 @@ namespace Value {
|
|||
alias as_int(value) = value_as_int
|
||||
alias as_float(value) = value_as_float
|
||||
alias as_str(value) = value_as_str
|
||||
alias free(value) = value_free
|
||||
}
|
||||
namespace Json {
|
||||
alias encode(value) = json_encode
|
||||
alias parse(text) = json_parse
|
||||
alias free(value) = json_free
|
||||
}
|
||||
namespace Xml {
|
||||
alias parse(text) = xml_parse
|
||||
|
|
|
|||
|
|
@ -166,9 +166,23 @@ function jp_skip_ws(p: JP) -> void {
|
|||
function json_parse(s: pointer) -> Val {
|
||||
let p = new JP
|
||||
p.s = s; p.i = 0; p.n = len(s)
|
||||
return jp_value(p)
|
||||
let v = jp_value(p)
|
||||
free(p)
|
||||
return v
|
||||
}
|
||||
|
||||
# Let a parsed tree go once what it describes is built: every node and its lists. Its strings stay -
|
||||
# a caller may keep a name it read out of the tree - and a tree put together by hand, which may share
|
||||
# a node or hold a literal, is not for this (Ludic has no collector; plan 23 of maroon-lake).
|
||||
function value_free(v: Val) -> void {
|
||||
if v == null { return }
|
||||
for i in 0 .. len(v.kids) { value_free(v.kids[i]) }
|
||||
free(v.kids)
|
||||
free(v.keys)
|
||||
free(v)
|
||||
}
|
||||
function json_free(v: Val) -> void { value_free(v) }
|
||||
|
||||
function jp_value(p: JP) -> Val {
|
||||
jp_skip_ws(p)
|
||||
if p.i >= p.n { return value_null() }
|
||||
|
|
@ -182,9 +196,29 @@ function jp_value(p: JP) -> Val {
|
|||
return jp_number(p)
|
||||
}
|
||||
|
||||
# read a quoted string (cursor on the opening quote) -> the unescaped contents.
|
||||
# read a quoted string (cursor on the opening quote) -> the unescaped contents. One allocation for a
|
||||
# string with no escape in it: built a character at a time, every step was a string never freed
|
||||
function jp_string(p: JP) -> string {
|
||||
p.i += 1 # skip opening quote
|
||||
let a = p.i
|
||||
while p.i < p.n {
|
||||
let c = p.s[p.i]
|
||||
if c == '"' {
|
||||
let out = p.s[a..p.i]
|
||||
p.i += 1
|
||||
return out
|
||||
}
|
||||
if c == '\\' {
|
||||
p.i = a
|
||||
return jp_string_esc(p)
|
||||
}
|
||||
p.i += 1
|
||||
}
|
||||
return p.s[a..p.n]
|
||||
}
|
||||
|
||||
# the same with escapes in it, the rare case, a character at a time
|
||||
function jp_string_esc(p: JP) -> string {
|
||||
var out = ""
|
||||
while p.i < p.n {
|
||||
let c = p.s[p.i]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue