aboutsummaryrefslogtreecommitdiffstats
path: root/searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp')
-rw-r--r--searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp286
1 files changed, 286 insertions, 0 deletions
diff --git a/searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp b/searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp
new file mode 100644
index 00000000000..6716e945a0d
--- /dev/null
+++ b/searchcore/src/tests/proton/matching/docid_range_scheduler/docid_range_scheduler_test.cpp
@@ -0,0 +1,286 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/fastos/fastos.h>
+#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/searchcore/proton/matching/docid_range_scheduler.h>
+#include <chrono>
+#include <thread>
+
+using namespace proton::matching;
+
+void verify_range(DocidRange a, DocidRange b) {
+ EXPECT_EQUAL(a.begin, b.begin);
+ EXPECT_EQUAL(a.end, b.end);
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that default docid range constructor creates and empty range") {
+ EXPECT_TRUE(DocidRange().empty());
+ EXPECT_EQUAL(DocidRange().size(), 0u);
+}
+
+TEST("require that docid range ensures end is not less than begin") {
+ EXPECT_EQUAL(DocidRange(10, 20).size(), 10u);
+ EXPECT_TRUE(!DocidRange(10, 20).empty());
+ EXPECT_EQUAL(DocidRange(10, 20).begin, 10u);
+ EXPECT_EQUAL(DocidRange(10, 20).end, 20u);
+ EXPECT_EQUAL(DocidRange(20, 10).size(), 0u);
+ EXPECT_TRUE(DocidRange(20, 10).empty());
+ EXPECT_EQUAL(DocidRange(20, 10).begin, 20u);
+ EXPECT_EQUAL(DocidRange(20, 10).end, 20u);
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that default constructed IdleObserver is always zero") {
+ IdleObserver observer;
+ EXPECT_TRUE(observer.is_always_zero());
+ EXPECT_EQUAL(0u, observer.get());
+}
+
+TEST("require that IdleObserver can observe an atomic size_t value") {
+ std::atomic<size_t> idle(0);
+ IdleObserver observer(idle);
+ EXPECT_TRUE(!observer.is_always_zero());
+ EXPECT_EQUAL(0u, observer.get());
+ idle = 10;
+ EXPECT_EQUAL(10u, observer.get());
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that the docid range splitter can split a docid range") {
+ DocidRangeSplitter splitter(DocidRange(1, 16), 4);
+ TEST_DO(verify_range(splitter.get(0), DocidRange(1, 5)));
+ TEST_DO(verify_range(splitter.get(1), DocidRange(5, 9)));
+ TEST_DO(verify_range(splitter.get(2), DocidRange(9, 13)));
+ TEST_DO(verify_range(splitter.get(3), DocidRange(13, 16)));
+}
+
+TEST("require that the docid range splitter can split an empty range") {
+ DocidRangeSplitter splitter(DocidRange(5, 5), 2);
+ TEST_DO(verify_range(splitter.get(0), DocidRange(5, 5)));
+ TEST_DO(verify_range(splitter.get(1), DocidRange(5, 5)));
+}
+
+TEST("require that the docid range splitter can split a range into more parts than values") {
+ DocidRangeSplitter splitter(DocidRange(1, 4), 4);
+ TEST_DO(verify_range(splitter.get(0), DocidRange(1, 2)));
+ TEST_DO(verify_range(splitter.get(1), DocidRange(2, 3)));
+ TEST_DO(verify_range(splitter.get(2), DocidRange(3, 4)));
+ TEST_DO(verify_range(splitter.get(3), DocidRange(4, 4)));
+}
+
+TEST("require that the docid range splitter gives empty ranges if accessed with too high index") {
+ DocidRangeSplitter splitter(DocidRange(1, 4), 3);
+ TEST_DO(verify_range(splitter.get(0), DocidRange(1, 2)));
+ TEST_DO(verify_range(splitter.get(1), DocidRange(2, 3)));
+ TEST_DO(verify_range(splitter.get(2), DocidRange(3, 4)));
+ TEST_DO(verify_range(splitter.get(3), DocidRange(4, 4)));
+ TEST_DO(verify_range(splitter.get(100), DocidRange(4, 4)));
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that the partition scheduler acts as expected") {
+ PartitionDocidRangeScheduler scheduler(4, 16);
+ TEST_DO(verify_range(scheduler.total_span(0), DocidRange(1, 5)));
+ TEST_DO(verify_range(scheduler.total_span(1), DocidRange(5, 9)));
+ TEST_DO(verify_range(scheduler.total_span(2), DocidRange(9, 13)));
+ TEST_DO(verify_range(scheduler.total_span(3), DocidRange(13, 16)));
+ EXPECT_EQUAL(scheduler.total_size(0), 4u);
+ EXPECT_EQUAL(scheduler.total_size(1), 4u);
+ EXPECT_EQUAL(scheduler.total_size(2), 4u);
+ EXPECT_EQUAL(scheduler.total_size(3), 3u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(1, 5)));
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(5, 9)));
+ TEST_DO(verify_range(scheduler.first_range(2), DocidRange(9, 13)));
+ TEST_DO(verify_range(scheduler.first_range(3), DocidRange(13, 16)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange()));
+ TEST_DO(verify_range(scheduler.next_range(1), DocidRange()));
+ TEST_DO(verify_range(scheduler.next_range(2), DocidRange()));
+ TEST_DO(verify_range(scheduler.next_range(3), DocidRange()));
+}
+
+TEST("require that the partition scheduler protects against documents underflow") {
+ PartitionDocidRangeScheduler scheduler(2, 0);
+ TEST_DO(verify_range(scheduler.total_span(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.total_span(1), DocidRange(1,1)));
+ EXPECT_EQUAL(scheduler.total_size(0), 0u);
+ EXPECT_EQUAL(scheduler.total_size(1), 0u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange()));
+ TEST_DO(verify_range(scheduler.next_range(1), DocidRange()));
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that the task scheduler acts as expected") {
+ TaskDocidRangeScheduler scheduler(2, 5, 20);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 19u);
+ TEST_DO(verify_range(scheduler.total_span(0), DocidRange(1, 20)));
+ TEST_DO(verify_range(scheduler.total_span(1), DocidRange(1, 20)));
+ EXPECT_EQUAL(scheduler.total_size(0), 0u);
+ EXPECT_EQUAL(scheduler.total_size(1), 0u);
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(1, 5)));
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(5, 9)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange(9, 13)));
+ EXPECT_EQUAL(scheduler.unassigned_size(), 7u);
+ TEST_DO(verify_range(scheduler.next_range(1), DocidRange(13, 17)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange(17, 20)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange(20, 20)));
+ TEST_DO(verify_range(scheduler.next_range(1), DocidRange(20, 20)));
+ EXPECT_EQUAL(scheduler.total_size(0), 11u);
+ EXPECT_EQUAL(scheduler.total_size(1), 8u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+}
+
+TEST("require that the task scheduler protects against documents underflow") {
+ TaskDocidRangeScheduler scheduler(2, 4, 0);
+ TEST_DO(verify_range(scheduler.total_span(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.total_span(1), DocidRange(1,1)));
+ EXPECT_EQUAL(scheduler.total_size(0), 0u);
+ EXPECT_EQUAL(scheduler.total_size(1), 0u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.next_range(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.next_range(1), DocidRange(1,1)));
+}
+
+//-----------------------------------------------------------------------------
+
+TEST("require that the adaptive scheduler starts by dividing the docid space equally") {
+ AdaptiveDocidRangeScheduler scheduler(4, 1, 16);
+ EXPECT_EQUAL(scheduler.total_size(0), 4u);
+ EXPECT_EQUAL(scheduler.total_size(1), 4u);
+ EXPECT_EQUAL(scheduler.total_size(2), 4u);
+ EXPECT_EQUAL(scheduler.total_size(3), 3u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(1, 5)));
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(5, 9)));
+ TEST_DO(verify_range(scheduler.first_range(2), DocidRange(9, 13)));
+ TEST_DO(verify_range(scheduler.first_range(3), DocidRange(13, 16)));
+}
+
+TEST("require that the adaptive scheduler reports the full span to all threads") {
+ AdaptiveDocidRangeScheduler scheduler(3, 1, 16);
+ TEST_DO(verify_range(scheduler.total_span(0), DocidRange(1,16)));
+ TEST_DO(verify_range(scheduler.total_span(1), DocidRange(1,16)));
+ TEST_DO(verify_range(scheduler.total_span(2), DocidRange(1,16)));
+}
+
+TEST_MT_F("require that the adaptive scheduler terminates when all workers request more work",
+ 4, AdaptiveDocidRangeScheduler(num_threads, 1, 16))
+{
+ (void) f1.first_range(thread_id);
+ DocidRange range = f1.next_range(thread_id);
+ EXPECT_TRUE(range.empty());
+}
+
+void wait_idle(const DocidRangeScheduler &scheduler, size_t wanted) {
+ IdleObserver observer = scheduler.make_idle_observer();
+ while (observer.get() != wanted) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+TEST_MT_F("require that the adaptive scheduler enables threads to share work",
+ 3, AdaptiveDocidRangeScheduler(num_threads, 1, 28))
+{
+ DocidRange range = f1.first_range(thread_id);
+ if (thread_id == 0) {
+ TEST_DO(verify_range(range, DocidRange(1,10)));
+ } else if (thread_id == 1) {
+ TEST_DO(verify_range(range, DocidRange(10,19)));
+ } else {
+ TEST_DO(verify_range(range, DocidRange(19,28)));
+ }
+ EXPECT_EQUAL(f1.total_size(thread_id), 9u);
+ TEST_DO(verify_range(f1.share_range(thread_id, range), range));
+ TEST_BARRIER();
+ if (thread_id == 0) {
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange(25,28)));
+ } else if (thread_id == 1) {
+ wait_idle(f1, 1);
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange(22,25)));
+ } else {
+ wait_idle(f1, 2);
+ verify_range(f1.share_range(thread_id, range), DocidRange(19,22));
+ }
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ EXPECT_EQUAL(f1.total_size(0), 12u);
+ EXPECT_EQUAL(f1.total_size(1), 12u);
+ EXPECT_EQUAL(f1.total_size(2), 3u);
+}
+
+TEST("require that the adaptive scheduler protects against documents underflow") {
+ AdaptiveDocidRangeScheduler scheduler(2, 1, 0);
+ TEST_DO(verify_range(scheduler.first_range(0), DocidRange(1,1)));
+ TEST_DO(verify_range(scheduler.first_range(1), DocidRange(1,1)));
+ EXPECT_EQUAL(scheduler.total_size(0), 0u);
+ EXPECT_EQUAL(scheduler.total_size(1), 0u);
+ EXPECT_EQUAL(scheduler.unassigned_size(), 0u);
+}
+
+TEST_MT_F("require that the adaptive scheduler respects the minimal task size",
+ 2, AdaptiveDocidRangeScheduler(num_threads, 3, 21))
+{
+ EXPECT_EQUAL(f1.first_range(thread_id).size(), 10u);
+ if (thread_id == 0) {
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange(18,21)));
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ } else {
+ wait_idle(f1, 1);
+ // a range with size 5 will not be split
+ TEST_DO(verify_range(f1.share_range(thread_id, DocidRange(16,21)), DocidRange(16,21)));
+ // a range with size 6 will be split
+ TEST_DO(verify_range(f1.share_range(thread_id, DocidRange(15,21)), DocidRange(15,18)));
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ }
+}
+
+TEST_MT_F("require that the adaptive scheduler will never split a task with size 1",
+ 2, AdaptiveDocidRangeScheduler(num_threads, 0, 21))
+{
+ EXPECT_EQUAL(f1.first_range(thread_id).size(), 10u);
+ if (thread_id == 0) {
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ } else {
+ IdleObserver observer = f1.make_idle_observer();
+ while (observer.get() == 0) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+ DocidRange small_range = DocidRange(20,21);
+ verify_range(f1.share_range(thread_id, small_range), small_range);
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ }
+}
+
+TEST_MT_F("require that the adaptive scheduler can leave idle workers alone due to minimal task size",
+ 3, AdaptiveDocidRangeScheduler(num_threads, 3, 28))
+{
+ EXPECT_EQUAL(f1.first_range(thread_id).size(), 9u);
+ if (thread_id == 0) {
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ } else if (thread_id == 1) {
+ wait_idle(f1, 1);
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange(24,28)));
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ } else {
+ wait_idle(f1, 2);
+ verify_range(f1.share_range(thread_id, DocidRange(20,28)), DocidRange(20,24));
+ TEST_DO(verify_range(f1.next_range(thread_id), DocidRange()));
+ }
+ EXPECT_EQUAL(f1.total_size(0), 9u);
+ EXPECT_EQUAL(f1.total_size(1), 13u);
+ EXPECT_EQUAL(f1.total_size(2), 5u);
+}
+
+//-----------------------------------------------------------------------------
+
+TEST_MAIN() { TEST_RUN_ALL(); }