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
|
|
@ -163,7 +163,11 @@ function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, no
|
||||||
render3d_st.gltf_dir = dir
|
render3d_st.gltf_dir = dir
|
||||||
let text = Fs.read_text(dir + "/" + file)
|
let text = Fs.read_text(dir + "/" + file)
|
||||||
if text == null { print(`gltf: cannot read {dir}/{file}`); return null }
|
if text == null { print(`gltf: cannot read {dir}/{file}`); return null }
|
||||||
|
# the last file's tree goes first (it is read only until the next load: ludic.anim's read.ludic),
|
||||||
|
# and the text once parsed - nothing gives memory back on its own (plan 23 of maroon-lake)
|
||||||
|
if render3d_st.gltf_doc != null { Json.free(render3d_st.gltf_doc) }
|
||||||
render3d_st.gltf_doc = Json.parse(text)
|
render3d_st.gltf_doc = Json.parse(text)
|
||||||
|
free(text)
|
||||||
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
||||||
let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri"))
|
let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri"))
|
||||||
render3d_st.gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
render3d_st.gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
||||||
|
|
|
||||||
|
|
@ -478,10 +478,12 @@ namespace Value {
|
||||||
alias as_int(value) = value_as_int
|
alias as_int(value) = value_as_int
|
||||||
alias as_float(value) = value_as_float
|
alias as_float(value) = value_as_float
|
||||||
alias as_str(value) = value_as_str
|
alias as_str(value) = value_as_str
|
||||||
|
alias free(value) = value_free
|
||||||
}
|
}
|
||||||
namespace Json {
|
namespace Json {
|
||||||
alias encode(value) = json_encode
|
alias encode(value) = json_encode
|
||||||
alias parse(text) = json_parse
|
alias parse(text) = json_parse
|
||||||
|
alias free(value) = json_free
|
||||||
}
|
}
|
||||||
namespace Xml {
|
namespace Xml {
|
||||||
alias parse(text) = xml_parse
|
alias parse(text) = xml_parse
|
||||||
|
|
|
||||||
|
|
@ -166,9 +166,23 @@ function jp_skip_ws(p: JP) -> void {
|
||||||
function json_parse(s: pointer) -> Val {
|
function json_parse(s: pointer) -> Val {
|
||||||
let p = new JP
|
let p = new JP
|
||||||
p.s = s; p.i = 0; p.n = len(s)
|
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 {
|
function jp_value(p: JP) -> Val {
|
||||||
jp_skip_ws(p)
|
jp_skip_ws(p)
|
||||||
if p.i >= p.n { return value_null() }
|
if p.i >= p.n { return value_null() }
|
||||||
|
|
@ -182,9 +196,29 @@ function jp_value(p: JP) -> Val {
|
||||||
return jp_number(p)
|
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 {
|
function jp_string(p: JP) -> string {
|
||||||
p.i += 1 # skip opening quote
|
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 = ""
|
var out = ""
|
||||||
while p.i < p.n {
|
while p.i < p.n {
|
||||||
let c = p.s[p.i]
|
let c = p.s[p.i]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue