summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeir Storli <geirst@vespa.ai>2024-04-15 15:14:22 +0200
committerGitHub <noreply@github.com>2024-04-15 15:14:22 +0200
commitd6a8d0ef6e75704546693843b9dfbe8518620e77 (patch)
tree1868be6c25443c44132a96d8099c95a3aaf07614
parentcb95c888950f1c4a1b27b46b921159a216b738d8 (diff)
parentf12988878b1aba79d69de8770a7aeb082f55117b (diff)
Merge pull request #30913 from vespa-engine/geirst/benchmarking-of-intermediate-blueprints
Add benchmarking of intermediate blueprints with configurable childreā€¦
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/CMakeLists.txt1
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.cpp3
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.h1
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/common.cpp33
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/common.h2
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.cpp78
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.h39
-rw-r--r--searchlib/src/tests/queryeval/iterator_benchmark/iterator_benchmark_test.cpp235
8 files changed, 350 insertions, 42 deletions
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/CMakeLists.txt b/searchlib/src/tests/queryeval/iterator_benchmark/CMakeLists.txt
index 872fb4ca6ca..dadd06ee7cd 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/CMakeLists.txt
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/CMakeLists.txt
@@ -1,6 +1,7 @@
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_executable(searchlib_iterator_benchmark_test_app TEST
SOURCES
+ intermediate_blueprint_factory.cpp
attribute_ctx_builder.cpp
benchmark_blueprint_factory.cpp
common.cpp
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.cpp b/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.cpp
index 0496a0e6dc8..504ad057d3d 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.cpp
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.cpp
@@ -162,6 +162,9 @@ public:
double op_hit_ratio, uint32_t children, bool disjunct_children);
std::unique_ptr<Blueprint> make_blueprint() override;
+ vespalib::string get_name(Blueprint& blueprint) const override {
+ return get_class_name(blueprint);
+ }
};
MyFactory::MyFactory(const FieldConfig& field_cfg, QueryOperator query_op,
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.h b/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.h
index 423f517ffb0..2a90dbbbef8 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.h
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/benchmark_blueprint_factory.h
@@ -16,6 +16,7 @@ class BenchmarkBlueprintFactory {
public:
virtual ~BenchmarkBlueprintFactory() = default;
virtual std::unique_ptr<Blueprint> make_blueprint() = 0;
+ virtual vespalib::string get_name(Blueprint& blueprint) const = 0;
};
std::unique_ptr<BenchmarkBlueprintFactory>
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/common.cpp b/searchlib/src/tests/queryeval/iterator_benchmark/common.cpp
index c67a5ee1074..d2b5ec2cb8b 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/common.cpp
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/common.cpp
@@ -1,6 +1,7 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "common.h"
+#include <vespa/searchlib/queryeval/blueprint.h>
#include <sstream>
using search::attribute::CollectionType;
@@ -42,6 +43,38 @@ to_string(QueryOperator query_op)
namespace {
+std::string
+delete_substr_from(const std::string& source, const std::string& substr)
+{
+ std::string res = source;
+ auto i = res.find(substr);
+ while (i != std::string::npos) {
+ res.erase(i, substr.length());
+ i = res.find(substr, i);
+ }
+ return res;
+}
+
+}
+
+vespalib::string
+get_class_name(const auto& obj)
+{
+ auto res = obj.getClassName();
+ res = delete_substr_from(res, "search::attribute::");
+ res = delete_substr_from(res, "search::queryeval::");
+ res = delete_substr_from(res, "vespalib::btree::");
+ res = delete_substr_from(res, "search::");
+ res = delete_substr_from(res, "vespalib::");
+ res = delete_substr_from(res, "anonymous namespace");
+ return res;
+}
+
+template vespalib::string get_class_name<Blueprint>(const Blueprint& obj);
+template vespalib::string get_class_name<SearchIterator>(const SearchIterator& obj);
+
+namespace {
+
// TODO: Make seed configurable.
constexpr uint32_t default_seed = 1234;
std::mt19937 gen(default_seed);
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/common.h b/searchlib/src/tests/queryeval/iterator_benchmark/common.h
index bf16e6f51d7..6341b16e96a 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/common.h
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/common.h
@@ -79,6 +79,8 @@ public:
auto end() const { return _specs.end(); }
};
+vespalib::string get_class_name(const auto& obj);
+
std::mt19937& get_gen();
BitVector::UP random_docids(uint32_t docid_limit, uint32_t count);
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.cpp b/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.cpp
new file mode 100644
index 00000000000..ad8c0b1008e
--- /dev/null
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.cpp
@@ -0,0 +1,78 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "intermediate_blueprint_factory.h"
+#include <vespa/searchlib/queryeval/intermediate_blueprints.h>
+#include <sstream>
+
+namespace search::queryeval::test {
+
+template <typename BlueprintType>
+char
+IntermediateBlueprintFactory<BlueprintType>::child_name(void* blueprint) const
+{
+ auto itr = _child_names.find(blueprint);
+ if (itr != _child_names.end()) {
+ return itr->second;
+ }
+ return '?';
+}
+
+template <typename BlueprintType>
+IntermediateBlueprintFactory<BlueprintType>::IntermediateBlueprintFactory(vespalib::stringref name)
+ : _name(name),
+ _children(),
+ _child_names()
+{
+}
+
+template <typename BlueprintType>
+IntermediateBlueprintFactory<BlueprintType>::~IntermediateBlueprintFactory() = default;
+
+template <typename BlueprintType>
+std::unique_ptr<Blueprint>
+IntermediateBlueprintFactory<BlueprintType>::make_blueprint()
+{
+ auto res = std::make_unique<BlueprintType>();
+ _child_names.clear();
+ char name = 'A';
+ for (const auto& factory : _children) {
+ auto child = factory->make_blueprint();
+ _child_names[child.get()] = name++;
+ res->addChild(std::move(child));
+ }
+ return res;
+}
+
+template <typename BlueprintType>
+vespalib::string
+IntermediateBlueprintFactory<BlueprintType>::get_name(Blueprint& blueprint) const
+{
+ auto* intermediate = blueprint.asIntermediate();
+ if (intermediate != nullptr) {
+ std::ostringstream oss;
+ bool first = true;
+ oss << _name << "[";
+ for (size_t i = 0; i < intermediate->childCnt(); ++i) {
+ auto* child = &intermediate->getChild(i);
+ oss << (first ? "" : ",") << child_name(child) << ".";
+ if (child->strict()) {
+ oss << "s(" << std::setw(6) << std::setprecision(3) << child->strict_cost() << ")";
+ } else {
+ oss << "n(" << std::setw(6) << std::setprecision(3) << child->cost() << ")";
+ }
+ first = false;
+ }
+ oss << "]";
+ return oss.str();
+ }
+ return get_class_name(blueprint);
+}
+
+template class IntermediateBlueprintFactory<AndBlueprint>;
+
+AndBlueprintFactory::AndBlueprintFactory()
+ : IntermediateBlueprintFactory<AndBlueprint>("AND")
+{}
+
+}
+
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.h b/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.h
new file mode 100644
index 00000000000..6f7fe4f9ee7
--- /dev/null
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/intermediate_blueprint_factory.h
@@ -0,0 +1,39 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "benchmark_blueprint_factory.h"
+#include <vespa/searchlib/queryeval/intermediate_blueprints.h>
+#include <unordered_map>
+
+namespace search::queryeval::test {
+
+/**
+ * Factory that creates an IntermediateBlueprint (of the given type) with children created by the given factories.
+ */
+template <typename BlueprintType>
+class IntermediateBlueprintFactory : public BenchmarkBlueprintFactory {
+private:
+ vespalib::string _name;
+ std::vector<std::shared_ptr<BenchmarkBlueprintFactory>> _children;
+ std::unordered_map<void*, char> _child_names;
+
+ char child_name(void* blueprint) const;
+
+public:
+ IntermediateBlueprintFactory(vespalib::stringref name);
+ ~IntermediateBlueprintFactory();
+ void add_child(std::shared_ptr<BenchmarkBlueprintFactory> child) {
+ _children.push_back(std::move(child));
+ }
+ std::unique_ptr<Blueprint> make_blueprint() override;
+ vespalib::string get_name(Blueprint& blueprint) const override;
+};
+
+class AndBlueprintFactory : public IntermediateBlueprintFactory<AndBlueprint> {
+public:
+ AndBlueprintFactory();
+};
+
+}
+
diff --git a/searchlib/src/tests/queryeval/iterator_benchmark/iterator_benchmark_test.cpp b/searchlib/src/tests/queryeval/iterator_benchmark/iterator_benchmark_test.cpp
index c6dae52fd69..f82b9804720 100644
--- a/searchlib/src/tests/queryeval/iterator_benchmark/iterator_benchmark_test.cpp
+++ b/searchlib/src/tests/queryeval/iterator_benchmark/iterator_benchmark_test.cpp
@@ -1,5 +1,6 @@
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "intermediate_blueprint_factory.h"
#include "benchmark_blueprint_factory.h"
#include "common.h"
#include <vespa/searchlib/fef/matchdata.h>
@@ -25,6 +26,25 @@ using vespalib::make_string_short::fmt;
const vespalib::string field_name = "myfield";
double budget_sec = 1.0;
+enum class PlanningAlgo {
+ Order,
+ Estimate,
+ Cost,
+ CostForceStrict
+};
+
+vespalib::string
+to_string(PlanningAlgo algo)
+{
+ switch (algo) {
+ case PlanningAlgo::Order: return "ordr";
+ case PlanningAlgo::Estimate: return "esti";
+ case PlanningAlgo::Cost: return "cost";
+ case PlanningAlgo::CostForceStrict: return "forc";
+ }
+ return "unknown";
+}
+
struct BenchmarkResult {
double time_ms;
uint32_t seeks;
@@ -127,31 +147,6 @@ public:
}
};
-std::string
-delete_substr_from(const std::string& source, const std::string& substr)
-{
- std::string res = source;
- auto i = res.find(substr);
- while (i != std::string::npos) {
- res.erase(i, substr.length());
- i = res.find(substr, i);
- }
- return res;
-}
-
-vespalib::string
-get_class_name(const auto& obj)
-{
- auto res = obj.getClassName();
- res = delete_substr_from(res, "search::attribute::");
- res = delete_substr_from(res, "search::queryeval::");
- res = delete_substr_from(res, "vespalib::btree::");
- res = delete_substr_from(res, "search::");
- res = delete_substr_from(res, "vespalib::");
- res = delete_substr_from(res, "anonymous namespace");
- return res;
-}
-
struct MatchLoopContext {
Blueprint::UP blueprint;
MatchData::UP match_data;
@@ -174,12 +169,37 @@ struct MatchLoopContext {
MatchLoopContext::~MatchLoopContext() = default;
+Blueprint::Options
+to_sort_options(PlanningAlgo algo)
+{
+ Blueprint::Options opts;
+ if (algo == PlanningAlgo::Order) {
+ opts.keep_order(true);
+ } else if (algo == PlanningAlgo::Cost) {
+ opts.sort_by_cost(true);
+ } else if (algo == PlanningAlgo::CostForceStrict) {
+ opts.sort_by_cost(true).allow_force_strict(true);
+ }
+ return opts;
+}
+
+void
+sort_blueprint(Blueprint& blueprint, InFlow in_flow, uint32_t docid_limit, Blueprint::Options opts)
+{
+ auto opts_guard = blueprint.bind_opts(opts);
+ blueprint.setDocIdLimit(docid_limit);
+ blueprint.each_node_post_order([docid_limit](Blueprint &bp){
+ bp.update_flow_stats(docid_limit);
+ });
+ blueprint.sort(in_flow);
+}
+
MatchLoopContext
-make_match_loop_context(BenchmarkBlueprintFactory& factory, InFlow in_flow, uint32_t docid_limit)
+make_match_loop_context(BenchmarkBlueprintFactory& factory, InFlow in_flow, uint32_t docid_limit, PlanningAlgo algo)
{
auto blueprint = factory.make_blueprint();
assert(blueprint);
- blueprint->basic_plan(in_flow, docid_limit);
+ sort_blueprint(*blueprint, in_flow, docid_limit, to_sort_options(algo));
blueprint->fetchPostings(ExecuteInfo::FULL);
// Note: All blueprints get the same TermFieldMatchData instance.
// This is OK as long as we don't do unpacking and only use 1 thread.
@@ -191,13 +211,13 @@ make_match_loop_context(BenchmarkBlueprintFactory& factory, InFlow in_flow, uint
template <bool do_unpack>
BenchmarkResult
-strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit)
+strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, PlanningAlgo algo)
{
BenchmarkTimer timer(budget_sec);
uint32_t hits = 0;
MatchLoopContext ctx;
while (timer.has_budget()) {
- ctx = make_match_loop_context(factory, true, docid_limit);
+ ctx = make_match_loop_context(factory, true, docid_limit, algo);
auto* itr = ctx.iterator.get();
timer.before();
hits = 0;
@@ -216,12 +236,12 @@ strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit)
timer.after();
}
FlowStats flow(ctx.blueprint->estimate(), ctx.blueprint->cost(), ctx.blueprint->strict_cost());
- return {timer.min_time() * 1000.0, hits + 1, hits, flow, flow.strict_cost, get_class_name(*ctx.iterator), get_class_name(*ctx.blueprint)};
+ return {timer.min_time() * 1000.0, hits + 1, hits, flow, flow.strict_cost, get_class_name(*ctx.iterator), factory.get_name(*ctx.blueprint)};
}
template <bool do_unpack>
BenchmarkResult
-non_strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, double filter_hit_ratio, bool force_strict)
+non_strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, double filter_hit_ratio, bool force_strict, PlanningAlgo algo)
{
BenchmarkTimer timer(budget_sec);
uint32_t seeks = 0;
@@ -231,7 +251,7 @@ non_strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, doub
uint32_t docid_skip = 1.0 / filter_hit_ratio;
MatchLoopContext ctx;
while (timer.has_budget()) {
- ctx = make_match_loop_context(factory, InFlow(force_strict, filter_hit_ratio), docid_limit);
+ ctx = make_match_loop_context(factory, InFlow(force_strict, filter_hit_ratio), docid_limit, algo);
auto* itr = ctx.iterator.get();
timer.before();
seeks = 0;
@@ -250,27 +270,31 @@ non_strict_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, doub
}
FlowStats flow(ctx.blueprint->estimate(), ctx.blueprint->cost(), ctx.blueprint->strict_cost());
double actual_cost = flow.cost * filter_hit_ratio;
- return {timer.min_time() * 1000.0, seeks, hits, flow, actual_cost, get_class_name(*ctx.iterator), get_class_name(*ctx.blueprint)};
+ return {timer.min_time() * 1000.0, seeks, hits, flow, actual_cost, get_class_name(*ctx.iterator), factory.get_name(*ctx.blueprint)};
}
BenchmarkResult
-benchmark_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, bool strict_context, bool force_strict, bool unpack_iterator, double filter_hit_ratio)
+benchmark_search(BenchmarkBlueprintFactory& factory, uint32_t docid_limit, bool strict_context, bool force_strict, bool unpack_iterator, double filter_hit_ratio, PlanningAlgo algo)
{
if (strict_context) {
if (unpack_iterator) {
- return strict_search<true>(factory, docid_limit);
+ return strict_search<true>(factory, docid_limit, algo);
} else {
- return strict_search<false>(factory, docid_limit);
+ return strict_search<false>(factory, docid_limit, algo);
}
} else {
if (unpack_iterator) {
- return non_strict_search<true>(factory, docid_limit, filter_hit_ratio, force_strict);
+ return non_strict_search<true>(factory, docid_limit, filter_hit_ratio, force_strict, algo);
} else {
- return non_strict_search<false>(factory, docid_limit, filter_hit_ratio, force_strict);
+ return non_strict_search<false>(factory, docid_limit, filter_hit_ratio, force_strict, algo);
}
}
}
+
+
+
+
//-----------------------------------------------------------------------------
double est_forced_strict_cost(double estimate, double strict_cost, double rate) {
@@ -343,12 +367,12 @@ void analyze_crossover(BenchmarkBlueprintFactory &fixed, std::function<std::uniq
auto a = first.make_blueprint();
a->basic_plan(true, docid_limit);
double est_a = a->estimate();
- double a_ms = benchmark_search(first, docid_limit, true, false, false, 1.0).time_ms;
- double b_ms = benchmark_search(last, docid_limit, false, false, false, est_a).time_ms;
+ double a_ms = benchmark_search(first, docid_limit, true, false, false, 1.0, PlanningAlgo::Cost).time_ms;
+ double b_ms = benchmark_search(last, docid_limit, false, false, false, est_a, PlanningAlgo::Cost).time_ms;
if (!allow_force_strict) {
return Sample(a_ms + b_ms);
}
- double c_ms = benchmark_search(last, docid_limit, false, true, false, est_a).time_ms;
+ double c_ms = benchmark_search(last, docid_limit, false, true, false, est_a, PlanningAlgo::Cost).time_ms;
if (c_ms < b_ms) {
return Sample(a_ms + c_ms, a_ms + b_ms, true);
}
@@ -615,7 +639,7 @@ run_benchmark_case(const BenchmarkCaseSetup& setup)
for (double filter_hit_ratio : setup.filter_hit_ratios) {
if (filter_hit_ratio * setup.filter_crossover_factor <= op_hit_ratio) {
auto res = benchmark_search(*factory, setup.num_docs + 1,
- setup.bcase.strict_context, setup.bcase.force_strict, setup.bcase.unpack_iterator, filter_hit_ratio);
+ setup.bcase.strict_context, setup.bcase.force_strict, setup.bcase.unpack_iterator, filter_hit_ratio, PlanningAlgo::Cost);
print_result(res, children, op_hit_ratio, filter_hit_ratio, setup.num_docs);
result.add(res);
}
@@ -650,6 +674,119 @@ run_benchmarks(const BenchmarkSetup& setup)
print_summary(summary);
}
+//---------------------------------------------------------------------------------------
+// Tools for benchmarking root intermediate blueprints with configurable children setups.
+//---------------------------------------------------------------------------------------
+
+void
+print_intermediate_blueprint_result_header(size_t children)
+{
+ // This matches the naming scheme in IntermediateBlueprintFactory.
+ char name = 'A';
+ for (size_t i = 0; i < children; ++i) {
+ std::cout << "| " << name++ << ".ratio ";
+ }
+ std::cout << "| flow.cost | flow.scost | flow.est | ratio | hits | seeks | ms_per_cost | time_ms | algo | blueprint |" << std::endl;
+}
+
+void
+print_intermediate_blueprint_result(const BenchmarkResult& res, const std::vector<double>& children_ratios, PlanningAlgo algo, uint32_t num_docs)
+{
+ std::cout << std::fixed << std::setprecision(5);
+ for (auto ratio : children_ratios) {
+ std::cout << "| " << std::setw(7) << ratio << " ";
+ }
+ std::cout << std::setprecision(5)
+ << "| " << std::setw(10) << res.flow.cost
+ << " | " << std::setw(10) << res.flow.strict_cost
+ << " | " << std::setw(8) << res.flow.estimate
+ << " | " << std::setw(7) << ((double) res.hits / (double) num_docs)
+ << std::setprecision(4)
+ << " | " << std::setw(8) << res.hits
+ << " | " << std::setw(8) << res.seeks
+ << std::setprecision(3)
+ << " | " << std::setw(11) << res.ms_per_actual_cost()
+ << " | " << std::setw(8) << res.time_ms
+ << " | " << to_string(algo)
+ << " | " << res.blueprint_name << " |" << std::endl;
+}
+
+struct BlueprintFactorySetup {
+ FieldConfig field_cfg;
+ QueryOperator query_op;
+ std::vector<double> op_hit_ratios;
+ uint32_t children;
+ bool disjunct_children;
+ uint32_t default_values_per_document;
+
+ BlueprintFactorySetup(const FieldConfig& field_cfg_in, QueryOperator query_op_in, const std::vector<double>& op_hit_ratios_in)
+ : BlueprintFactorySetup(field_cfg_in, query_op_in, op_hit_ratios_in, 1, false)
+ {}
+ BlueprintFactorySetup(const FieldConfig& field_cfg_in, QueryOperator query_op_in, const std::vector<double>& op_hit_ratios_in,
+ uint32_t children_in, bool disjunct_children_in)
+ : field_cfg(field_cfg_in),
+ query_op(query_op_in),
+ op_hit_ratios(op_hit_ratios_in),
+ children(children_in),
+ disjunct_children(disjunct_children_in),
+ default_values_per_document(0)
+ {}
+ ~BlueprintFactorySetup();
+ std::unique_ptr<BenchmarkBlueprintFactory> make_factory(size_t num_docs, double op_hit_ratio) const {
+ return make_blueprint_factory(field_cfg, query_op, num_docs, default_values_per_document, op_hit_ratio, children, disjunct_children);
+ }
+ std::shared_ptr<BenchmarkBlueprintFactory> make_factory_shared(size_t num_docs, double op_hit_ratio) const {
+ return std::shared_ptr<BenchmarkBlueprintFactory>(make_factory(num_docs, op_hit_ratio));
+ }
+};
+
+BlueprintFactorySetup::~BlueprintFactorySetup() = default;
+
+template <typename IntermediateBlueprintFactoryType>
+void
+run_intermediate_blueprint_benchmark(const BlueprintFactorySetup& a, const BlueprintFactorySetup& b, size_t num_docs)
+{
+ print_intermediate_blueprint_result_header(2);
+ for (double b_hit_ratio: b.op_hit_ratios) {
+ auto b_factory = b.make_factory_shared(num_docs, b_hit_ratio);
+ for (double a_hit_ratio : a.op_hit_ratios) {
+ IntermediateBlueprintFactoryType factory;
+ factory.add_child(a.make_factory(num_docs, a_hit_ratio));
+ factory.add_child(b_factory);
+ for (auto algo: {PlanningAlgo::Order, PlanningAlgo::Estimate, PlanningAlgo::Cost, PlanningAlgo::CostForceStrict}) {
+ auto res = benchmark_search(factory, num_docs + 1, true, false, false, 1.0, algo);
+ print_intermediate_blueprint_result(res, {a_hit_ratio, b_hit_ratio}, algo, num_docs);
+ }
+ std::cout << std::endl;
+ }
+ }
+}
+
+void
+run_and_benchmark(const BlueprintFactorySetup& a, const BlueprintFactorySetup& b, size_t num_docs)
+{
+ run_intermediate_blueprint_benchmark<AndBlueprintFactory>(a, b, num_docs);
+}
+
+//-------------------------------------------------------------------------------------
+
+std::vector<double>
+gen_ratios(double middle, double range_multiplier, size_t num_samples)
+{
+ double lower = middle / range_multiplier;
+ double upper = middle * range_multiplier;
+ // Solve the following equation:
+ // lower * (factor ^ (num_samples - 1)) = upper;
+ double factor = std::pow(upper / lower, 1.0 / (num_samples - 1));
+ std::vector<double> res;
+ double ratio = lower;
+ for (size_t i = 0; i < num_samples; ++i) {
+ res.push_back(ratio);
+ ratio *= factor;
+ }
+ return res;
+}
+
FieldConfig
make_attr_config(BasicType basic_type, CollectionType col_type, bool fast_search)
{
@@ -790,6 +927,20 @@ TEST(IteratorBenchmark, or_vs_filter_crossover_with_allow_force_strict)
analyze_crossover(*fixed_or, variable_term, num_docs + 1, true, 0.0001);
}
+TEST(IteratorBenchmark, analyze_and_with_filter_vs_in)
+{
+ run_and_benchmark({int32_fs, QueryOperator::Term, gen_ratios(0.1, 8.0, 15)},
+ {int32_fs, QueryOperator::In, {0.1}, 100, false},
+ num_docs);
+}
+
+TEST(IteratorBenchmark, analyze_and_with_filter_vs_or)
+{
+ run_and_benchmark({int32_fs, QueryOperator::Term, gen_ratios(0.1, 8.0, 15)},
+ {int32_fs, QueryOperator::Or, {0.1}, 100, false},
+ num_docs);
+}
+
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
int res = RUN_ALL_TESTS();