Repository navigation
Proxy join: columnar bridges and block matches, no owned times on the common path - #1069
Draft
frankmcsherry wants to merge 4 commits into
Draft
frankmcsherry wants to merge 4 commits into
frankmcsherry wants to merge 4 commits into
Conversation
… common path The proxy join's bridges become columns (`JoinBridge`), with times in a backend-chosen `TimeColumn`, and matches become blocks of row ranges: block (r0, r1) is every row of r0 with every row of r1, at the join of their times and the product of their diffs. The backend forms output times and diffs from the blocks it keeps (`extend_cross_join`, `extend_cross_product`). The direct path pushes one block per key. The long-history replay keeps its owned-time `ValueHistory`, appending each step's rows to the bridges; it and the as-of wrapper use the scaffolding exits `owned_get`/`owned_push`, to delete as column operations replace them. DDIR stages the join on lanes (copy ranges, join with `lower` per lane, sort positions to consolidate) and implements `TimeColumn` for `ColTimes`; the half join keeps its row staging. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Single-chunk runs copy their times and diffs whole, with tokens written per value run, and `lower` is joined without allocating a frontier per key. - `cross` fills only the token columns the projection reads, and output time columns drop coordinates no row carries, as `ColTimes` promises. - Output time lanes and diffs grow as rows are added instead of being reserved at each cut's full row count, which had raised reachability's peak footprint by 31 MiB with the same live data. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…in place Each side of a key is appended directly to its bridge, with its times joined with `lower` as the lanes are copied, and consolidated in place there: a side already in (token, time) order with no zero diff is left alone, and one out of order sorts a (token, first two time coordinates, position) record. A key with an empty side is truncated off both bridges. This removes the copy from a per-key scratch into the bridge. Sorting each whole block once instead was measured and dropped: most keys are already in order and need nothing, while a block sort touches every row (LDBC 9 s slower). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…h the rest of the block Matching found each key's run with partition_point over the remaining block, about fourteen probes per side for a key of one row. Lookup joins have a row per key per side, as IS2 mine's second and third joins do, at 12,900 keys a tick. On a synthetic lookup of 13,000 keys, matching took 24.2 ns a key against 9.4 on master-next, and takes 3.4 with the scan. Runs of r0 and r1 rows make r0 * r1 >= r0 + r1 - 1 matches, so the scan costs at most a comparison per match, plus one. On one LDBC one-off replay each, back to back on mini-02, IS2 mine goes from 6.43 to 6.06 ms a tick (master-next: 5.75), and the whole run from 1877.3 to 1869.4 G instructions; answers identical. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H6wB4H99TLw4tqu4x48pXL
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.
The proxy join stops building an owned timestamp per staged row and per match. Bridges become columns, with times in a column type the backend chooses, and matches become blocks of row ranges. On DDIR this is LDBC SF1 −9.5% CPU (−12.1% instructions), SCC's first tick −9.3% and reachability's first tick −22.3%, with identical answers.
What changes
DD (
int_proxy)TimeColumn<T>:len,clear,extend_cross_join(a, b, blocks), plus two scaffolding exits,owned_get/owned_push, to delete once nothing calls them. No default bodies;Vec<T>implements it.JoinBridge<C, R, K, V>: keys, values, times (C) and diffs as columns.ProxyBridgestays for the reduce and the half join.JoinBlocks, pairs of row ranges: block(r0, r1)is every row ofr0with every row ofr1, att0[i] ∨ t1[j]with diffd0[i] · d1[j].JoinMatchesand its per-matchids/timesgo.ProxyJoinBackendgainstype Times.advancefills twoJoinBridges, andcrossreceives both bridges and the blocks. The backend forms times and diffs from the blocks it keeps, after any filtering.ValueHistoryand appends each step's edit and buffer rows to the bridges, so its matches are blocks too. That's correct because the meet a buffered time was advanced by is at or below the edit's time.DDIR (
interactive)ColTimesimplementsTimeColumn: a max per coordinate, per block.join/lanes.rs). Each key is appended straight into the bridges, withlowerjoined while copying. Each side is consolidated in place: a side already in (token, time) order is left alone, and one out of order sorts a compact record. A key with an empty side is truncated off both bridges.crossworks from blocks:TARGET_OUTrows;Commits
mine's second and third joins see about 12,900 such keys a tick, andminewent from 5.75 ms/tick ona6b7922ato 6.43. The scan costs at most one comparison per match, plus one, and bringsmineto 6.06. The remaining 0.30 ms/tick is per-key work incrossand staging. On one replay each, the whole LDBC run goes from 1877.3 to 1869.4 G instructions, with identical answers.Measurements
One worker, mini-02. Medians of three alternating trials against
a6b7922a, on commits 1–3.Answers were identical in every trial: all 808 LDBC answer digests and physical digests, and the SCC and reachability results at the start and end of churn.
cross11.9 → 10.3 s and staging 28.9 → 27.0 s. Owned-time work in the join drops from 14.1 ms to 0.001 ms per tick.Scaffolding left
Callers of
owned_get/owned_push, none in DDIR:Known rough edge
About 150 lines of
join/lanes.rsrepeat the row staging's token minting, because the half join still stages rows. They go away when the half join moves to lanes.Tests
cargo test -p differential-dataflow -p differential-dogs3 -p interactive: 459 passed, 0 failed.join_matches_direct_cross_product. It runs the whole tactic over DDIR's backend against a direct cross product, covering the replay path, cancellation on load, times of one and four coordinates, primitive and compound value tokens, and cut output containers.Implemented by Claude (Opus 5.5) agents in background sessions; squashed, checked and opened by Claude Code.
🤖 Generated with Claude Code