Commit ebfa720
perf(gfql/polars): make the chain combine proportional to the traversal result
Two graph-sized terms sat inside a combine whose answer is a handful of rows.
1. `_combine_edges` ran the prev/next endpoint gates for EVERY step, including the
node steps whose edge frame is `g._edges.clear()` -- zero rows. The eager combine
skipped those; the collect-once (Track B) rewrite lazified the step frames and
`.lazy()` erases the height, so the skip silently went dead. The cost lands on the
side that is NOT empty: for the first step the gate's key side is the whole node
table, and polars builds the hash table on that side before discovering the probe
side has no rows. Isolated in raw polars: 6.99 ms for one such join at N=2M vs
0.22 ms against a one-row key side -- and a chain pays one per node step.
`_LazyShim.step` now records the row count while the frame is still eager and an
empty step is dropped from the id union. It can contribute no ids, so the result is
unchanged by construction. The skip keys on KNOWN-empty only: a frame that arrives
already lazy reports no height and is planned normally.
2. The output node rows were materialized in TWO passes over the node table -- one for
the ids the steps kept, one more for the surviving edges' endpoints the first pass
missed -- then concatenated. The output node set is the UNION of those two id sides,
so `_materialize_node_rows` unions the ids first and reads the node table ONCE. The
row-level `unique(subset=[node])` is preserved verbatim: those rows go on to feed
`how="left"` alias joins where a node table carrying the same id twice multiplies
rows. The id sides themselves are NOT deduplicated -- they are semi key sides.
Note for the record: the recorded description of this residual ("UNIQUE over a UNION of
the per-step FULL frames") named the wrong operator. The union inputs are 1-row step
frames; the `unique` costs 0.008 ms. It was the union BRANCHES -- empty-probe joins
against graph-sized build sides -- exactly as the earlier semi-dedup finding.
MEASURED, one dimension at a time, synthetic LDBC-IS5-shaped graphs (one-row answer,
polars-engine resident indexes, local CPU, min of 9):
vary NODES at E=2M 250k -> 4M: 10.17 -> 45.48 ms before
7.60 -> 17.17 ms after (2.65x at 4M)
node-count slope 3.7x flatter (+35.3 ms -> +9.6 ms over 16x N)
vary EDGES at N=1M 500k -> 8M: 13.47 -> 26.02 ms before
7.08 -> 17.57 ms after
edge slope essentially unchanged, as expected for a node-side fix; constant ~6 ms lower
same 2-3x holds for n-e-n, n-e-n-e-n and undirected shapes
PARITY: 280 shape x graph combinations (clean / self-loop / dangling / duplicate keys /
multi-edge / back-edge / isolated) and 400 duplicate-node-id combinations x 5 repeats
compared as FULL frames including row order -- 0 diffs vs the pre-change tree, and
stable across repeated runs. `graphistry/tests/compute/` failure set is byte-identical
before and after (119 pre-existing failures, 0 new, +119 passed = the new file).
TESTS pin the boundary, not a wall clock. Mutation-checked; each row is a deliberate
reintroduction of a bug, run against the new file:
drop the empty-step skip -> 2 fail (both structural tests)
restore the two-pass materialization -> 1 fail (node table read 2x)
drop the row-level node dedup -> 18 fail (parity + dedup + order)
drop the endpoint fold -> 2 fail
drop the node order restore -> 34 fail
skip steps whose height is UNKNOWN -> 1 fail
NOT caught, and reported as such: dropping the edge order restore, and dropping
`maintain_order` on the node dedup. Neither is observable -- the edge semi-join returns
EORD order naturally at 5k/60k/300k across 5 shapes, and the node semi-join feeding the
dedup is unordered either way. Both are pre-existing defensive code, untouched here.
Also honest about coverage: the endpoint fold could not be shown load-bearing
end-to-end (6300 generated chain executions found no graph/shape where the step ids miss
an edge endpoint), so it is tested at the helper, where the case can be constructed --
an end-to-end test of it would have passed with the endpoint side removed entirely.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VARwnAqmvczhz5EmP7TYyZ1 parent 7d508ab commit ebfa720
3 files changed
Lines changed: 463 additions & 19 deletions
File tree
- graphistry
- compute/gfql/lazy/engine/polars
- tests/compute/gfql
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
222 | | - | |
223 | | - | |
224 | | - | |
225 | | - | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
226 | 235 | | |
227 | 236 | | |
228 | 237 | | |
229 | 238 | | |
230 | 239 | | |
231 | 240 | | |
| 241 | + | |
232 | 242 | | |
233 | 243 | | |
234 | 244 | | |
235 | 245 | | |
236 | 246 | | |
237 | | - | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
238 | 256 | | |
239 | 257 | | |
240 | 258 | | |
| |||
247 | 265 | | |
248 | 266 | | |
249 | 267 | | |
250 | | - | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
251 | 278 | | |
252 | 279 | | |
253 | 280 | | |
| |||
287 | 314 | | |
288 | 315 | | |
289 | 316 | | |
290 | | - | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
291 | 329 | | |
292 | 330 | | |
293 | 331 | | |
| |||
296 | 334 | | |
297 | 335 | | |
298 | 336 | | |
299 | | - | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
304 | 366 | | |
305 | 367 | | |
306 | 368 | | |
| |||
953 | 1015 | | |
954 | 1016 | | |
955 | 1017 | | |
956 | | - | |
| 1018 | + | |
957 | 1019 | | |
958 | | - | |
959 | | - | |
960 | | - | |
961 | | - | |
962 | | - | |
963 | | - | |
| 1020 | + | |
| 1021 | + | |
964 | 1022 | | |
965 | 1023 | | |
966 | 1024 | | |
| |||
0 commit comments