Skip to content

Improve performance of strings count_matches utility with specific expression patterns - #22429

Merged
rapids-bot[bot] merged 35 commits into
NVIDIA:mainfrom
davidwendt:count-regex-faster
Jun 11, 2026
Merged

Improve performance of strings count_matches utility with specific expression patterns#22429
rapids-bot[bot] merged 35 commits into
NVIDIA:mainfrom
davidwendt:count-regex-faster

Conversation

@davidwendt

@davidwendt davidwendt commented May 8, 2026

Copy link
Copy Markdown
Contributor

Description

Updates the logic in the cudf::strings::detail::count_matches() utility to use the faster positional::END_ONLY regex state engine path when input pattern does not include empty-matching instruction paths.
The empty-matching-only instructions require tracking the start/end position which the END_ONLY path does not record. These patterns are not common like where the entire pattern is just an anchor like "\b" and "$" or the pattern can match virtually nothing (i.e. return an empty string) like with "(?:)" or "a*" or "a{0,}".
The instruction set is analyzed for such a path and if it does not exist, the faster END_ONLY code logic is used.

The count_matches utility is used in count_re as well as split_re, findall and extract_all.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@davidwendt davidwendt self-assigned this May 8, 2026
@davidwendt davidwendt added 2 - In Progress Currently a work in progress libcudf Affects libcudf (C++/CUDA) code. improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels May 8, 2026
@copy-pr-bot

copy-pr-bot Bot commented May 8, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@davidwendt

davidwendt commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

Benchmark results show up to 25% improvement for count_re

# count

## [0] NVIDIA RTX A6000

| max_width | num_rows | pattern |   Ref Time |   Cmp Time |          Diff |   %Diff |
|-----------|----------|---------|------------|------------|---------------|---------|
|    32     |  32768   |    0    | 169.300 us | 157.907 us |    -11.393 us |  -6.73% |
|    64     |  32768   |    0    | 326.756 us | 305.313 us |    -21.442 us |  -6.56% |
|    128    |  32768   |    0    | 648.013 us | 590.018 us |    -57.995 us |  -8.95% |
|    256    |  32768   |    0    |   1.250 ms |   1.141 ms |   -109.176 us |  -8.74% |
|    32     |  262144  |    0    | 648.595 us | 548.233 us |   -100.362 us | -15.47% |
|    64     |  262144  |    0    |   1.357 ms |   1.137 ms |   -220.500 us | -16.25% |
|    128    |  262144  |    0    |   2.953 ms |   2.344 ms |   -609.145 us | -20.63% |
|    256    |  262144  |    0    |   6.731 ms |   5.042 ms |  -1689.250 us | -25.10% |
|    32     | 2097152  |    0    |   4.519 ms |   3.846 ms |   -673.378 us | -14.90% |
|    64     | 2097152  |    0    |   9.556 ms |   7.984 ms |  -1571.139 us | -16.44% |
|    128    | 2097152  |    0    |  20.753 ms |  16.747 ms |  -4006.077 us | -19.30% |
|    256    | 2097152  |    0    |  46.701 ms |  35.662 ms | -11039.035 us | -23.64% |
|    32     |  32768   |    1    |  40.960 us |  40.610 us |     -0.350 us |  -0.85% |
|    64     |  32768   |    1    |  52.573 us |  52.247 us |     -0.326 us |  -0.62% |
|    128    |  32768   |    1    |  82.011 us |  81.694 us |     -0.317 us |  -0.39% |
|    256    |  32768   |    1    | 149.608 us | 148.300 us |     -1.307 us |  -0.87% |
|    32     |  262144  |    1    |  97.580 us |  92.489 us |     -5.091 us |  -5.22% |
|    64     |  262144  |    1    | 140.513 us | 132.223 us |     -8.290 us |  -5.90% |
|    128    |  262144  |    1    | 253.803 us | 242.245 us |    -11.558 us |  -4.55% |
|    256    |  262144  |    1    | 609.375 us | 577.544 us |    -31.831 us |  -5.22% |
|    32     | 2097152  |    1    | 548.561 us | 507.003 us |    -41.558 us |  -7.58% |
|    64     | 2097152  |    1    | 836.430 us | 802.318 us |    -34.112 us |  -4.08% |
|    128    | 2097152  |    1    |   1.665 ms |   1.602 ms |    -63.098 us |  -3.79% |
|    256    | 2097152  |    1    |   4.002 ms |   3.831 ms |   -170.964 us |  -4.27% |

