From 5fac4ad47b07fbfa43016aeffc30feaa6d5ff5b9 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 16 May 2023 21:15:30 -0700 Subject: [PATCH] [fuzzing] Improve OSA distance performance (#33149) Early out evaluating this function where we can, and use macros to eliminate function calls in debug builds. Takes per-example time from 5400ms to 1200ms in debug asan builds. --------- Co-authored-by: ctiller --- test/core/end2end/end2end_test_fuzzer.cc | 44 ++++++++++++++++++------ test/core/util/osa_distance.cc | 41 +++++++++------------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/test/core/end2end/end2end_test_fuzzer.cc b/test/core/end2end/end2end_test_fuzzer.cc index 2131e095032..dbe503e7bb8 100644 --- a/test/core/end2end/end2end_test_fuzzer.cc +++ b/test/core/end2end/end2end_test_fuzzer.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,10 @@ DEFINE_PROTO_FUZZER(const core_end2end_test_fuzzer::Msg& msg) { factory; }; + static const auto only_suite = grpc_core::GetEnv("GRPC_TEST_FUZZER_SUITE"); + static const auto only_test = grpc_core::GetEnv("GRPC_TEST_FUZZER_TEST"); + static const auto only_config = grpc_core::GetEnv("GRPC_TEST_FUZZER_CONFIG"); + static const auto all_tests = grpc_core::CoreEnd2endTestRegistry::Get().AllTests(); static const auto tests = []() { @@ -76,9 +81,6 @@ DEFINE_PROTO_FUZZER(const core_end2end_test_fuzzer::Msg& msg) { grpc_core::ForceEnableExperiment("event_engine_client", true); grpc_core::ForceEnableExperiment("event_engine_listener", true); - auto only_suite = grpc_core::GetEnv("GRPC_TEST_FUZZER_SUITE"); - auto only_test = grpc_core::GetEnv("GRPC_TEST_FUZZER_TEST"); - auto only_config = grpc_core::GetEnv("GRPC_TEST_FUZZER_CONFIG"); std::vector tests; for (const auto& test : all_tests) { if (test.config->feature_mask & FEATURE_MASK_DO_NOT_FUZZ) continue; @@ -87,14 +89,17 @@ DEFINE_PROTO_FUZZER(const core_end2end_test_fuzzer::Msg& msg) { if (only_config.has_value() && test.config->name != only_config.value()) { continue; } + std::string test_name = + absl::StrCat(test.suite, ".", test.name, "/", test.config->name); tests.emplace_back( - Test{absl::StrCat(test.suite, ".", test.name, "/", test.config->name), - [&test]() { + Test{std::move(test_name), [&test]() { return std::unique_ptr( test.make_test(test.config)); }}); } GPR_ASSERT(!tests.empty()); + std::sort(tests.begin(), tests.end(), + [](const Test& a, const Test& b) { return a.name < b.name; }); return tests; }(); static const auto only_experiment = @@ -106,13 +111,30 @@ DEFINE_PROTO_FUZZER(const core_end2end_test_fuzzer::Msg& msg) { auto test_name = absl::StrCat(msg.suite(), ".", msg.test(), "/", msg.config()); - size_t best_test = 0; - for (size_t i = 0; i < tests.size(); i++) { - if (grpc_core::OsaDistance(test_name, tests[i].name) < - grpc_core::OsaDistance(test_name, tests[best_test].name)) { - best_test = i; + auto it = std::lower_bound( + tests.begin(), tests.end(), test_name, + [](const Test& a, absl::string_view b) { return a.name < b; }); + if (only_suite.has_value() || only_test.has_value() || + only_config.has_value()) { + // We get faster convergence for selective tests if we do a fuzzy match + // instead of an exact one. The opposite is true for non-selective tests. + if (it == tests.end() || it->name != test_name) { + size_t best_test = 0; + size_t best_distance = std::numeric_limits::max(); + for (size_t i = 0; i < tests.size(); i++) { + auto distance = grpc_core::OsaDistance(test_name, tests[i].name); + if (distance < best_distance) { + best_test = i; + best_distance = distance; + if (distance == 0) break; + } + } + it = tests.begin() + best_test; } } + if (it == tests.end()) { + return; + } if (only_experiment.has_value() && msg.config_vars().experiments() != only_experiment.value()) { @@ -134,7 +156,7 @@ DEFINE_PROTO_FUZZER(const core_end2end_test_fuzzer::Msg& msg) { auto engine = std::dynamic_pointer_cast(GetDefaultEventEngine()); - auto test = tests[best_test].factory(); + auto test = it->factory(); test->SetCrashOnStepFailure(); test->SetQuiesceEventEngine( [](std::shared_ptr&& ee) { diff --git a/test/core/util/osa_distance.cc b/test/core/util/osa_distance.cc index 9691feb5d54..f323e705af9 100644 --- a/test/core/util/osa_distance.cc +++ b/test/core/util/osa_distance.cc @@ -23,47 +23,38 @@ namespace grpc_core { -namespace { -class Matrix { - public: - Matrix(size_t width, size_t height) - : width_(width), - data_(width * height, std::numeric_limits::max()) {} - - size_t& operator()(size_t x, size_t y) { return data_[y * width_ + x]; } - - private: - size_t width_; - std::vector data_; -}; -} // namespace - size_t OsaDistance(absl::string_view s1, absl::string_view s2) { if (s1.size() > s2.size()) std::swap(s1, s2); if (s1.empty()) return static_cast(s2.size()); - Matrix d(s1.size() + 1, s2.size() + 1); - d(0, 0) = 0; + const auto width = s1.size() + 1; + const auto height = s2.size() + 1; + std::vector matrix(width * height, + std::numeric_limits::max()); +#define MATRIX_CELL(x, y) matrix[(y)*width + (x)] + + MATRIX_CELL(0, 0) = 0; for (size_t i = 1; i <= s1.size(); ++i) { - d(i, 0) = i; + MATRIX_CELL(i, 0) = i; } for (size_t j = 1; j <= s2.size(); ++j) { - d(0, j) = j; + MATRIX_CELL(0, j) = j; } for (size_t i = 1; i <= s1.size(); ++i) { for (size_t j = 1; j <= s2.size(); ++j) { const size_t cost = s1[i - 1] == s2[j - 1] ? 0 : 1; - d(i, j) = std::min({ - d(i - 1, j) + 1, // deletion - d(i, j - 1) + 1, // insertion - d(i - 1, j - 1) + cost // substitution + MATRIX_CELL(i, j) = std::min({ + MATRIX_CELL(i - 1, j) + 1, // deletion + MATRIX_CELL(i, j - 1) + 1, // insertion + MATRIX_CELL(i - 1, j - 1) + cost // substitution }); if (i > 1 && j > 1 && s1[i - 1] == s2[j - 2] && s1[i - 2] == s2[j - 1]) { - d(i, j) = std::min(d(i, j), d(i - 2, j - 2) + 1); // transposition + MATRIX_CELL(i, j) = std::min( + MATRIX_CELL(i, j), MATRIX_CELL(i - 2, j - 2) + 1); // transposition } } } - return d(s1.size(), s2.size()); + return MATRIX_CELL(s1.size(), s2.size()); } } // namespace grpc_core