Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f880b72
Improve performance of strings::count_matches utility
davidwendt May 8, 2026
d3e6a94
add nodiscard
davidwendt May 8, 2026
97fe517
Merge branch 'main' into count-regex-faster
davidwendt May 11, 2026
8a4ea74
fix doxygen comment
davidwendt May 11, 2026
ecdddbc
Merge branch 'main' into count-regex-faster
davidwendt May 11, 2026
652c044
Merge branch 'main' into count-regex-faster
davidwendt May 11, 2026
486b7fc
Merge branch 'main' into count-regex-faster
davidwendt May 12, 2026
4c791a3
Merge branch 'main' into count-regex-faster
gforsyth May 12, 2026
bb6cb9e
Merge branch 'main' into count-regex-faster
davidwendt May 12, 2026
baf8e6b
fix check_path logic and gtest
davidwendt May 12, 2026
acaf9f7
Merge branch 'main' into count-regex-faster
davidwendt May 13, 2026
4e25915
Merge branch 'main' into count-regex-faster
davidwendt May 13, 2026
1071d27
Merge branch 'main' into count-regex-faster
davidwendt May 14, 2026
004392a
Merge branch 'main' into count-regex-faster
davidwendt May 18, 2026
737bb1f
Merge branch 'main' into count-regex-faster
davidwendt May 18, 2026
e94c164
Merge branch 'main' into count-regex-faster
davidwendt May 20, 2026
e050a67
Merge branch 'main' into count-regex-faster
davidwendt May 22, 2026
eb216eb
Merge branch 'main' into count-regex-faster
davidwendt May 22, 2026
c49750f
Merge branch 'main' into count-regex-faster
davidwendt May 26, 2026
17e65a1
Merge branch 'main' into count-regex-faster
davidwendt May 27, 2026
52a0ece
Merge branch 'main' into count-regex-faster
davidwendt May 28, 2026
f975d79
Merge branch 'main' into count-regex-faster
davidwendt May 29, 2026
ec40c34
Merge branch 'main' into count-regex-faster
davidwendt Jun 1, 2026
419593d
Merge branch 'main' into count-regex-faster
davidwendt Jun 1, 2026
a85867f
Merge branch 'main' into count-regex-faster
davidwendt Jun 3, 2026
bd3a54f
Merge branch 'main' into count-regex-faster
davidwendt Jun 3, 2026
481621e
Merge branch 'main' into count-regex-faster
davidwendt Jun 5, 2026
514a152
Merge branch 'main' into count-regex-faster
davidwendt Jun 5, 2026
4a2078e
Merge branch 'main' into count-regex-faster
davidwendt Jun 8, 2026
4c4de23
Merge branch 'main' into count-regex-faster
davidwendt Jun 8, 2026
ff40d12
Merge branch 'main' into count-regex-faster
davidwendt Jun 8, 2026
e583d16
Merge branch 'main' into count-regex-faster
davidwendt Jun 10, 2026
cbae721
Merge branch 'count-regex-faster' of github.com:davidwendt/cudf into …
davidwendt Jun 10, 2026
1649dcf
Merge branch 'main' into count-regex-faster
davidwendt Jun 10, 2026
98df544
Merge branch 'main' into count-regex-faster
davidwendt Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions cpp/src/strings/count_matches.cu
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -19,6 +19,7 @@ namespace {
/**
* @brief Kernel counts the total matches for the given regex in each string.
*/
template <positional P>
struct count_fn {
column_device_view const d_strings;

Expand All @@ -33,7 +34,7 @@ struct count_fn {

auto itr = d_str.begin();
while (itr.position() <= nchars) {
auto result = prog.find(thread_idx, d_str, itr);
auto result = prog.find<P>(thread_idx, d_str, itr);
if (!result) { break; }
++count;
// increment the iterator is faster than creating a new one
Expand All @@ -58,7 +59,13 @@ std::unique_ptr<column> count_matches(column_device_view const& d_strings,

auto d_results = results->mutable_view().data<cudf::size_type>();

launch_transform_kernel(count_fn{d_strings}, d_prog, d_results, d_strings.size(), stream);
if (d_prog.is_empty_match_possible()) {
launch_transform_kernel(
count_fn<positional::BEGIN_END>{d_strings}, d_prog, d_results, d_strings.size(), stream);
} else {
launch_transform_kernel(
count_fn<positional::END_ONLY>{d_strings}, d_prog, d_results, d_strings.size(), stream);
}

return results;
}
Expand Down
29 changes: 29 additions & 0 deletions cpp/src/strings/regex/regcomp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <stack>
#include <string>
#include <tuple>
#include <unordered_set>
#include <vector>

namespace cudf {
Expand Down Expand Up @@ -1213,6 +1214,34 @@ void reprog::check_for_errors()
}
}

match_flags reprog::compute_match_flags() const
{
static const std::unordered_set<int> non_consuming_inst_types{
OR, BOL, EOL, BOW, NBOW, LBRA, RBRA};

auto check_paths = [this](auto&& self, int id, std::unordered_set<int>& visited) -> bool {
if (id < 0 || !std::get<1>(visited.insert(id))) { return false; }
auto const& inst = _insts[id];
if (inst.type == END) { return false; }
if (non_consuming_inst_types.find(inst.type) == non_consuming_inst_types.end()) { return true; }
if (inst.type == OR) {
return self(self, inst.u2.left_id, visited) && self(self, inst.u1.right_id, visited);
}
return self(self, inst.u2.next_id, visited);
};

bool found_non_consuming_path = false;
for (auto start : _startinst_ids) {
if (start == -1) break;
std::unordered_set<int> visited;
if (!check_paths(check_paths, start, visited)) {
found_non_consuming_path = true;
break;
}
}
return found_non_consuming_path ? match_flags::EMPTY_MATCH : match_flags::NONE;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#ifndef NDEBUG
void reprog::print(regex_flags const flags)
{
Expand Down
10 changes: 9 additions & 1 deletion cpp/src/strings/regex/regcomp.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -80,6 +80,11 @@ struct reinst {
int32_t reserved4;
};

enum match_flags : int32_t {
NONE = 0, // no special handling
EMPTY_MATCH = 1, // may match an empty string, e.g. a* or \b$
};

/**
* @brief Regex program handles parsing a pattern into a vector
* of chained instructions.
Expand Down Expand Up @@ -129,6 +134,9 @@ class reprog {
void optimize();
void finalize();
void check_for_errors();

[[nodiscard]] match_flags compute_match_flags() const;

#ifndef NDEBUG
void print(regex_flags const flags);
#endif
Expand Down
17 changes: 13 additions & 4 deletions cpp/src/strings/regex/regex.cuh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION. All rights reserved.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -110,6 +110,14 @@ class reprog_device {
*/
[[nodiscard]] __device__ inline bool is_empty() const;

/**
* @brief Returns true if the instructions in this program can match an empty string
*/
[[nodiscard]] CUDF_HOST_DEVICE bool is_empty_match_possible() const
{
return _empty_match_possible;
}

/**
* @brief Returns the size needed for working memory for the given thread count.
*
Expand Down Expand Up @@ -258,9 +266,10 @@ class reprog_device {
int32_t const* _startinst_ids{}; // array of start instruction ids
reclass_device const* _classes{}; // array of regex classes

std::size_t _prog_size{}; // total size of this instance
void* _buffer{}; // working memory buffer
int32_t _thread_count{}; // threads available in working memory
std::size_t _prog_size{}; // total size of this instance
void* _buffer{}; // working memory buffer
int32_t _thread_count{}; // threads available in working memory
bool _empty_match_possible{}; // true if the regex can match an empty string
};

/**
Expand Down
2 changes: 2 additions & 0 deletions cpp/src/strings/regex/regexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ std::unique_ptr<reprog_device, std::function<void(reprog_device*)>> reprog_devic
// initialize the rest of the elements
d_prog->_max_insts = insts_count;
d_prog->_prog_size = memsize + sizeof(reprog_device);
d_prog->_empty_match_possible =
(h_prog.compute_match_flags() == cudf::strings::detail::match_flags::EMPTY_MATCH);

// copy flat prog to device memory
cudf::detail::cuda_memcpy<u_char>(*d_buffer, h_buffer, stream);
Expand Down
36 changes: 36 additions & 0 deletions cpp/tests/strings/contains_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,42 @@ TEST_F(StringsContainsTests, CountTest)
}
}

TEST_F(StringsContainsTests, CountEmptyMatching)
{
auto input = cudf::test::strings_column_wrapper({"hello", "world", "", "abc"});
auto sv = cudf::strings_column_view(input);
auto patterns = std::vector<std::string>{"a*", "X?", "b{0,}", "()", "(?:)", "[A-Z]*"};
auto expected = cudf::test::fixed_width_column_wrapper<int32_t>({6, 6, 1, 4});
for (auto pattern : patterns) {
auto prog = cudf::strings::regex_program::create(pattern);
auto results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
}
Comment thread
davidwendt marked this conversation as resolved.
// "\\b", "\\B",
expected = cudf::test::fixed_width_column_wrapper<int32_t>({1, 1, 1, 1});
auto prog = cudf::strings::regex_program::create("^");
auto results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
prog = cudf::strings::regex_program::create("$");
results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);

expected = cudf::test::fixed_width_column_wrapper<int32_t>({0, 0, 1, 0});
prog = cudf::strings::regex_program::create("^$");
results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);

expected = cudf::test::fixed_width_column_wrapper<int32_t>({2, 2, 0, 2});
prog = cudf::strings::regex_program::create("\\b");
results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);

expected = cudf::test::fixed_width_column_wrapper<int32_t>({4, 4, 1, 2});
prog = cudf::strings::regex_program::create("\\B");
results = cudf::strings::count_re(sv, *prog);
CUDF_TEST_EXPECT_COLUMNS_EQUAL(*results, expected);
}

TEST_F(StringsContainsTests, FixedQuantifier)
{
auto input = cudf::test::strings_column_wrapper({"a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"});
Expand Down
Loading