and up to 21% for split_re

# split_re

## [0] NVIDIA RTX A6000

| max_width | num_rows |   Ref Time |   Cmp Time |          Diff |   %Diff |
|-----------|----------|------------|------------| --------------|---------|
|    32     |  32768   | 408.231 us | 404.176 us |     -4.055 us |  -0.99% |
|    64     |  32768   | 699.318 us | 720.646 us |     21.327 us |   3.05% |
|    128    |  32768   |   1.437 ms |   1.397 ms |    -40.356 us |  -2.81% |
|    256    |  32768   |   2.627 ms |   2.628 ms |      1.324 us |   0.05% |
|    32     |  262144  |   1.567 ms |   1.407 ms |   -159.413 us | -10.18% |
|    64     |  262144  |   3.141 ms |   2.785 ms |   -356.621 us | -11.35% |
|    128    |  262144  |   6.778 ms |   5.750 ms |  -1027.694 us | -15.16% |
|    256    |  262144  |  15.556 ms |  12.274 ms |  -3282.096 us | -21.10% |
|    32     | 2097152  |  10.744 ms |   9.664 ms |  -1079.461 us | -10.05% |
|    64     | 2097152  |  22.108 ms |  19.810 ms |  -2297.471 us | -10.39% |
|    128    | 2097152  |  48.239 ms |  41.677 ms |  -6562.017 us | -13.60% |
|    256    | 2097152  | 109.929 ms |  89.671 ms | -20258.460 us | -18.43% |

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt davidwendt added 3 - Ready for Review Ready for review by team and removed 2 - In Progress Currently a work in progress labels May 11, 2026
@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@davidwendt

Copy link
Copy Markdown
Contributor Author

/ok to test

@gforsyth

Copy link
Copy Markdown
Contributor

/ok to test

@davidwendt
davidwendt marked this pull request as ready for review May 12, 2026 15:17
@davidwendt
davidwendt requested a review from a team as a code owner May 12, 2026 15:17
@davidwendt
davidwendt requested review from karthikeyann and vyasr May 12, 2026 15:17
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Detects when a regex can match an empty string, exposes that flag on device, adapts the counting kernel to select a positional policy accordingly, and adds a test validating empty-match counting for several patterns.

Changes

Empty Match Detection and Handling

Layer / File(s) Summary
Match flags contract definition
cpp/src/strings/regex/regcomp.h
Defines match_flags enum with NONE and EMPTY_MATCH values, and declares reprog::compute_match_flags() const method.
Host-side empty-match detection algorithm
cpp/src/strings/regex/regcomp.cpp
Implements compute_match_flags() using depth-first graph traversal with visited-set tracking to identify non-consuming instruction paths and determine if empty matches are possible.
Device-side empty-match state and accessor
cpp/src/strings/regex/regex.cuh
Adds _empty_match_possible member and is_empty_match_possible() accessor to reprog_device.
Device initialization of empty-match flag
cpp/src/strings/regex/regexec.cpp
Initializes reprog_device::_empty_match_possible from host compute_match_flags() during device program creation.
Kernel template instantiation based on empty-match flag
cpp/src/strings/count_matches.cu
Templates count_fn on positional policy, uses prog.find<P>(), and conditionally instantiates BEGIN_END or END_ONLY kernel variants based on the empty-match predicate.
Empty-match counting validation test
cpp/tests/strings/contains_tests.cpp
Adds CountEmptyMatching test validating count_re for patterns that can and cannot match empty strings, including anchors and boundary assertions.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: improving count_matches performance by using a faster regex execution path for patterns without empty-matching instruction paths.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description clearly explains the purpose of the changes: optimizing count_matches() to use the faster positional::END_ONLY path when patterns don't contain empty-matching instruction paths, with specific examples and context about affected utilities.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/strings/regex/regcomp.cpp`:
- Around line 1217-1243: The OR branch logic in reprog::compute_match_flags
(inside the check_paths lambda) incorrectly uses logical OR causing a
consuming-path result if either branch consumes; change the OR handling to
require both branches to be consuming by replacing the current `||` behavior
with logical AND so that the OR case returns self(self, inst.u2.left_id,
visited) && self(self, inst.u1.right_id, visited); this ensures patterns where
one branch reaches END without consuming (e.g., a*, a?, etc.) are detected as
empty-match paths.

