Skip to content

perf(gfql): dtype-gate temporal-text detection to avoid spurious stringification (#1650)#1651

Closed
lmeyerov wants to merge 1 commit into
masterfrom
dev/gfql-1650-entity-text-perf
Closed

perf(gfql): dtype-gate temporal-text detection to avoid spurious stringification (#1650)#1651
lmeyerov wants to merge 1 commit into
masterfrom
dev/gfql-1650-entity-text-perf

Conversation

@lmeyerov

Copy link
Copy Markdown
Contributor

Summary

Fixes the spurious-stringification half of #1650. GFQL ran row-wise entity/temporal stringification in comparison paths whose output never contains entity-text (e.g. where_rows). Root cause: order_detect_temporal_mode (ordering.py) runs astype(str) + up to 6 regex fullmatch passes on both operands of every comparison — including int64/bool columns that can never hold temporal text. A 10k-row where_rows(val > 50), which returns a plain 3-column table, fired ~160,006 spurious re.fullmatch calls, all discarded.

Note: the where_rows cost is order_detect_temporal_mode, not entity_text.py/entity_props.py as the issue body speculated (cProfile-confirmed), and the issue's ~10× over-render does not reproduce — entity-text already renders on the deduped node table. This PR targets the confirmed spurious path; whole-entity RETURN a structured/Arrow returns are tracked separately.

Fix

order_detect_temporal_mode early-returns None when series.dtype.kind in {i, u, f, b, c} (numeric/bool/complex). Object/string/datetime columns are unaffected, so temporal-text detection is preserved.

Results (byte-identical output, gated == ungated)

where_rows(val>50) before after speedup
pandas @10k 48.9 ms 15.6 ms 3.1×
cuDF @10k 37.4 ms 8.5 ms 4.4×
cuDF @100k (1M edges) 191.0 ms 14.4 ms 13.3×

Speedup scales with row count (spurious scan is O(rows)). cuDF measured on a GB10 box with graphistry/test-gpu:latest (cudf 25.12).

Tests

  • pandas: temporal + ordering + row suites — 91 pass
  • cuDF (TEST_CUDF=1 container): temporal + ordering + row suites — 83 pass
  • Added: order_detect_temporal_mode unit coverage (numeric/bool skip; object temporal still detected) + end-to-end numeric where_rows filter assertion.

🤖 Generated with Claude Code

…ngification (#1650)

`order_detect_temporal_mode` ran `astype(str)` + up to 6 regex `fullmatch`
passes on BOTH operands of every comparison, including numeric/bool columns
that can never hold temporal *text*. This fired ~160k spurious `re.fullmatch`
on a 10k-row `where_rows(val > 50)` whose output is a plain table — pure waste.

Gate: early-return None when `dtype.kind in {i,u,f,b,c}`. Object/string/datetime
columns are unaffected, so temporal-text detection is preserved.

Byte-identical output. Measured `where_rows` speedups:
  - pandas @10k:  48.9 -> 15.6 ms (3.1x)
  - cuDF   @10k:  37.4 ->  8.5 ms (4.4x)
  - cuDF   @100k: 191.0 -> 14.4 ms (13.3x, 1M edges)

Tests: pandas 91 + cuDF 83 temporal/ordering/row suites pass; adds unit
coverage for the gate (numeric/bool skip, object temporal still detected) and
an end-to-end numeric `where_rows` filter assertion.

Scope: this is the spurious-path half of #1650. Whole-entity `RETURN a` text
rendering (structured/Arrow returns) is tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lmeyerov

Copy link
Copy Markdown
Contributor Author

Folded into the PR0 general-optimizations base (#1652, commit b539212c, original authorship preserved) so it sits beneath the polars-engine stack (#1648#1649). Validated there: full gfql suite 2883 passed, 0 failed. Superseded by #1652.

@lmeyerov lmeyerov closed this Jun 26, 2026
lmeyerov added a commit that referenced this pull request Jun 26, 2026
…on scans

Follows #1650/#1651 (which gated temporal-text detection). Profiling numeric
RETURN/ORDER BY queries @100k showed ~40–48% of the call was STILL spurious
`astype(str)` + regex on int/float/bool columns — not in temporal detection
(already gated) but in the LIST-detection + projection scans:
- order_detect_list_series / order_detect_stringified_list_series (ordering.py)
- RowPipelineMixin._gfql_series_is_list_like (pipeline.py)
- _project_property_column temporal scan (result_postprocess.py)

All run `series.astype(str)` to test for list/temporal *text* on every column,
including numeric/bool/complex columns that can never hold it. Gate them with a
shared `_is_non_listable_dtype` (kind in i,u,f,b,c) → early return False/series.
Byte-identical (the scans return False/None for these dtypes anyway); object/
string/datetime columns are untouched, preserving list/temporal detection.

Result (where the projection/order path runs over numeric cols): ORDER BY and
arithmetic-projection drop their ~50–100ms spurious string render to ~0.

+ tests: numeric/bool dtype pass-through; behavioral numeric ORDER BY (would fail
if values were lexically stringified). Base cypher/row code -> PR0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jun 28, 2026
The #1650/#1651 dtype gate (numeric/bool/complex columns skip the spurious
astype(str)+regex list/temporal scan) was duplicated across 4 sites with 3
different sentinel conventions ("O", None, factored). Consolidate the kind
set into is_non_textual_scalar_dtype() in series_str_compat.py (the engine-poly
Series-string layer these gates short-circuit) and call it from ordering.py
(x2), pipeline.py, and cypher/result_postprocess.py. Byte-identical; adds a
direct parametrized unit test for the helper across dtype kinds + None.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jun 28, 2026
The #1650/#1651 dtype gate (numeric/bool/complex columns skip the spurious
astype(str)+regex list/temporal scan) was duplicated across 4 sites with 3
different sentinel conventions ("O", None, factored). Consolidate the kind
set into is_non_textual_scalar_dtype() in series_str_compat.py (the engine-poly
Series-string layer these gates short-circuit) and call it from ordering.py
(x2), pipeline.py, and cypher/result_postprocess.py. Byte-identical; adds a
direct parametrized unit test for the helper across dtype kinds + None.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jun 29, 2026
The #1650/#1651 dtype gate (numeric/bool/complex columns skip the spurious
astype(str)+regex list/temporal scan) was duplicated across 4 sites with 3
different sentinel conventions ("O", None, factored). Consolidate the kind
set into is_non_textual_scalar_dtype() in series_str_compat.py (the engine-poly
Series-string layer these gates short-circuit) and call it from ordering.py
(x2), pipeline.py, and cypher/result_postprocess.py. Byte-identical; adds a
direct parametrized unit test for the helper across dtype kinds + None.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lmeyerov added a commit that referenced this pull request Jun 29, 2026
The #1650/#1651 dtype gate (numeric/bool/complex columns skip the spurious
astype(str)+regex list/temporal scan) was duplicated across 4 sites with 3
different sentinel conventions ("O", None, factored). Consolidate the kind
set into is_non_textual_scalar_dtype() in series_str_compat.py (the engine-poly
Series-string layer these gates short-circuit) and call it from ordering.py
(x2), pipeline.py, and cypher/result_postprocess.py. Byte-identical; adds a
direct parametrized unit test for the helper across dtype kinds + None.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant