Commit efe17f2
perf(gfql): serve NAMED patterns on the native chain fast path
`_try_chain_fast_path` rejected ANY op carrying an alias, so a named
`g.gfql([n(name='x'), e_forward(name='r'), n(name='y')])` fell to the full
two-pass BFS purely because the ops were named. Naming is a PROJECTION concern,
not a traversal one; it should not decide which engine path runs.
RE-MEASURED on a 200-node / 800-edge pandas graph (data work ~0, so this is the
per-query plan floor). Five ALTERNATING PAIRED runs vs origin/master 233b64c,
each value the median of 200 reps after 20 warmups (range over the five runs in
parentheses -- the run-to-run spread is real):
named 3-op traversal 25.2 ms (24.2-27.0) -> 2.3 ms (2.1-2.7) ~11x
the same + rows(binding_ops) 40.4 (38.1-43.6) -> 17.4 (16.8-19.8) ~2.3x
The rows() output is byte-identical between arms; the traversal output is
value-identical (dtypes deliberately changed, below).
SCOPE, AND THE EARLIER CLAIM WAS WRONG. The previous description said "every
Cypher multi-alias MATCH...RETURN names its ops, so the entire Cypher surface is
locked out". An instrumented run of the graph-benchmark q1-q9 lane at 20k, both
engines, both arms, falsifies that three independent ways: every consult on that
lane is ops=1 and this change only edits the len(ops)==3 branch; g.gfql(<cypher>)
for those shapes is served earlier by gfql_fast_paths.py
(_execute_single_hop_grouped_aggregate_fast_path, _execute_two_hop_count_fast_path,
_execute_seeded_typed_hop_fast_path) and never reaches here; and this path is
pandas/cuDF only while the losing column is polars. Zero consults on q1/q2/q3/q4/
q8/q9 pandas and on all of q1-q9 polars, identical on both arms. NO BENCHMARK CELL
MOVES. A positive control confirms the lever is live rather than inert (arm A
DECLINE at the removed gate, arm B SERVE, same native 3-op named chain).
Under the pyg-bench-proof-plus-lock-in rule this therefore carries NO perf claim
on the board; the lane is specified as follow-up in the PR description.
HOW THE TAGS ARE DERIVED: from the KEPT EDGES the fast path returns.
`combine_steps` tags a node with an alias iff it still participates in a surviving
edge, and those edges are exactly the surviving ones, so `isin` over their endpoint
columns is the same predicate without the join. A seed whose edges all fail the
filter yields an empty edge frame and is tagged False -- the dead-end case, and why
the tag keys on edges rather than on the node filter.
DECLINES KEPT DELIBERATELY: undirected + named (alias identity is not derivable
from endpoint columns there); duplicate alias names (E201 lives in `combine_steps`,
so serving them would BYPASS the check and silently succeed); and a resident index
that would actually serve the shape (`_resident_seed_indexes` asks about index
VALIDITY -- a registry-presence check declines on every query and makes the whole
thing a measured no-op).
CODE LOCATION: `_tag_fast_path_aliases` lives in `chain_fast_paths.py`, not
`chain.py`. That is the pre-existing direction, confirmed not recalled: the module
was created by 289c6ab "extract seeded fast-path specializations to dedicated
modules (#1755)" and its docstring states the one-way import rule. The docstring
now records it explicitly so the next helper does not land in chain.py again.
DTYPES: ADOPT the conformant ones. The pandas full path upcasts non-id int64 ->
float64 and bool -> object through its rows-pivot merges. Verified rather than
assumed that this is an ARTIFACT: openCypher TCK
clauses/return/Return2.feature [2] expects `1` for an integer node property (its
value grammar distinguishes Integer from Float); Neo4j treats INTEGER and FLOAT as
distinct property types and reports valueType()="INTEGER NOT NULL"; and this
library's own polars and cuDF canonical paths already return int64/bool for the
same query, so only the pandas merge upcasts. The fast path keeps int64/bool.
User-visible where a named pandas pattern is served: 30 rather than 30.0, and a
bool alias flag rather than object. Values unchanged. NOT aligned yet, deliberately
out of scope: the pandas full path and the Cypher-layer projection still emit the
artifact.
TESTS: 4 deliberate rewrites, each commented with the rule it now encodes.
test_fast_path_gating_returns_none_for_ineligible and the _FAST_SHAPES/_BYPASS_SHAPES
differential move named shapes from decline to serve and add the new declines;
test_pandas_datetime_property_declines and test_engine_mismatch_declines keep
asserting the Cypher-layer decline and identical values, with the dtype rule now
asserted explicitly instead of incidentally. NEW coverage for the new capability:
test_fast_path_named_alias_columns_match_full_path (9 shapes x 2 engines) compares
the alias flag COLUMNS and per-row values fast-vs-full, which the id-set differential
cannot see, and is serve-asserted.
CHANGELOG added (Performance + Changed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB1 parent 49db91c commit efe17f2
6 files changed
Lines changed: 280 additions & 16 deletions
File tree
- graphistry
- compute
- tests/compute
- gfql
- index
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
822 | 822 | | |
823 | 823 | | |
824 | 824 | | |
825 | | - | |
826 | | - | |
827 | | - | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
828 | 830 | | |
829 | 831 | | |
830 | 832 | | |
| |||
852 | 854 | | |
853 | 855 | | |
854 | 856 | | |
855 | | - | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
856 | 871 | | |
857 | | - | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
858 | 875 | | |
859 | 876 | | |
860 | 877 | | |
861 | | - | |
| 878 | + | |
862 | 879 | | |
863 | 880 | | |
864 | 881 | | |
| |||
868 | 885 | | |
869 | 886 | | |
870 | 887 | | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
871 | 893 | | |
872 | 894 | | |
873 | 895 | | |
| |||
877 | 899 | | |
878 | 900 | | |
879 | 901 | | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
880 | 916 | | |
881 | 917 | | |
882 | 918 | | |
| |||
902 | 938 | | |
903 | 939 | | |
904 | 940 | | |
905 | | - | |
| 941 | + | |
| 942 | + | |
906 | 943 | | |
907 | 944 | | |
908 | 945 | | |
| |||
935 | 972 | | |
936 | 973 | | |
937 | 974 | | |
938 | | - | |
| 975 | + | |
| 976 | + | |
939 | 977 | | |
940 | 978 | | |
941 | 979 | | |
942 | 980 | | |
943 | 981 | | |
944 | 982 | | |
945 | 983 | | |
946 | | - | |
| 984 | + | |
| 985 | + | |
947 | 986 | | |
948 | 987 | | |
949 | 988 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
7 | 11 | | |
8 | 12 | | |
9 | 13 | | |
| |||
18 | 22 | | |
19 | 23 | | |
20 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
21 | 72 | | |
22 | 73 | | |
23 | 74 | | |
| |||
0 commit comments