In `@cpp/tests/strings/contains_tests.cpp`:
- Around line 411-415: The test loop is using the loop variable pattern but is
hardcoded to regex_program::create("a*"); change it so regex_program::create
uses the loop variable pattern (the variable named pattern) so each entry from
patterns is compiled and passed to cudf::strings::count_re(sv, *prog); ensure
you still compare *results to expected and keep the existing variables sv,
expected and function calls unchanged.
- Around line 408-410: The test currently hardcodes
cudf::strings::regex_program::create("a*") instead of using the loop variable
pattern, so change the call to use pattern (e.g.,
cudf::strings::regex_program::create(pattern)) in the contains test (where the
regex is constructed) and update expectations: for patterns that match empty
everywhere ("a*", "X?", "b{0,}", "()", "(?:)", "[a-z]*") keep expected
{6,6,1,4}; for "^" and "$" use {1,1,1,1}; for "^$" use {0,0,1,0}; and set
appropriate counts for "\\b" and "\\B" (or split the loop into separate
TEST_CASEs per pattern) so the assertion compares the result against the correct
per-pattern expected vector instead of a single hardcoded expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b6057e77-3a65-4626-876d-d6cc754e656f

📥 Commits

Reviewing files that changed from the base of the PR and between 57e27e7 and 4c791a3.

📒 Files selected for processing (6)
  • cpp/src/strings/count_matches.cu
  • cpp/src/strings/regex/regcomp.cpp
  • cpp/src/strings/regex/regcomp.h
  • cpp/src/strings/regex/regex.cuh
  • cpp/src/strings/regex/regexec.cpp
  • cpp/tests/strings/contains_tests.cpp

Comment thread cpp/src/strings/regex/regcomp.cpp
Comment thread cpp/tests/strings/contains_tests.cpp Outdated
Comment thread cpp/tests/strings/contains_tests.cpp
@davidwendt

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit b5588b6 into NVIDIA:main Jun 11, 2026
134 checks passed
@davidwendt
davidwendt deleted the count-regex-faster branch June 11, 2026 18:34
abigalekim pushed a commit to abigalekim/cudf that referenced this pull request Jun 12, 2026
…pression patterns (NVIDIA#22429)

Updates the logic in the `cudf::strings::detail::count_matches()` utility to use the faster `positional::END_ONLY` regex state engine path when input pattern does not include empty-matching instruction paths.
The empty-matching-only instructions require tracking the start/end position which the `END_ONLY` path does not record. These patterns are not common like where the entire pattern is just an anchor like "\b" and "$" or the pattern can match virtually nothing (i.e. return an empty string) like with "(?:)" or "a*" or "a{0,}".
The instruction set is analyzed for such a path and if it does not exist, the faster `END_ONLY` code logic is used.

The `count_matches` utility is used in `count_re` as well as `split_re`, `findall` and `extract_all`.

Authors:
  - David Wendt (https://github.com/davidwendt)
  - Gil Forsyth (https://github.com/gforsyth)

Approvers:
  - Bradley Dice (https://github.com/bdice)
  - Yunsong Wang (https://github.com/PointKernel)

URL: NVIDIA#22429
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 - Ready for Review Ready for review by team improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants