mirror of
https://github.com/nlohmann/json.git
synced 2026-11-04 11:49:22 +00:00
docs: captions, test-file table and figure fixes for the 3.13.0 post
- caption every table and figure - describe what makes twitter.json, citm_catalog.json, canada.json and jeopardy.json hard, and what each timeline panel exercises - split the json_view layout figure into the tree and the index, with the example text above; fix the arrows into the string boxes - arrows and a "less is better" axis label in the cliffs figure - name nlohmann/json in the comparison tables and chart - licenses of the adapted code in the thanks section Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
18 files changed
+468
-160
No files matched your search
@@ -104,10 +104,8 @@ def ribbon(body, x0, y, cw, prefix, label=True):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- figure 1: parse() vs json_document
|
||||
def layout():
|
||||
W, H, P = 840, 692, "jvl"
|
||||
b = markers(P)
|
||||
# --- panel A: basic_json::parse()
|
||||
def layout_tree(b, P):
|
||||
"""panel A: basic_json::parse(), y from 0 to 220"""
|
||||
b.append(text(16, 26, "basic_json::parse(): a tree, every array, object, and string in its own heap allocation", "nj-jsonview-h"))
|
||||
b.append(rect(16, 98, 60, 34, "nj-jsonview-box"))
|
||||
b.append(text(46, 120, "json", "nj-jsonview-mono", anchor="middle"))
|
||||
@@ -117,7 +115,7 @@ def layout():
|
||||
rows = [('"port"', "6379", None), ('"price"', "19.9", "double: 19.90 is gone"), ('"tags"', "\u2022", None)]
|
||||
for i, (k, v, note) in enumerate(rows):
|
||||
y = 50 + i * 48
|
||||
b.append(curve(192, 115, 226, y + 17, LINE, P, bend=0))
|
||||
b.append(line(192, 115, 226, y + 17, LINE, P))
|
||||
b.append(rect(228, y, 180, 34, "nj-jsonview-heap"))
|
||||
b.append(text(240, y + 22, k, "nj-jsonview-mono", KEY))
|
||||
b.append(text(396, y + 22, v, "nj-jsonview-mono", NUM if i < 2 else LINE, anchor="end"))
|
||||
@@ -132,8 +130,9 @@ def layout():
|
||||
b.append('<path class="nj-jsonview-sep" d="M624 %g V%g"/>' % (yt - 17, yt + 17))
|
||||
b.append(text(601, yt + 5, "json", "nj-jsonview-mono", anchor="middle"))
|
||||
b.append(text(647, yt + 5, "json", "nj-jsonview-mono", anchor="middle"))
|
||||
b.append(curve(601, yt - 17, 706, 130, LINE, P, bend=-20))
|
||||
b.append(curve(647, yt + 17, 706, 196, LINE, P, bend=20))
|
||||
for x1, y1, y2 in ((601, yt - 17, 130), (647, yt + 17, 196)):
|
||||
b.append('<path class="nj-jsonview-arr nj-jsonview-c-%s" d="M%g %g C%g %g %g %g %g %g" marker-end="url(#%s-%s)"/>'
|
||||
% (LINE, x1, y1, x1, y2, x1 + 40, y2, 706, y2, P, LINE))
|
||||
b.append(rect(708, 113, 126, 34, "nj-jsonview-heap"))
|
||||
b.append(text(771, 135, "std::string eu", "nj-jsonview-mono nj-jsonview-sm", STR, anchor="middle"))
|
||||
b.append(rect(708, 179, 126, 34, "nj-jsonview-heap"))
|
||||
@@ -141,9 +140,10 @@ def layout():
|
||||
# legend
|
||||
b.append(rect(16, 186, 18, 14, "nj-jsonview-heap", rx=3))
|
||||
b.append(text(42, 198, "a separate heap allocation", "nj-jsonview-note"))
|
||||
# --- panel B: json_document::parse()
|
||||
y0 = 252
|
||||
b.append('<path class="nj-jsonview-sep" d="M16 %g H824"/>' % (y0 - 18))
|
||||
|
||||
|
||||
def layout_index(b, P, y0):
|
||||
"""panel B: json_document::parse(), y from y0 - 16 to y0 + 440"""
|
||||
b.append(text(16, y0 + 10, "json_document::parse(): the source text stays where it is; one flat index of 16-byte nodes", "nj-jsonview-h"))
|
||||
b.append(text(16, y0 + 38, "source text, 54 bytes (borrowed, or owned by the document)", "nj-jsonview-note"))
|
||||
cw, rx0, ry = 14, 42, y0 + 48
|
||||
@@ -185,6 +185,25 @@ def layout():
|
||||
b.append(curve(vx + 105, vy, n6x, ny + nh, STRUCT, P))
|
||||
b.append(text(30, ny + nh + 50, "integers are converted while parsing and kept in their node;", "nj-jsonview-note"))
|
||||
b.append(text(30, ny + nh + 68, "floats and strings point back into the text", "nj-jsonview-note"))
|
||||
|
||||
|
||||
TREE_DESC = ("The text {\"port\": 6379, \"price\": 19.90, \"tags\": [\"eu\", \"a\\\"b\"]} parsed by basic_json::parse(): "
|
||||
"the root json value points to a std::map whose three nodes are separate heap allocations; the price is stored "
|
||||
"as the double 19.9; the tags array is a std::vector of two json values, each pointing to its own std::string.")
|
||||
INDEX_DESC = ("The same text parsed by json_document::parse(): it keeps the 54 bytes of source text and builds a flat index of "
|
||||
"nine 16-byte nodes in document order: object, key port, unsigned integer 6379 (the converted value is in the "
|
||||
"node), key price, float, key tags, array, string eu, and an escaped string. Keys, the float, and the plain "
|
||||
"string point back into the source text; the escaped string points into the document's buffer of decoded "
|
||||
"strings. A json_view is two pointers: one to the document and one to a node, here the array.")
|
||||
|
||||
|
||||
def layout():
|
||||
# both panels in one figure (features/json_view.md)
|
||||
W, H, P = 840, 692, "jvl"
|
||||
b = markers(P)
|
||||
layout_tree(b, P)
|
||||
b.append('<path class="nj-jsonview-sep" d="M16 234 H824"/>')
|
||||
layout_index(b, P, 252)
|
||||
write("json_view_layout.svg", W, H, "fig-jvl-title",
|
||||
"The same J\u200bSON text parsed by basic_json::parse() and by json_document::parse()",
|
||||
"The text {\"port\": 6379, \"price\": 19.90, \"tags\": [\"eu\", \"a\\\"b\"]}. Top: basic_json::parse() builds a tree: "
|
||||
@@ -195,6 +214,16 @@ def layout():
|
||||
"key tags, array, string eu, and an escaped string. Keys, the float, and the plain string point back into the "
|
||||
"source text; the escaped string points into the document's buffer of decoded strings. A json_view is two "
|
||||
"pointers: one to the document and one to a node, here the array.", b)
|
||||
# each panel on its own (home/blog/how_3_13_0_got_faster.md), with their own marker ids
|
||||
b = markers("jvlt")
|
||||
layout_tree(b, "jvlt")
|
||||
write("json_view_layout_tree.svg", W, 222, "fig-jvlt-title",
|
||||
"A J\u200bSON text parsed by basic_json::parse()", TREE_DESC, b)
|
||||
b = markers("jvli") + ['<g transform="translate(0 -236)">']
|
||||
layout_index(b, "jvli", 252)
|
||||
b.append('</g>')
|
||||
write("json_view_layout_index.svg", W, H - 236, "fig-jvli-title",
|
||||
"The same J\u200bSON text parsed by json_document::parse()", INDEX_DESC, b)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- figure 2: lifetimes
|
||||
|
||||
@@ -164,10 +164,10 @@ def overview():
|
||||
desc.append("%s %s" % (label, fx(v).replace(TIMES, "x")))
|
||||
b.append('<line class="nj-charts-ref" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(1), top, X(1), axis_y))
|
||||
b.append('<line class="nj-charts-zero" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(0), top, X(0), axis_y))
|
||||
b.append('<text class="nj-charts-note" x="%g" y="%g">speedup over 3.12.0 (the line at 1%s is 3.12.0)</text>' % (L, 18, TIMES))
|
||||
b.append('<text class="nj-charts-note" x="%g" y="%g">speedup over 3.12.0, more is better (the line at 1%s is 3.12.0)</text>' % (L, 18, TIMES))
|
||||
write("perf_blog_overview.svg", W, axis_y + 28, "nj-charts--blog", "nj-blog-overview",
|
||||
"Speedup of 3.13.0 over 3.12.0 per workload, Apple M1 Max, Apple clang",
|
||||
"Horizontal bars, longer is faster. " + "; ".join(desc) + ".", b)
|
||||
"Horizontal bars of the speedup, more is better. " + "; ".join(desc) + ".", b)
|
||||
|
||||
|
||||
# ---------- timeline: one panel per workload ----------
|
||||
@@ -188,7 +188,7 @@ MONTHS = [("2025-04", "3.12.0"), ("2026-09", "Sep 2026"), ("2026-10", "Oct")]
|
||||
def timeline():
|
||||
rows = list(csv.DictReader((HERE / "data" / "perf_timeline.csv").open()))
|
||||
n = len(rows)
|
||||
W, H = 340, 252
|
||||
W, H = 340, 270
|
||||
L, R, T, B = 44, 330, 62, 208
|
||||
for col, title, labels in TIMELINE:
|
||||
vals = [float(r[col]) if r[col] else None for r in rows]
|
||||
@@ -251,6 +251,7 @@ def timeline():
|
||||
b.append('<line class="nj-charts-grid" stroke-dasharray="2 2" x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f"/>' % (x, ty + 4, x, y - 5))
|
||||
b.append('<text class="nj-charts-note" x="%.1f" y="%.1f" text-anchor="middle">%s</text>' % (tx, ty, text))
|
||||
b.append('<line class="nj-charts-axis" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (L, B, R, B))
|
||||
b.append('<text class="nj-charts-note" x="%g" y="%g">speedup over 3.12.0 (log scale), more is better</text>' % (8, H - 8))
|
||||
final = rel[-1]
|
||||
desc = ("%s: speedup over 3.12.0 at each of %d commits, ending at %s. Labeled steps: %s."
|
||||
% (title, n, fx(final).replace(TIMES, "x"), ", ".join("#%s" % p for p in labels)))
|
||||
@@ -284,19 +285,24 @@ def cliffs():
|
||||
b.append('<line class="nj-charts-grid" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(10 ** e), top - 10, X(10 ** e), axis_y))
|
||||
lab = {-3: "1 µs", -2: "10 µs", -1: "0.1 ms", 0: "1 ms", 1: "10 ms", 2: "0.1 s", 3: "1 s", 4: "10 s"}[e]
|
||||
b.append('<text class="nj-charts-tick" x="%g" y="%g">%s</text>' % (X(10 ** e), axis_y + 16, lab))
|
||||
b.append('<text class="nj-charts-tick" x="%g" y="%g">runtime (log scale), less is better</text>' % ((L + R) / 2, axis_y + 34))
|
||||
b.append('<defs><marker id="nj-blog-cliffs-head" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" '
|
||||
'orient="auto"><path class="nj-charts-head" d="M0 0 L10 5 L0 10 z"/></marker></defs>')
|
||||
b.append('<circle class="nj-charts-ojson-dot" cx="%g" cy="14" r="5"/><text class="nj-charts-leg" x="%g" y="18.5">3.12.0</text>' % (L, L + 10))
|
||||
b.append('<circle class="nj-charts-json-dot" cx="%g" cy="14" r="5"/><text class="nj-charts-leg" x="%g" y="18.5">3.13.0</text>' % (L + 80, L + 90))
|
||||
for i, (op, old, new) in enumerate(CLIFFS):
|
||||
y = top + i * pitch + 8
|
||||
b.append('<text class="nj-charts-rowlabel" x="%g" y="%g" text-anchor="end">%s</text>' % (L - 10, y + 4, esc(op)))
|
||||
b.append('<line class="nj-charts-axis" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(new), y, X(old), y))
|
||||
# an arrow from the old time to the new one, ending just before the new dot
|
||||
b.append('<line class="nj-charts-axis" x1="%g" y1="%g" x2="%g" y2="%g" marker-end="url(#nj-blog-cliffs-head)"/>'
|
||||
% (X(old) - 6, y, X(new) + 7, y))
|
||||
b.append('<circle class="nj-charts-ojson-dot" cx="%g" cy="%g" r="5"><title>%s, 3.12.0: %g ms</title></circle>' % (X(old), y, esc(op), old))
|
||||
b.append('<circle class="nj-charts-json-dot" cx="%g" cy="%g" r="5"><title>%s, 3.13.0: %g ms</title></circle>' % (X(new), y, esc(op), new))
|
||||
b.append('<text class="nj-charts-factor" x="%g" y="%g">%s</text>' % ((X(old) + X(new)) / 2, y - 7, fx(old / new)))
|
||||
desc.append("%s: %g ms to %g ms" % (op, old, new))
|
||||
write("perf_blog_cliffs.svg", W, axis_y + 28, "nj-charts--blog", "nj-blog-cliffs",
|
||||
write("perf_blog_cliffs.svg", W, axis_y + 44, "nj-charts--blog", "nj-blog-cliffs",
|
||||
"Operations that grew quadratically or worse: time with 3.12.0 and 3.13.0, log scale",
|
||||
"Dot plot on a logarithmic time axis. " + "; ".join(desc) + ".", b)
|
||||
"Dot plot on a logarithmic time axis, less is better; an arrow leads from each 3.12.0 time to the 3.13.0 time. " + "; ".join(desc) + ".", b)
|
||||
|
||||
|
||||
# ---------- json_view: parse throughput ----------
|
||||
@@ -304,13 +310,13 @@ def cliffs():
|
||||
VIEW_FILES = ["twitter.json", "citm_catalog.json", "canada.json", "jeopardy.json"]
|
||||
VIEW_BYTES = [631514, 1727204, 2251051, 55554625]
|
||||
VIEW_US = [ # (engine, css class, [µs per file]), fresh documents, from the table in "How it compares"
|
||||
("json_document", "nj-charts-c1", [183, 364, 943, 22681]),
|
||||
("nlohmann/json json_document", "nj-charts-c1", [183, 364, 943, 22681]),
|
||||
("yyjson", "nj-charts-c2", [194, 478, 1489, 30342]),
|
||||
("simdjson DOM", "nj-charts-c3", [202, 486, 2097, 26904]),
|
||||
("sonic-rs (Rust)", "nj-charts-c4", [302, 664, 1907, 30202]),
|
||||
("sonic-cpp", "nj-charts-c5", [379, 986, 2580, 40804]),
|
||||
("Boost.JSON", "nj-charts-c6", [868, 1483, 2481, 88925]),
|
||||
("json::parse", "nj-charts-c1 nj-charts-dim", [1352, 2386, 5915, 163750]),
|
||||
("nlohmann/json json::parse", "nj-charts-c1 nj-charts-dim", [1352, 2386, 5915, 163750]),
|
||||
]
|
||||
VIEW = [(e, c, [n / (t * 1000) for n, t in zip(VIEW_BYTES, us)]) for e, c, us in VIEW_US] # GB/s
|
||||
|
||||
@@ -321,7 +327,6 @@ def view():
|
||||
hi = math.ceil(hi * 2) / 2
|
||||
X = lambda v: L + v / hi * (R - L)
|
||||
bh, gap = 9, 2
|
||||
top = 64
|
||||
group_h = len(VIEW) * (bh + gap) + 14
|
||||
b, desc = [], []
|
||||
x, ly = 8, 10
|
||||
@@ -332,13 +337,14 @@ def view():
|
||||
b.append('<rect class="%s" x="%g" y="%g" width="12" height="12" rx="3"/>' % (cls, x, ly))
|
||||
b.append('<text class="nj-charts-leg" x="%g" y="%g">%s</text>' % (x + 18, ly + 10.5, esc(eng)))
|
||||
x += w
|
||||
top = ly + 34 # below the last row of the legend
|
||||
axis_y = top + group_h * len(VIEW_FILES)
|
||||
t = 0.5
|
||||
while t <= hi + 1e-9:
|
||||
b.append('<line class="nj-charts-grid" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(t), top - 4, X(t), axis_y))
|
||||
b.append('<text class="nj-charts-tick" x="%g" y="%g">%g</text>' % (X(t), axis_y + 16, t))
|
||||
t += 0.5
|
||||
b.append('<text class="nj-charts-tick" x="%g" y="%g">GB/s, longer is faster</text>' % ((L + R) / 2, axis_y + 34))
|
||||
b.append('<text class="nj-charts-tick" x="%g" y="%g">GB/s, more is better</text>' % ((L + R) / 2, axis_y + 34))
|
||||
for i, f in enumerate(VIEW_FILES):
|
||||
y0 = top + i * group_h
|
||||
b.append('<text class="nj-charts-rowlabel" x="%g" y="%g" text-anchor="end">%s</text>' % (L - 8, y0 + group_h / 2, esc(f)))
|
||||
@@ -349,8 +355,8 @@ def view():
|
||||
desc.append("%s %s %.2f GB/s" % (f, eng, vals[i]))
|
||||
b.append('<line class="nj-charts-zero" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(0), top - 4, X(0), axis_y))
|
||||
write("perf_blog_view.svg", W, axis_y + 44, "nj-charts--blog", "nj-blog-view",
|
||||
"Parse throughput of json_document and six other document parsers, fresh documents, Apple M1 Max",
|
||||
"Grouped horizontal bars in GB/s. " + "; ".join(desc) + ".", b)
|
||||
"Parse throughput of nlohmann/json's json_document and json::parse and five other document parsers, fresh documents, Apple M1 Max",
|
||||
"Grouped horizontal bars in GB/s, more is better. " + "; ".join(desc) + ".", b)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user