perf(ir): a map read-modify-write is one probe (P9) #411
Loading…
Reference in a new issue
No description provided.
Delete branch "loop/56-map-takeout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #56
Task
PERFORMANCE.md round P9 / finding 10: a write through
map[key]on a concrete map probes the table once (torb_map_slot) instead of twice, in both back ends. Cut to its core by the loop;Map.getOrInsert/Map.updateon top of the slot stay out of scope.What
runtime/map.c:torb_map_slot(map, key), the address of the value's slot, valid until the next write to the table; the table is made unique first,NULLfor a missing key. VM kernel operationmap.slot.Instruction.TakeOutcarries an optionalMapMiss: theIndex.atthe round trip began with, called only where the probe finds nothing, so a missing key panics with the same message and site.ir/maps.trb(new pass afterir/elements.trb):map[key].f = von aMapStoragecontainer that is a whole slot becomes onetakeOutand oneputBack. Refused: a container with steps (field, box), anything else naming the map in between, a suspension.void *local. Bytecode emitter: a register behindmap.slot; the tworeportsites are gone.sites.trb,called-members.trbknow the miss call.Test
compiler/tests/maps.test.trb: IR snapshots (one takeOut/putBack, nested, read on the right-hand side, plain assignment unchanged, field container and boxed map keep the round trip).bytecode.test.trb: the chunk.ir.test.trb: verify and print with a miss call.exclusivity.test.trb: a write to the map from inside a call on one of its values is rejected.runtime/tests/map_test.c: the slot.map-take-out(fields, nested path, counted values with copy on write against a shared map, a narrow field) andmap-take-out-missing(panic, exit 101, site).benchmarks/map-writeis new (the path write);tools/map-cost.shruns it andmap-countwithbenchmarks/run.sh(RUNS=9, allocations) for the compiler of the merge base and of the branch, at the end of tier B.Two-commit rule
Commit 1 (runtime,
MapMiss, verifier, bytecode emitter and VM, IR-level tests) is reached by nothing. Commit 2 holds the pass, the C emission, the snapshot, conformance, benchmark and docs. No seed refresh: no new syntax and no std name looked up by string; the seed stays as it is.9cd509fdab7751ef2c8eReview of head
7751ef2c. CI is green for this head (tier A/B, bootstrap, gates, pdisc), but the PR cannot be merged as it stands.Findings
mainmoved (merge base5f075c84, main now301f2149, 18 commits, among them the decimal-literal work) and the Forgejo mergeability check reports a conflict. Files changed on both sides:.pdisc-sidecar.yaml,compiler/src/backend/bytecode/emit.trb,compiler/src/backend/bytecode/format.trb,compiler/src/backend/c/emit.trb,compiler/src/ir/ir.trb,compiler/src/ir/print.trb,docs/PERFORMANCE.md,runtime/machine.c. Please rebase both commits onto currentmain, keep the two-commit rule, regenerateINDEX.md/sidecar and re-run CI.Scope of this review
The rebase touches the instruction set (
ir.trb,format.trb,emit.trb,machine.c) that the newTakeOut/MapMissandmap.slotcode builds on, so the head will change. I did not do a line-by-line review of the pass on this head; the correctness review ofir/maps.trb, the C/bytecode emission and the benchmark numbers follows on the rebased head. From the PR body the scope matches issue #56 (P9, noMap.getOrInsert/Map.update), tests and conformance cases are present for both back ends, and nothing here needs a decision from Torben.7751ef2c8e0f2ef1501d0f2ef1501dfbe244b671Review of head
fbe244b6(rebased onto main301f2149, mergeable, two commits as the two-commit rule says). CI is green for this head (tier A/B, bootstrap, gates, pdisc, same-C check, playground). My earlier finding (conflict withmain) is resolved.Checklist
maps.test.trb: one takeOut/putBack, nested, RHS read, plain assignment, boxed map and field container keep the round trip), bytecode chunk, verifier/printer with a miss call (including the arity error), exclusivity pin,torb_map_slotruntime test, conformancemap-take-out(counted values with copy on write against a shared map, narrow field) andmap-take-out-missing(panic message, site, exit 101).maps.trbproves what it needs before rewriting: the container is a whole slot ofMapStorage, receiver and element are confined to the access in one block, container and key are undisturbed, nothing else names the map between take-out and put-back, no suspension. The miss path calls the originalIndex.atat the original site in both back ends,sites.trb/called-members.trb/callsProgramCodeknow the miss call, andtorb_map_slotmakes the table unique before the probe. The cursor shared per (container, key) cannot overlap because the accesses are block-local and sequential.getOrInsert/updatestay out. No crons or other automation. The new CI step is a measurement (tools/map-cost.shexits 0 and haspermissionsvia the existing job).Findings
docs/PERFORMANCE.md(new paragraph after "What it refuses, and why that is the exclusivity rule's") ends with the literal placeholder**Measured.** @@MEASURED@@. That is the one acceptance item of #56 that is not met: "The named benchmark or snapshot recorded before and after". The PR adds the means (benchmarks/map-write,tools/map-cost.sh, CI step) but records no result, and a template marker would land inmainand in the status line that now says P1 to P10 are done. Please replace the marker with the numberstools/map-cost.shprints for this head (base vs head:map-countandmap-writetimes and allocated blocks, runner named), amend commit 2 and let CI run again. If you prefer to keep the numbers out of the doc, say in the PR body where they are recorded and drop the marker; either way no@@...@@may reachmain.Non-blocking
runtime/include/torb.h(torb_map_slot): the comment says a caller that takes the value out "leaves zero bytes in the slot, astorb_map_take_outdoes". Neither back end zeroes the slot (the value stays there, the target owns its count, and the put-back overwrites it), which is whatPERFORMANCE.mdand the emitter docs say. Align the header comment with the actual contract (value stays, caller must store before any other table access), since a future caller oftorb_map_slotwould rely on it.fbe244b671d3253f09c7d3253f09c7d4208184ecHead
d4208184(commit 2 amended, two-commit rule kept, commit 1 unchanged).@@MEASURED@@replaced indocs/PERFORMANCE.mdwith the numberstools/map-cost.shprinted in tier B of run 1397 (linux-x64 runner, gcc 13.3.0 -O2, fastest of 9, base vs head):map-count170946 -> 168933 us,map-write317925 -> 314818 us, allocated blocks unchanged (50046 / 100047). Recorded as it is: about 1 % faster, inside runner noise, so the second probe was not where the time ofmap-writegoes. No@@...@@remains in the tree.torb_map_slotheader comment aligned with the contract: the value stays in the slot, the caller owns its count and must store before any other table access.The head before this one (
d3253f09) only added the emitted-C counts totools/map-cost.sh; it is part of this head.Re-review of head
d4208184(commit 2 amended, commit 1 unchanged, two-commit rule kept). CI is green for exactly this head (tier A/B, bootstrap, gates, pdisc, same-C check, playground).Earlier findings
@@MEASURED@@placeholder: resolved.docs/PERFORMANCE.mdnow records base vs head from tier B (map-count 170946 -> 168933 us, map-write 317925 -> 314818 us, blocks unchanged), runner named, with an honest reading (about 1 %, inside noise). No@@...@@remains in the diff. The acceptance item "benchmark recorded before and after" of #56 is met.torb_map_slotheader comment: resolved. It now states the real contract (value stays in the slot, caller owns its count and must store before any other table access).Checklist
tools/map-cost.sh(a measurement that exits 0, not a gate).Approved; merging by rebase.