Repository navigation
perf(join): retain left columns for identity output mappings #943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
aff43d9
perf(join): retain left columns for identity output mappings
vbmithr 6217b31
fix(join): gather slice columns in identity left joins
vbmithr fa2092c
Merge branch 'dev' into perf/identity-left-join
morsel-ray e8a2e56
Merge branch 'dev' into perf/identity-left-join
morsel-ray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Identity left-join gather | ||
|
|
||
| The flat LEFT join executor retains left columns when the completed left | ||
| index mapping is exactly `[0, ..., left_rows - 1]`. It avoids allocating | ||
| and gathering unchanged columns and preserves their null bitmaps, string | ||
| pools and symbol domains. Updates retain the existing copy-on-write | ||
| semantics. Duplicate expansion and reordered output use the gather path; | ||
| FULL joins also keep that path because right-only rows change the left side. | ||
| Slice-valued left columns are gathered independently even with an identity | ||
| mapping: slice-header COW does not detach their parent. Other columns in the | ||
| same table can still be retained. | ||
|
|
||
| ## Standalone probe | ||
|
|
||
| Build separate release binaries from the base and candidate, then run each | ||
| from this checkout: | ||
|
|
||
| ```sh | ||
| RAYFORCE_CORES=4 /path/to/base bench/join_identity/probe.rfl | ||
| RAYFORCE_CORES=4 /path/to/candidate bench/join_identity/probe.rfl | ||
| ``` | ||
|
|
||
| The probe builds one million rows with eight left columns and three unique | ||
| right keys. It measures one join, excluding input construction, and checks | ||
| the left row order, the last payload column, right payload sum and unmatched | ||
| null count. Each process prints `BENCH|milliseconds|valid|rows`. Compare fresh | ||
| processes in randomized order after discarded warm-ups, with identical build | ||
| flags and worker settings. Whole-process timing includes construction and | ||
| validation; use the printed join timing when comparing the executor itself. | ||
| The probe checks values; C ownership tests verify that sharing actually occurs. | ||
|
|
||
| ## Backtester measurement | ||
|
|
||
| Matched local release builds based on `d7153e72`, Clang 23.1.3, default | ||
| `make release` flags; the candidate changes only `join.c`. Both variants | ||
| use the same projected backtester report. The workload has 100 instruments, | ||
| 26,304 hourly steps, 2,630,400 bars and 705 fills, using xsmomf. The default | ||
| worker configuration is unchanged between variants. | ||
|
|
||
| One discarded warm-up per variant, then three fresh processes each in | ||
| shuffled order (seed 22), without concurrent local compilation or tests: | ||
|
|
||
| | Median | Base | Candidate | | ||
| |---|---:|---:| | ||
| | Post-run checks | 1146.46 ms | 1047.97 ms | | ||
| | Whole process | 4.137 s | 4.022 s | | ||
| | Peak RSS | 1608.2 MiB | 1590.1 MiB | | ||
|
|
||
| These measurements include the slice fallback review correction. | ||
| Check ranges: base 1134.08–1163.55 ms, candidate 1043.04–1051.07 ms. | ||
| Process ranges: base 4.074–4.326 s, candidate 4.011–4.052 s. | ||
| This small experiment on a shared host is evidence for this workload, | ||
| not a general speedup guarantee. The backtester and its generated data are | ||
| external to this repository; the standalone probe above needs neither. | ||
|
|
||
| ## Regression coverage | ||
|
|
||
| `test/test_join_buildside.c` checks pointer sharing, copy-on-write, null | ||
| metadata, string-pool lifetime after releasing the source, duplicate | ||
| expansion and a large order-preserving join. The large sharing fixture keeps | ||
| its build side below radix dispatch. | ||
| A separate equal-cardinality fixture takes the radix route and verifies | ||
| that output really is reordered, every left/right payload remains matched, | ||
| and reordered columns are gathered. The slice fixture mutates an owned | ||
| output and verifies the source slice, its parent and the original join | ||
| result retain their values, while an ordinary column still shares. | ||
| The slice regression fails on the original PR at the source-value assertion. | ||
| Deliberately removing the mapping guard fails the radix test at the | ||
| left/right payload-association assertion; restoring the guard passes. | ||
|
|
||
| `test/rfl/join/join_identity.rfl` checks nulls, symbols, nested lists, | ||
| source/result update isolation, missing matches, duplicate expansion and | ||
| FULL joins. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| ;; One measured join per process; input construction is outside timeit. | ||
| (set n 1000000) | ||
| (set ids (til n)) | ||
| (set left (table [k a b c d e f g] | ||
| (list ids (+ ids 1) (+ ids 2) (+ ids 3) | ||
| (+ ids 4) (+ ids 5) (+ ids 6) (+ ids 7)))) | ||
| (set right (table [k mark] (list [0 500000 999999] [10 20 30]))) | ||
| (set elapsed_ms (timeit (set joined (left-join [k] left right)))) | ||
| (set valid (and (== (count joined) n) | ||
| (all (== (at joined 'k) ids)) | ||
| (all (== (at joined 'g) (+ ids 7))) | ||
| (== (sum (at joined 'mark)) 60) | ||
| (== (sum (map nil? (at joined 'mark))) (- n 3)))) | ||
| (if (not valid) (raise "identity-join: incorrect result")) | ||
| (println (format "BENCH|%|%|%" elapsed_ms (as 'I64 valid) (count joined))) |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| ;; One-to-one LEFT joins preserve left values and mutation isolation. | ||
| (set identity_left (table [k v s nested] (list [1 2 3] [10 0Nl 30] [z a b] (list [1 2] [3] [4 5])))) | ||
| (set identity_right (table [k r] (list [1 3] [100 300]))) | ||
| (set identity_join (left-join [k] identity_left identity_right)) | ||
| (at identity_join 'v) -- [10 0Nl 30] | ||
| (at identity_join 's) -- [z a b] | ||
| (at identity_join 'nested) -- (list [1 2] [3] [4 5]) | ||
| (set identity_changed (update {v: 99 where: (== k 1) from: identity_join})) | ||
| (at identity_left 'v) -- [10 0Nl 30] | ||
| (at identity_join 'v) -- [10 0Nl 30] | ||
| (at identity_changed 'v) -- [99 0Nl 30] | ||
| (set identity_left (update {v: 77 where: (== k 3) from: identity_left})) | ||
| (at identity_join 'v) -- [10 0Nl 30] | ||
| (at identity_left 'v) -- [10 0Nl 77] | ||
| ;; All unmatched rows still preserve the left side. | ||
| (count (left-join [k] identity_left (table [k r] (list [8] [80])))) -- 3 | ||
| ;; Duplicate matches must expand and gather the left side. | ||
| (at (left-join [k] identity_left (table [k r] (list [1 1 3] [4 5 6]))) 'k) -- [1 1 2 3] | ||
| ;; FULL joins must allocate, since right-only rows change the left side. | ||
| (count (full-join [k] identity_left (table [k r] (list [1 8] [4 5])))) -- 4 |
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.