aboutsummaryrefslogtreecommitdiffstats
path: root/searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp')
-rw-r--r--searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp317
1 files changed, 220 insertions, 97 deletions
diff --git a/searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp b/searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp
index d5ee88e1617..dc05471a1eb 100644
--- a/searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp
+++ b/searchcore/src/tests/proton/matching/match_loop_communicator/match_loop_communicator_test.cpp
@@ -1,7 +1,10 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include <vespa/vespalib/testkit/test_kit.h>
#include <vespa/searchcore/proton/matching/match_loop_communicator.h>
+#include <vespa/searchlib/features/first_phase_rank_lookup.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vespalib/test/nexus.h>
#include <algorithm>
+#include <atomic>
using namespace proton::matching;
@@ -12,10 +15,22 @@ using Hit = MatchLoopCommunicator::Hit;
using Hits = MatchLoopCommunicator::Hits;
using TaggedHit = MatchLoopCommunicator::TaggedHit;
using TaggedHits = MatchLoopCommunicator::TaggedHits;
+using search::features::FirstPhaseRankLookup;
using search::queryeval::SortedHitSequence;
+using vespalib::test::Nexus;
+
+namespace search::queryeval {
+
+void PrintTo(const Scores& scores, std::ostream* os) {
+ *os << "{" << scores.low << "," << scores.high << "}";
+}
+
+}
std::vector<Hit> hit_vec(std::vector<Hit> list) { return list; }
+auto do_nothing = []() noexcept {};
+
Hits makeScores(size_t id) {
switch (id) {
case 0: return {{1, 5.4}, {2, 4.4}, {3, 3.4}, {4, 2.4}, {5, 1.4}};
@@ -27,6 +42,13 @@ Hits makeScores(size_t id) {
return {};
}
+Hits make_first_scores(size_t id, size_t size) {
+ auto result = makeScores(id);
+ EXPECT_LE(size, result.size());
+ result.resize(size);
+ return result;
+}
+
std::tuple<size_t,Hits,RangePair> second_phase(MatchLoopCommunicator &com, const Hits &hits, size_t thread_id, double delta = 0.0) {
std::vector<uint32_t> refs;
for (size_t i = 0; i < hits.size(); ++i) {
@@ -60,25 +82,6 @@ size_t my_work_size(MatchLoopCommunicator &com, const Hits &hits, size_t thread_
return work_size;
}
-void equal(size_t count, const Hits & a, const Hits & b) {
- EXPECT_EQUAL(count, b.size());
- for (size_t i(0); i < count; i++) {
- EXPECT_EQUAL(a[i].first, b[i].first);
- EXPECT_EQUAL(a[i].second , b[i].second);
- }
-}
-
-void equal_range(const Range &a, const Range &b) {
- EXPECT_EQUAL(a.isValid(), b.isValid());
- EXPECT_EQUAL(a.low, b.low);
- EXPECT_EQUAL(a.high, b.high);
-}
-
-void equal_ranges(const RangePair &a, const RangePair &b) {
- TEST_DO(equal_range(a.first, b.first));
- TEST_DO(equal_range(a.second, b.second));
-}
-
struct EveryOdd : public search::queryeval::IDiversifier {
bool accepted(uint32_t docId) override {
return docId & 0x01;
@@ -89,122 +92,242 @@ struct None : public search::queryeval::IDiversifier {
bool accepted(uint32_t) override { return false; }
};
-TEST_F("require that selectBest gives appropriate results for single thread", MatchLoopCommunicator(num_threads, 3)) {
- TEST_DO(equal(2u, hit_vec({{1, 5}, {2, 4}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}}), thread_id)));
- TEST_DO(equal(3u, hit_vec({{1, 5}, {2, 4}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}}), thread_id)));
- TEST_DO(equal(3u, hit_vec({{1, 5}, {2, 4}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}, {4, 2}}), thread_id)));
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_gives_appropriate_results_for_single_thread)
+{
+ constexpr size_t num_threads = 1;
+ constexpr size_t thread_id = 0;
+ MatchLoopCommunicator f1(num_threads, 3);
+ EXPECT_EQ(hit_vec({{1, 5}, {2, 4}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}}), thread_id));
+ EXPECT_EQ(hit_vec({{1, 5}, {2, 4}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}}), thread_id));
+ EXPECT_EQ(hit_vec({{1, 5}, {2, 4}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}, {4, 2}}), thread_id));
}
-TEST_F("require that selectBest gives appropriate results for single thread with filter",
- MatchLoopCommunicator(num_threads, 3, std::make_unique<EveryOdd>()))
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_gives_appropriate_results_for_single_thread_with_filter)
{
- TEST_DO(equal(1u, hit_vec({{1, 5}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}}), thread_id)));
- TEST_DO(equal(2u, hit_vec({{1, 5}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}}), thread_id)));
- TEST_DO(equal(3u, hit_vec({{1, 5}, {3, 3}, {5, 1}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}, {4, 2}, {5, 1}, {6, 0}}), thread_id)));
+ constexpr size_t num_threads = 1;
+ constexpr size_t thread_id = 0;
+ MatchLoopCommunicator f1(num_threads, 3, std::make_unique<EveryOdd>(), nullptr, do_nothing);
+ EXPECT_EQ(hit_vec({{1, 5}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}}), thread_id));
+ EXPECT_EQ(hit_vec({{1, 5}, {3, 3}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}}), thread_id));
+ EXPECT_EQ(hit_vec({{1, 5}, {3, 3}, {5, 1}}), selectBest(f1, hit_vec({{1, 5}, {2, 4}, {3, 3}, {4, 2}, {5, 1}, {6, 0}}), thread_id));
}
-TEST_MT_F("require that selectBest works with no hits", 10, MatchLoopCommunicator(num_threads, 10)) {
- EXPECT_TRUE(selectBest(f1, hit_vec({}), thread_id).empty());
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_works_with_no_hits)
+{
+ constexpr size_t num_threads = 10;
+ MatchLoopCommunicator f1(num_threads, 10);
+ auto task = [&f1](Nexus& ctx) {
+ EXPECT_TRUE(selectBest(f1, hit_vec({}), ctx.thread_id()).empty());
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that selectBest works with too many hits from all threads", 5, MatchLoopCommunicator(num_threads, 13)) {
- if (thread_id < 3) {
- TEST_DO(equal(3u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- } else {
- TEST_DO(equal(2u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- }
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_works_with_too_many_hits_from_all_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 13);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ if (thread_id < 3) {
+ EXPECT_EQ(make_first_scores(thread_id, 3), selectBest(f1, makeScores(thread_id), thread_id));
+ } else {
+ EXPECT_EQ(make_first_scores(thread_id, 2), selectBest(f1, makeScores(thread_id), thread_id));
+ }
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that selectBest works with some exhausted threads", 5, MatchLoopCommunicator(num_threads, 22)) {
- if (thread_id < 2) {
- TEST_DO(equal(5u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- } else {
- TEST_DO(equal(4u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- }
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_works_with_some_exhausted_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 22);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ if (thread_id < 2) {
+ EXPECT_EQ(makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id));
+ } else {
+ EXPECT_EQ(make_first_scores(thread_id, 4), selectBest(f1, makeScores(thread_id), thread_id));
+ }
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that selectBest can select all hits from all threads", 5, MatchLoopCommunicator(num_threads, 100)) {
- EXPECT_EQUAL(5u, selectBest(f1, makeScores(thread_id), thread_id).size());
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_can_select_all_hits_from_all_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 100);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ EXPECT_EQ(5u, selectBest(f1, makeScores(thread_id), thread_id).size());
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that selectBest works with some empty threads", 10, MatchLoopCommunicator(num_threads, 7)) {
- if (thread_id < 2) {
- TEST_DO(equal(2u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- } else if (thread_id < 5) {
- TEST_DO(equal(1u, makeScores(thread_id), selectBest(f1, makeScores(thread_id), thread_id)));
- } else {
- EXPECT_TRUE(selectBest(f1, makeScores(thread_id), thread_id).empty());
- }
+TEST(MatchLoopCommunicatorTest, require_that_selectBest_works_with_some_empty_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 7);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ if (thread_id < 2) {
+ EXPECT_EQ(make_first_scores(thread_id, 2), selectBest(f1, makeScores(thread_id), thread_id));
+ } else if (thread_id < 5) {
+ EXPECT_EQ(make_first_scores(thread_id, 1), selectBest(f1, makeScores(thread_id), thread_id));
+ } else {
+ EXPECT_TRUE(selectBest(f1, makeScores(thread_id), thread_id).empty());
+ }
+ };
+ Nexus::run(num_threads, task);
}
-TEST_F("require that rangeCover works with a single thread", MatchLoopCommunicator(num_threads, 5)) {
+TEST(MatchLoopCommunicatorTest, require_that_rangeCover_works_with_a_single_thread)
+{
+ constexpr size_t num_threads = 1;
+ constexpr size_t thread_id = 0;
+ MatchLoopCommunicator f1(num_threads, 5);
RangePair res = rangeCover(f1, hit_vec({{1, 7.5}, {2, 1.5}}), thread_id, 10);
- TEST_DO(equal_ranges(RangePair({1.5, 7.5}, {11.5, 17.5}), res));
+ EXPECT_EQ(RangePair({1.5, 7.5}, {11.5, 17.5}), res);
}
-TEST_MT_F("require that rangeCover works with multiple threads", 5, MatchLoopCommunicator(num_threads, 10)) {
- RangePair res = rangeCover(f1, hit_vec({{thread_id * 100 + 1, 100.0 + thread_id}, {thread_id * 100 + 2, 100.0 - thread_id}}), thread_id, 10);
- TEST_DO(equal_ranges(RangePair({96.0, 104.0}, {106.0, 114.0}), res));
+TEST(MatchLoopCommunicatorTest, require_that_rangeCover_works_with_multiple_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 10);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ RangePair res = rangeCover(f1, hit_vec({{thread_id * 100 + 1, 100.0 + thread_id}, {thread_id * 100 + 2, 100.0 - thread_id}}), thread_id, 10);
+ EXPECT_EQ(RangePair({96.0, 104.0}, {106.0, 114.0}), res);
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that rangeCover works with no hits", 10, MatchLoopCommunicator(num_threads, 5)) {
- RangePair res = rangeCover(f1, hit_vec({}), thread_id, 10);
- TEST_DO(equal_ranges(RangePair({}, {}), res));
+TEST(MatchLoopCommunicatorTest, require_that_rangeCover_works_with_no_hits)
+{
+ constexpr size_t num_threads = 10;
+ MatchLoopCommunicator f1(num_threads, 5);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ RangePair res = rangeCover(f1, hit_vec({}), thread_id, 10);
+ EXPECT_EQ(RangePair({}, {}), res);
+ };
+ Nexus::run(num_threads, task);
}
-TEST_FFF("require that hits dropped due to lack of diversity affects range cover result",
- MatchLoopCommunicator(num_threads, 3),
- MatchLoopCommunicator(num_threads, 3, std::make_unique<EveryOdd>()),
- MatchLoopCommunicator(num_threads, 3, std::make_unique<None>()))
+TEST(MatchLoopCommunicatorTest, require_that_hits_dropped_due_to_lack_of_diversity_affects_range_cover_result)
{
+ constexpr size_t num_threads = 1;
+ constexpr size_t thread_id = 0;
+ MatchLoopCommunicator f1(num_threads, 3);
+ MatchLoopCommunicator f2(num_threads, 3, std::make_unique<EveryOdd>(), nullptr, do_nothing);
+ MatchLoopCommunicator f3(num_threads, 3, std::make_unique<None>(), nullptr, do_nothing);
auto hits_in = hit_vec({{1, 5}, {2, 4}, {3, 3}, {4, 2}, {5, 1}});
auto [my_work1, hits1, ranges1] = second_phase(f1, hits_in, thread_id, 10);
auto [my_work2, hits2, ranges2] = second_phase(f2, hits_in, thread_id, 10);
auto [my_work3, hits3, ranges3] = second_phase(f3, hits_in, thread_id, 10);
- EXPECT_EQUAL(my_work1, 3u);
- EXPECT_EQUAL(my_work2, 3u);
- EXPECT_EQUAL(my_work3, 0u);
+ EXPECT_EQ(my_work1, 3u);
+ EXPECT_EQ(my_work2, 3u);
+ EXPECT_EQ(my_work3, 0u);
- TEST_DO(equal(3u, hit_vec({{1, 15}, {2, 14}, {3, 13}}), hits1));
- TEST_DO(equal(3u, hit_vec({{1, 15}, {3, 13}, {5, 11}}), hits2));
- TEST_DO(equal(0u, hit_vec({}), hits3));
+ EXPECT_EQ(hit_vec({{1, 15}, {2, 14}, {3, 13}}), hits1);
+ EXPECT_EQ(hit_vec({{1, 15}, {3, 13}, {5, 11}}), hits2);
+ EXPECT_EQ(hit_vec({}), hits3);
- TEST_DO(equal_ranges(RangePair({3,5},{13,15}), ranges1));
- TEST_DO(equal_ranges(RangePair({4,5},{11,15}), ranges2)); // best dropped: 4
+ EXPECT_EQ(RangePair({3,5},{13,15}), ranges1);
+ EXPECT_EQ(RangePair({4,5},{11,15}), ranges2); // best dropped: 4
// note that the 'drops all hits due to diversity' case will
// trigger much of the same code path as dropping second phase
// ranking due to hard doom.
- TEST_DO(equal_ranges(RangePair({},{}), ranges3));
+ EXPECT_EQ(RangePair({},{}), ranges3);
}
-TEST_MT_F("require that estimate_match_frequency will count hits and docs across threads", 4, MatchLoopCommunicator(num_threads, 5)) {
- double freq = (0.0/10.0 + 1.0/11.0 + 2.0/12.0 + 3.0/13.0) / 4.0;
- EXPECT_APPROX(freq, f1.estimate_match_frequency(Matches(thread_id, thread_id + 10)), 0.00001);
+TEST(MatchLoopCommunicatorTest, require_that_estimate_match_frequency_will_count_hits_and_docs_across_threads)
+{
+ constexpr size_t num_threads = 4;
+ MatchLoopCommunicator f1(num_threads, 5);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ double freq = (0.0/10.0 + 1.0/11.0 + 2.0/12.0 + 3.0/13.0) / 4.0;
+ EXPECT_NEAR(freq, f1.estimate_match_frequency(Matches(thread_id, thread_id + 10)), 0.00001);
+ };
+ Nexus::run(num_threads, task);
}
-TEST_MT_F("require that second phase work is evenly distributed among search threads", 5, MatchLoopCommunicator(num_threads, 20)) {
- size_t num_hits = thread_id * 5;
- size_t docid = thread_id * 100;
- double score = thread_id * 100.0;
- Hits my_hits;
- for(size_t i = 0; i < num_hits; ++i) {
- my_hits.emplace_back(++docid, score);
- score -= 1.0;
- }
- auto [my_work, best_hits, ranges] = second_phase(f1, my_hits, thread_id, 1000.0);
- EXPECT_EQUAL(my_work, 4u);
- TEST_DO(equal_ranges(RangePair({381,400},{1381,1400}), ranges));
- if (thread_id == 4) {
- for (auto &hit: my_hits) {
- hit.second += 1000.0;
- }
- TEST_DO(equal(num_hits, my_hits, best_hits));
- } else {
- EXPECT_TRUE(best_hits.empty());
+TEST(MatchLoopCommunicatorTest, require_that_second_phase_work_is_evenly_distributed_among_search_threads)
+{
+ constexpr size_t num_threads = 5;
+ MatchLoopCommunicator f1(num_threads, 20);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ size_t num_hits = thread_id * 5;
+ size_t docid = thread_id * 100;
+ double score = thread_id * 100.0;
+ Hits my_hits;
+ for(size_t i = 0; i < num_hits; ++i) {
+ my_hits.emplace_back(++docid, score);
+ score -= 1.0;
+ }
+ auto [my_work, best_hits, ranges] = second_phase(f1, my_hits, thread_id, 1000.0);
+ EXPECT_EQ(my_work, 4u);
+ EXPECT_EQ(RangePair({381,400},{1381,1400}), ranges);
+ if (thread_id == 4) {
+ for (auto &hit: my_hits) {
+ hit.second += 1000.0;
+ }
+ EXPECT_EQ(my_hits, best_hits);
+ } else {
+ EXPECT_TRUE(best_hits.empty());
+ }
+ };
+ Nexus::run(num_threads, task);
+}
+
+namespace {
+
+std::vector<double> extract_ranks(const FirstPhaseRankLookup& l) {
+ std::vector<double> result;
+ for (uint32_t docid = 21; docid < 26; ++docid) {
+ result.emplace_back(l.lookup(docid));
}
+ return result;
+}
+
+search::feature_t unranked = std::numeric_limits<search::feature_t>::max();
+
+using FeatureVec = std::vector<search::feature_t>;
+
+}
+
+TEST(MatchLoopCommunicatorTest, require_that_first_phase_rank_lookup_is_populated)
+{
+ constexpr size_t num_threads = 1;
+ constexpr size_t thread_id = 0;
+ FirstPhaseRankLookup l1;
+ FirstPhaseRankLookup l2;
+ MatchLoopCommunicator f1(num_threads, 3, {}, &l1, do_nothing);
+ MatchLoopCommunicator f2(num_threads, 3, std::make_unique<EveryOdd>(), &l2, do_nothing);
+ auto hits_in = hit_vec({{21, 5}, {22, 4}, {23, 3}, {24, 2}, {25, 1}});
+ auto res1 = second_phase(f1, hits_in, thread_id, 10);
+ auto res2 = second_phase(f2, hits_in, thread_id, 10);
+ EXPECT_EQ(FeatureVec({1, 2, 3, unranked, unranked}), extract_ranks(l1));
+ EXPECT_EQ(FeatureVec({1, unranked, 3, unranked, 5}), extract_ranks(l2));
+}
+
+TEST(MatchLoopCommunicatorTest, require_that_before_second_phase_is_called_once)
+{
+ constexpr size_t num_threads = 5;
+ std::atomic<int> cnt(0);
+ auto before_second_phase = [&cnt]() noexcept { ++cnt; };
+ MatchLoopCommunicator f1(num_threads, 3, {}, nullptr, before_second_phase);
+ auto task = [&f1](Nexus& ctx) {
+ auto thread_id = ctx.thread_id();
+ auto hits_in = hit_vec({});
+ (void) second_phase(f1, hits_in, thread_id, 1000.0);
+ };
+ Nexus::run(num_threads, task);
+ EXPECT_EQ(1, cnt.load(std::memory_order_acquire));
}
-TEST_MAIN() { TEST_RUN_ALL(); }
+GTEST_MAIN_RUN_ALL_TESTS()