Repository navigation
perf(reader): a pass over a file's records makes the plan of each class once, not once for each record (#1335) - #1503
Draft
DrunkOnJava wants to merge 13 commits into
Draft
DrunkOnJava wants to merge 13 commits into
DrunkOnJava wants to merge 13 commits into
Conversation
… each Family and FamilySymbol record (#1335)
…f each SWall and ArcWall graph (#1335)
… hex without a string per byte and reads the catalog's document without building what it ignores (#1335)
…reader, the probe imports them, and the index leaves are named fields (#1335)
…ss once, not once for each record (#1335)
… a difference and checks the bound, and held-out probe counts are kept (#1335)
# Conflicts: # docs/maintaining.md # probes/examples/probe_1335_projected_graphs.rs # src/research/native_document.rs # src/research/native_parameters.rs # src/research/native_walls.rs # tools/ci/scrub-holdout-results.sh
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #1483 (its commits come first on this branch; this PR is the last two). Decoding a record works out, for each class it meets, the chain of classes and the key of each field (the class plan). Each decode made them afresh, so a pass over a file's 17,000 records made them 17,000 times over. The passes of
extract_recordsnow keep the plans between records (native_parameters::PlanCache), in a cache whose size is bounded. A plan depends on the schema, the class and the fields a projected decode keeps, and nothing else, so a decode that finds one made earlier reads exactly as it would have with its own.Measured at 0767639, against #1483's measured head 4140b96 (run 42130101254): the exports of the 33 samples of Revit 2015 to 2023 take 0.974 of #1483's time (per file 0.96 to 1.00; every head run below every base run on 23 of 39 files, none the other way),
ifc.sha256anddiagnostics.jsonare equal on all 33 (66 of 66), and peak memory moves by +0.11 MiB on average (largest rise 1.4 MiB, inside run noise). The pushed head adds the bound on the cache, the probe's added checks and docs; time and bytes are not re-measured at it (the bound runs only when a plan is made, not when one is found, and no real file reaches it: the largest cache any of 88 files makes is 17.4% of the limit, see Testing).Motivation
#1335's plan-timing profile (run 42127560399, scratch head 76e8036, an EPYC 7763, shares only): 1,024,681 class plans were built over the 33 samples of 2015 to 2023, 2,114 ms of 68,189 (3.1%) of the exports' time; on 2015
rac_advanced47 of the definitions pass's 166 ms (28%), 20 of the wall records' 61 ms (32%) and 8.5 of the phase read's 111 ms. The measured gain is 2.6%.Changes
src/research/native_parameters.rs:ClassPlanno longer borrows the registry (its chain is the classes' tags, which the loop reads from the registry), so a plan outlives the decode that made it.PlanCacheholds them by class tag and the address and length of the kept fields; the plan records which fields it was made for, and a hit is taken only where they are the same fields, so another list at the same address is not taken for it. A cache is for one schema:decode_graph_cachedempties it when the schema's checksum is another's. A plan's depth check on a hit is the one the building made.decode_graph_with_catalog_usageanddecode_graph_projectedtake a fresh cache, as each decode made its own plans before.PLAN_ENTRY_COST(8) and one unit for each field of every class of its chain and each field kept; the cache holds at mostPLAN_CACHE_COST(1,000,000, an estimate of about 60 bytes a unit, so about 60 MB, not measured) of cost,PlanCache::with_limitsets another. A plan that would pass the limit empties the cache first; a plan dearer than the whole limit is not kept at all. Without it a crafted schema (one class of 50,000 fields and 30,000 empty child classes, each child's plan holding its 50,000 ancestor field names, about 2 MB, and 2,000 of them enough for wasm32's 4 GiB) or a caller of the publicdecode_graph_cachedthat passes a new field list each call would grow the cache without bound, where before each plan was freed with its record. The bound cannot change a graph: a decode that finds no plan makes its own, which is what every decode did before the cache, so a cleared cache or a plan that is not kept only costs the rebuilding. The probe decodes every record through a cache whose limit is 48 (so it empties itself or keeps nothing) and compares each with the decode that makes its own plans.src/research/native_document.rs:extract_recordsmakes one cache for its pass and decodes throughdecode_graph_cached.probes/examples/probe_1335_projected_graphs.rs: decodes every record through one cache shared by the whole file and every list, and through the 48-cost cache, in full and projected, and compares each with the same decode that makes its own plans: same refusal or the same graph (every object's class, token, start, end and fields, the edges, the consumed bytes, the usage). It prints the most the shared cache holds after any decode (plan-cache size), states its FACT in its module doc, and now exits 1 where anything it compares differs, so a job can turn red on it. What it proves is that a shared cache equals a fresh one; that a decode without a cache equals the one before the cache existed rests on the exports' byte identity, which Measure shows, not on this probe.docs/maintaining.md: the section on projected graphs covers the cache and says to run the probe after a change to the cache.tools/ci/scrub-holdout-results.sh: the probe's count lines (records decoded, differences, verdict words, schema class names, the readers' field names, never the file's name or an ElementId) are kept for held-out models, with itsexit N; checked on a copy of a real probe output.changelog.d/changed/1335-plan-cache.md.Testing
Measure run 42130101254 (
-f ref=0767639e -f base=4140b96c -f samples=true -f sample_years="2015 2023" -f models="-" -f scorers=none -f timing=true -f probe=probe_1335_projected_graphs; this head's first commit against #1483's measured head; shard 1 an EPYC 9V74 and shard 2 a 9V45; median of three exports each) and run 42130115171 (the probe alone, Revit 2024 to 2027 samples and the Revit 2025 models).tools/check-local.sh: nothing is built on this Mac (no local compute); CI's fast jobs run the draft.-f ref=274c1f4c -f base=none -f samples=true -f r2025=true -f scorers=none -f probe=probe_1335_projected_graphs; every sample of 2015 to 2027 and the Revit 2025 models, 88 files, the probe's status 0 on all 88). 7,896,301 decodes through the shared cache and 7,896,301 through the cache limited to 48 (which empties itself or keeps nothing) were each compared with the decode that makes its own plans: 0 mismatches in either, and 0 in the 6,768,258 projected decodes, withwall_factsthe same on every wall and the catalog the same on 79 files and refused alike on 9. The largest the shared cache reached on any file: 8,127 plans and a cost of 174,316 (Snowdon Towers architectural, 2024), 17.4% of the 1,000,000 limit, and no file emptied it (clears 0 on all 88; the smallest peak cost is 75,426 and the median 145,608; the next largest are 2027, 2026 and 2025racadvancedat 163,236, 160,478 and 158,647). That cache holds each class under seven field lists at once (no list, the readers' four and two that stress the mechanism), more than any pass shares (the definitions pass uses three), so a real pass holds less. No real file pays for the bound: none empties the cache, so none rebuilds a plan.ifc.sha256anddiagnostics.jsonof the 33 samples against perf(reader): the definitions pass, the wall records read and the catalog's document build only the fields they read; the preamble does less (#1335) #1483's head, 66 of 66 equal, at 0767639. Not measured at that head for Revit 2024 to 2027, the reference models, the offices, the Revit 2025 models or the held-out models: run 42144456661 (unfiltered,-f ref=2e5bc4cb -f base=e3cc717f -f samples=true -f r2025=true -f nordic=true -f holdout=true, every scorer, noifc_flags, frommain, which holdstools/re/bodies_across_saves.py;mainat e3cc717 is perf(reader): the definitions pass, the wall records read and the catalog's document build only the fields they read; the preamble does less (#1335) #1483 landed) measures the pushed head, 2e5bc4c, which mergesmainafter perf(reader): the definitions pass, the wall records read and the catalog's document build only the fields they read; the preamble does less (#1335) #1483 and is not yet in.Peak memory: the largest rise is 1.4 MiB, on 2015
rst_basic(the medians of three runs are 81,784 and 83,216 KiB, 79.9 and 81.3 MiB; the three runs of the base alone span 81,740 to 83,120 KiB and those of the head 80,920 to 83,352, so the rise is inside the run's own noise), the mean change over the 39 files is +0.11 MiB, and no file rises by 2 MiB. The profile estimated 3.1% of the export time for building plans; 2.6% is measured.Audit-honesty checklist
changelog.d/changed/.Breaking changes
None to the stable API, the CLIs or any export.
rvt::research(unstable):native_parametersgainsPlanCache(withwith_limit,entries,costandclears),PLAN_CACHE_COST,PLAN_ENTRY_COSTanddecode_graph_cached.Related issues
Refs #1335.