perf(gfql): SNB point-query levers — index-hit verify, numpy hop tail, row-call trims, no edge-index scaffold for row calls - #2070
Conversation
…thout re-filtering `_seed_node_rows` re-applied the canonical filter to the rows the node-id or node-property index had already gathered. For a filter that is exactly one scalar equality on the served column the gathered rows are that filter's rows (integer keys, membership verified after the probe, mismatched value families declined to the scan), so the re-filter is skipped on that shape only; every other shape keeps the canonical filter and its typed errors. Pinned on pandas and cuDF with parity to index_policy="off". Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…lumns in place `_tag_fast_path_aliases` attached the alias flags with assign, a full column reorder and reset_index on both frames (three copies each). On pandas, with integer ids and no colliding alias names, the flags are now numpy membership tests inserted into one reset copy; every other shape (binding column not first, colliding names, float or object ids, cuDF) keeps the generic path. Pinned frame-identical to the generic path for every alias shape and direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QztW7jYsDd66e8rb8pJNQA
…he hit rows instead of re-filtering The canonical filter costs about 0.3 ms of fixed overhead per call on a one-row frame; after an index hit the remaining scalar equalities are checked directly on the hit rows, with the same typed errors. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
…red rows with numpy array ops on pandas Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
… a one-row projection does not need Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
…ffolding Adding and dropping the internal edge index costs an O(E) column write per call and leaked the index column into an edge row table; row-table calls never read edge identity. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
… frame ops; no-scaffold call results drop the row context; lever pins join the polars lane Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
|
Real-GPU lane (dgx-spark, RAPIDS 26.02 image, safe_run) at b73cab4: 13830 pass / 7 fail. The 7 failure ids are identical to master's known set in that image (#2064 strictness cases + the routing-contract decline pin); zero branch-specific failures. Sentinel and SNB arm receipts are in the body; broad CPU+cuDF suites, tck-gfql and CI follow. |
…redicate, lane body, routes-off target, harness parity The harness then shows the polars GENERAL path dropping null-id rows and collapsing duplicate ids on node-only op lists where the lane and the pandas oracle keep them (#2071, #2034); ledgered as known divergences of that route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1
Receipts at 40edaf6
Polars point latency on dgx is thread fan-out (sentinel, same head, same window, ms)
On the 20-core ARM box the per-call cost of the polars point shapes is the thread pool, not the query: at one thread they land within 1.4× of pandas, and the bulk shape gets slower as expected. Polars has no per-collect thread control, so this PR does not change polars behavior; it records the finding. Consequence for the board: pandas is the point-query engine and polars the bulk engine, which is what the engines page already recommends. |
|
CI on 40edaf6: 84/84 green. Ready for review: all receipts on this PR (broad CPU+cuDF 13253/0, tck 4143/689 xfail, real-GPU lane = master's known set, sentinel gate ok with pandas points 8–28% faster, SNB arm table, polars thread experiment). |
| polars equivalent expr is ``pl.col(source).fill_null(False).cast(pl.Boolean)``). | ||
| Shared by ``rows``/``count_table`` so the null handling can't diverge.""" | ||
| mask = table_df[source] | ||
| if str(getattr(mask, "dtype", "")) == "bool": |
There was a problem hiding this comment.
can we move away from getattr in all these? audit, fix..
| pandas frames with numeric id columns; None keeps the engine-generic Series path.""" | ||
| import numpy as np | ||
| import pandas as pd | ||
| if not isinstance(cand, pd.DataFrame) or not isinstance(edges, pd.DataFrame): |
There was a problem hiding this comment.
does cudf need this?
| g_out = self.nodes(g_out._nodes).edges(final_edges_df, edge=original_edge) | ||
| else: | ||
| from .gfql.exec_context import clear_row_exec_context | ||
| g_out = clear_row_exec_context(g_out) # the row context must not escape on a result |
There was a problem hiding this comment.
most of the comments in this pr seems unnecessary, better handled by better variable naming, adding tests for the intended behavior, or when straight up redundant, just delete
| return res.nodes(nodes).edges(edges) | ||
|
|
||
|
|
||
| def _tag_fast_path_aliases_pandas( |
There was a problem hiding this comment.
x-platform for cudf?
lmeyerov
left a comment
There was a problem hiding this comment.
feels brittle
- sufficient positive/negative tests on either side of boundaries?
- saw some dynamic typing
- looked like missing cross-platform support & testing, eg, cudf
- I have to wonder about test amplification in general, and true generlaization...
Objective
GFQL's SNB-derived point queries (seed lookup, message creator, message content) lose to the indexed graph databases. This PR removes the per-call fixed costs the profile named; it changes no result.
What changed (each its own commit, pins both sides)
rows()/selecton pandas skip the copies and null handling a one-row projection does not need (bool marker mask short-circuit; no copy before a mask filter; the projection frame built directly; zero-row edge slice without copy).__gfql_edge_index__column on an edge row table.Numbers (dgx-spark, SNB indexed pandas arms, median of three runs, ms; competitor cells from the published board)
W/T/L on the point queries, pandas-idx: vs Neo4j SF0.1 3/0/0, SF1 1/2/0 (was 1/1/2 and 1/0/2); vs Kuzu 0/0/3 at both scales (0.62–0.88×, up from 0.40–0.63×); vs Memgraph 0/0/3 (0.16–0.43×). Polars arms unchanged: the levers are pandas-side.
Sentinel at 7eec9cc: gate ok against the master baseline; pandas points 8–28% faster (node-only-props 0.57 → 0.41 ms).
What this does not do, and why
The remaining 1–3 ms per call is orchestration: two chain entries per op list, the call executor, and Plottable rebinds. Memgraph answers these in 0.3–0.6 ms. Reaching that needs a dedicated point-lookup route (index gather → row table → projection in one pass) and, for seed_lookup, a one-call arm; both are follow-ups under the same objective. The polars point shapes decline the seeded lane and take the full chain; they need their own pass.
Receipts
Posted below as they land: broad CPU+cuDF suites, tck-gfql, real-GPU lane (RAPIDS 26.02), CI.
🤖 Generated with Claude Code
https://claude.ai/code/session_01HdYcPgiafGGutW9KfG3gg1