504 lines
20 KiB
C++
504 lines
20 KiB
C++
/*
|
|
Copyright (c) 2005-2022 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#include "common/test.h"
|
|
|
|
#include "tbb/blocked_range.h"
|
|
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
#include "common/utils_report.h"
|
|
|
|
namespace test_partitioner_utils {
|
|
|
|
struct RangeStatisticData {
|
|
// denotes the number of range objects
|
|
size_t m_rangeNum;
|
|
|
|
// store minimal and maximal range sizes (in terms of number of iterations)
|
|
size_t m_minRangeSize;
|
|
size_t m_maxRangeSize;
|
|
|
|
bool m_wasMinRangeSizeWritten; // shows whether relevant field was written or not
|
|
};
|
|
|
|
using tbb::split;
|
|
using tbb::proportional_split;
|
|
using tbb::blocked_range;
|
|
|
|
// helper for calculating number of range objects created before balancing phase is started
|
|
// and for finding maximum and minimum number of iterations among all such ranges
|
|
// Note: class does not provide exclusive access to members
|
|
class RangeStatisticCollector {
|
|
public:
|
|
RangeStatisticCollector(RangeStatisticData *statisticData) :
|
|
m_statData(statisticData)
|
|
{
|
|
m_called = false;
|
|
if (m_statData)
|
|
m_statData->m_rangeNum = 1;
|
|
}
|
|
|
|
// constructor is called from non-proportional split constructor of derived Range
|
|
RangeStatisticCollector(RangeStatisticCollector& sc, size_t rangeSize) {
|
|
if (!sc.m_called) {
|
|
// this is the first time non-proportional split constructor is called
|
|
// it means that work distribution phase has been completed and
|
|
// work balancing phase has been just started
|
|
sc.m_called = true;
|
|
|
|
if (sc.m_statData) {
|
|
size_t *minRangeSize = &sc.m_statData->m_minRangeSize;
|
|
if (*minRangeSize > rangeSize || !sc.m_statData->m_wasMinRangeSizeWritten) { // if minimum is not an actual minimum
|
|
*minRangeSize = rangeSize;
|
|
sc.m_statData->m_wasMinRangeSizeWritten = true;
|
|
}
|
|
size_t *maxRangeSize = &sc.m_statData->m_maxRangeSize;
|
|
if (*maxRangeSize < rangeSize) { // if maximum is not an actual maximum
|
|
*maxRangeSize = rangeSize;
|
|
}
|
|
}
|
|
}
|
|
*this = sc;
|
|
// constructor is used on work balancing phase only, so no need to increment
|
|
// number of range objects created
|
|
}
|
|
|
|
RangeStatisticCollector(RangeStatisticCollector& sc, proportional_split&) {
|
|
if (sc.m_statData)
|
|
sc.m_statData->m_rangeNum++;
|
|
*this = sc;
|
|
}
|
|
|
|
private:
|
|
RangeStatisticData *m_statData;
|
|
|
|
// turns to 'true' when non-proportional split constructor is called first time
|
|
bool m_called;
|
|
};
|
|
|
|
// Base class for fake ranges used in various tests for parallel
|
|
// algorithms as well as for partitioner
|
|
template <typename DerivedRange, typename T>
|
|
class RangeBase: public RangeStatisticCollector {
|
|
protected:
|
|
size_t my_begin, my_end;
|
|
bool m_provide_feedback;
|
|
bool m_ensure_non_empty_size;
|
|
public:
|
|
RangeBase(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeStatisticCollector(statData)
|
|
, my_begin(_begin), my_end(_end)
|
|
, m_provide_feedback(provide_feedback)
|
|
, m_ensure_non_empty_size(ensure_non_empty_size)
|
|
{ }
|
|
RangeBase(RangeBase& r, tbb::split) : RangeStatisticCollector(r, r.size()) {
|
|
*this = r;
|
|
size_t middle = r.my_begin + (r.my_end - r.my_begin) / 2u;
|
|
r.my_end = my_begin = middle;
|
|
}
|
|
|
|
RangeBase(RangeBase& r, proportional_split& p) : RangeStatisticCollector(r, p) {
|
|
*this = r;
|
|
size_t original_size = r.size();
|
|
T right = self().compute_right_part(r, p);
|
|
size_t right_part = self().round(right);
|
|
if( m_ensure_non_empty_size ) {
|
|
right_part = (original_size == right_part) ? (original_size - 1) : right_part;
|
|
right_part = (right_part != 0) ? right_part : 1;
|
|
}
|
|
r.my_end = my_begin = r.my_end - right_part;
|
|
if( m_ensure_non_empty_size )
|
|
CHECK_MESSAGE((r.my_end != r.my_begin && my_end != my_begin), "Incorrect range split");
|
|
}
|
|
|
|
size_t begin() const { return my_begin; }
|
|
size_t end() const { return my_end; }
|
|
bool is_divisible() const { return (my_end - my_begin) > 1; }
|
|
bool empty() const { return my_end == my_begin; }
|
|
size_t size() const { return my_end - my_begin; }
|
|
|
|
// helper methods (not part of the range concept)
|
|
DerivedRange& self() { return static_cast<DerivedRange&>(*this); }
|
|
size_t round(T part) { return size_t(part); }
|
|
T compute_right_part(RangeBase& r, proportional_split& p) {
|
|
return T(r.size() * T(p.right())) / T(p.left() + p.right());
|
|
}
|
|
bool is_ensure_non_emptiness() { return m_ensure_non_empty_size; }
|
|
};
|
|
|
|
namespace TestRanges {
|
|
/*
|
|
* RoundedUpRange rounds result up
|
|
* RoundedDownRange rounds result down
|
|
* Range1_2 forces proportion always to be 1:2 and rounds up
|
|
* Range1_999 uses weird proportion 1:999 and rounds up
|
|
* Range1_999 uses weird proportion 999:1 and rounds up
|
|
* BlockedRange uses tbb::blocked_range formula for proportion calculation
|
|
* InvertedProportionRange inverts proportion suggested by partitioner (e.g. 1:3 --> 3:1)
|
|
* ExactSplitRange uses integer arithmetic for accurate splitting
|
|
*/
|
|
|
|
class RoundedDownRange: public RangeBase<RoundedDownRange, float> {
|
|
public:
|
|
RoundedDownRange(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<RoundedDownRange, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
RoundedDownRange(RoundedDownRange& r, tbb::split)
|
|
: RangeBase<RoundedDownRange, float>(r, tbb::split()) { }
|
|
RoundedDownRange(RoundedDownRange& r, proportional_split& p)
|
|
: RangeBase<RoundedDownRange, float>(r, p) { }
|
|
// uses default implementation of RangeBase::round() which rounds down
|
|
};
|
|
|
|
class RoundedUpRange: public RangeBase<RoundedUpRange, float> {
|
|
public:
|
|
RoundedUpRange(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<RoundedUpRange, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
RoundedUpRange(RoundedUpRange& r, tbb::split)
|
|
: RangeBase<RoundedUpRange, float>(r, tbb::split()) { }
|
|
RoundedUpRange(RoundedUpRange& r, proportional_split& p)
|
|
: RangeBase<RoundedUpRange, float>(r, p) { }
|
|
size_t round(float part) { return size_t(std::ceil(part)); }
|
|
};
|
|
|
|
class Range1_2: public RangeBase<Range1_2, float> {
|
|
public:
|
|
Range1_2(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<Range1_2, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
Range1_2(Range1_2& r, tbb::split) : RangeBase<Range1_2, float>(r, tbb::split()) { }
|
|
Range1_2(Range1_2& r, proportional_split& p) : RangeBase<Range1_2, float>(r, p) { }
|
|
float compute_right_part(RangeBase<Range1_2, float>& r, proportional_split&) {
|
|
return float(r.size() * 2) / 3.0f;
|
|
}
|
|
// uses default implementation of RangeBase::round() which rounds down
|
|
};
|
|
|
|
class Range1_999: public RangeBase<Range1_999, float> {
|
|
public:
|
|
Range1_999(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<Range1_999, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
Range1_999(Range1_999& r, tbb::split) : RangeBase<Range1_999, float>(r, tbb::split()) { }
|
|
Range1_999(Range1_999& r, proportional_split& p) : RangeBase<Range1_999, float>(r, p) { }
|
|
float compute_right_part(RangeBase<Range1_999, float>& r, proportional_split&) {
|
|
return float(r.size() * 999) / 1000.0f;
|
|
}
|
|
// uses default implementation of RangeBase::round() which rounds down
|
|
};
|
|
|
|
class Range999_1: public RangeBase<Range999_1, float> {
|
|
public:
|
|
Range999_1(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<Range999_1, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
Range999_1(Range999_1& r, tbb::split) : RangeBase<Range999_1, float>(r, tbb::split()) { }
|
|
Range999_1(Range999_1& r, proportional_split& p) : RangeBase<Range999_1, float>(r, p) { }
|
|
float compute_right_part(RangeBase<Range999_1, float>& r, proportional_split&) {
|
|
return float(r.size()) / 1000.0f;
|
|
}
|
|
// uses default implementation of RangeBase::round() which rounds down
|
|
};
|
|
|
|
class BlockedRange: public RangeStatisticCollector, public blocked_range<size_t> {
|
|
public:
|
|
BlockedRange(size_t _begin, size_t _end, RangeStatisticData *statData, bool, bool)
|
|
: RangeStatisticCollector(statData), blocked_range<size_t>(_begin, _end) { }
|
|
BlockedRange(BlockedRange& r, split)
|
|
: RangeStatisticCollector(r, r.size()), blocked_range<size_t>(r, split()) { }
|
|
BlockedRange(BlockedRange& r, proportional_split& p)
|
|
: RangeStatisticCollector(r, p), blocked_range<size_t>(r, p) { }
|
|
bool is_ensure_non_emptiness() { return false; }
|
|
};
|
|
|
|
class InvertedProportionRange: public RangeBase<InvertedProportionRange, float> {
|
|
public:
|
|
InvertedProportionRange(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<InvertedProportionRange, float>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
InvertedProportionRange(InvertedProportionRange& r, split)
|
|
: RangeBase<InvertedProportionRange, float>(r, split()) { }
|
|
InvertedProportionRange(InvertedProportionRange& r, proportional_split& p)
|
|
: RangeBase<InvertedProportionRange, float>(r, p) { }
|
|
float compute_right_part(RangeBase<InvertedProportionRange, float>& r,
|
|
proportional_split& p) {
|
|
return float(r.size() * float(p.left())) / float(p.left() + p.right());
|
|
}
|
|
};
|
|
|
|
class ExactSplitRange: public RangeBase<ExactSplitRange, size_t> {
|
|
public:
|
|
ExactSplitRange(size_t _begin, size_t _end, RangeStatisticData *statData,
|
|
bool provide_feedback, bool ensure_non_empty_size)
|
|
: RangeBase<ExactSplitRange, size_t>(_begin, _end, statData, provide_feedback,
|
|
ensure_non_empty_size) { }
|
|
ExactSplitRange(ExactSplitRange& r, split)
|
|
: RangeBase<ExactSplitRange, size_t>(r, split()) { }
|
|
ExactSplitRange(ExactSplitRange& r, proportional_split& p)
|
|
: RangeBase<ExactSplitRange, size_t>(r, p) { }
|
|
size_t compute_right_part(RangeBase<ExactSplitRange, size_t>& r, proportional_split& p) {
|
|
size_t parts = size_t(p.left() + p.right());
|
|
size_t currSize = r.size();
|
|
size_t int_part = currSize / parts * p.right();
|
|
size_t remainder = currSize % parts * p.right();
|
|
int_part += remainder / parts;
|
|
remainder %= parts;
|
|
size_t right_part = int_part + (remainder > parts/2 ? 1 : 0);
|
|
return right_part;
|
|
}
|
|
};
|
|
|
|
} // namespace TestRanges
|
|
|
|
struct TreeNode {
|
|
size_t m_affinity;
|
|
size_t m_range_begin, m_range_end;
|
|
TreeNode *m_left, *m_right;
|
|
private:
|
|
TreeNode(size_t range_begin, size_t range_end, size_t affinity,
|
|
TreeNode* left, TreeNode* right)
|
|
: m_affinity(affinity), m_range_begin(range_begin), m_range_end(range_end),
|
|
m_left(left), m_right(right) { }
|
|
|
|
friend TreeNode* make_node(size_t range_begin, size_t range_end, size_t affinity,
|
|
TreeNode *left, TreeNode *right);
|
|
};
|
|
|
|
TreeNode* make_node(size_t range_begin, size_t range_end, size_t affinity,
|
|
TreeNode* left = nullptr, TreeNode* right = nullptr) {
|
|
CHECK_MESSAGE((range_begin <= range_end), "Incorrect range interval");
|
|
return new TreeNode(range_begin, range_end, affinity, left, right);
|
|
}
|
|
|
|
// Class stores nodes as a binary tree
|
|
// (marshals TreeNode objects in accordance with values of range intervals)
|
|
// Note: BinaryTree deletes all TreeNode objects pushed into it in a destruction phase
|
|
class BinaryTree {
|
|
public:
|
|
BinaryTree() : m_root(nullptr) { }
|
|
~BinaryTree() {
|
|
if (m_root)
|
|
remove_node_recursively(m_root);
|
|
}
|
|
|
|
// pushed node must be within subrange of the parent nodes
|
|
void push_node(TreeNode* node) {
|
|
if (!node)
|
|
return;
|
|
|
|
if (m_root) {
|
|
CHECK_MESSAGE((node->m_range_begin >= m_root->m_range_begin && node->m_range_end <= m_root->m_range_end),
|
|
"Cannot push node not from subrange");
|
|
}
|
|
|
|
push_subnode(m_root, node);
|
|
}
|
|
|
|
void visualize() {
|
|
if (!m_root) { // nothing to visualize
|
|
REPORT("Tree is empty\n");
|
|
return;
|
|
}
|
|
visualize_node(m_root);
|
|
}
|
|
|
|
bool operator ==(const BinaryTree& other_tree) const { return compare_nodes(m_root, other_tree.m_root); }
|
|
void fill_leafs(std::vector<TreeNode*>& leafs) const { fill_leafs_impl(m_root, leafs); }
|
|
|
|
private:
|
|
TreeNode *m_root;
|
|
|
|
void push_subnode(TreeNode *&root_node, TreeNode *node) {
|
|
if (!root_node) {
|
|
root_node = node;
|
|
return;
|
|
} else if (are_nodes_equal(root_node, node)) {
|
|
// no need to push the same node
|
|
return;
|
|
}
|
|
|
|
if (!has_children(root_node)) {
|
|
// if current root_node does not have children passed node
|
|
// should has one of the interval bounds to be equal to
|
|
// the same bound in the root_node
|
|
if (is_look_like_left_sibling(root_node, node))
|
|
push_subnode(root_node->m_left, node);
|
|
else
|
|
push_subnode(root_node->m_right, node);
|
|
return;
|
|
}
|
|
|
|
if (has_left_child(root_node)) {
|
|
if (is_subnode(root_node->m_left, node)) {
|
|
push_subnode(root_node->m_left, node);
|
|
return;
|
|
}
|
|
push_subnode(root_node->m_right, node);
|
|
return;
|
|
}
|
|
|
|
CHECK_MESSAGE(root_node->m_right != nullptr, "Right child is nullptr but must be present");
|
|
if (is_subnode(root_node->m_right, node)) {
|
|
push_subnode(root_node->m_right, node);
|
|
return;
|
|
}
|
|
push_subnode(root_node->m_left, node);
|
|
return;
|
|
}
|
|
|
|
bool has_children(TreeNode *node) { return node->m_left || node->m_right; }
|
|
|
|
bool is_look_like_left_sibling(TreeNode *root_node, TreeNode *node) {
|
|
if (root_node->m_range_begin == node->m_range_begin)
|
|
return true;
|
|
CHECK_MESSAGE(root_node->m_range_end == node->m_range_end, nullptr);
|
|
return false;
|
|
}
|
|
|
|
bool has_left_child(TreeNode *node) { return node->m_left != nullptr; }
|
|
|
|
bool is_subnode(TreeNode *root_node, TreeNode *node) {
|
|
return root_node->m_range_begin <= node->m_range_begin &&
|
|
node->m_range_end <= root_node->m_range_end;
|
|
}
|
|
|
|
bool are_nodes_equal(TreeNode *node1, TreeNode *node2) const {
|
|
return node1->m_range_begin == node2->m_range_begin &&
|
|
node1->m_range_end == node2->m_range_end;
|
|
}
|
|
|
|
void remove_node_recursively(TreeNode *node) {
|
|
if (node->m_left)
|
|
remove_node_recursively(node->m_left);
|
|
if (node->m_right)
|
|
remove_node_recursively(node->m_right);
|
|
delete node;
|
|
}
|
|
|
|
static void visualize_node(const TreeNode* node, unsigned indent = 0) {
|
|
// respecting indent
|
|
const char *indentStep = " ";
|
|
for (unsigned i = 0; i < indent; ++i)
|
|
REPORT("%s", indentStep);
|
|
|
|
size_t rangeSize = node->m_range_end - node->m_range_begin;
|
|
REPORT("[%llu, %llu)%%%llu@%llu\n", uint64_t(node->m_range_begin), uint64_t(node->m_range_end),
|
|
uint64_t(rangeSize), uint64_t(node->m_affinity));
|
|
|
|
if (node->m_left)
|
|
visualize_node(node->m_left, indent + 1);
|
|
if (node->m_right)
|
|
visualize_node(node->m_right, indent + 1);
|
|
}
|
|
|
|
bool compare_nodes(TreeNode* node1, TreeNode* node2) const {
|
|
if (node1 == nullptr && node2 == nullptr) return true;
|
|
if (node1 == nullptr || node2 == nullptr) return false;
|
|
return are_nodes_equal(node1, node2) && compare_nodes(node1->m_left, node2->m_left)
|
|
&& compare_nodes(node1->m_right, node2->m_right);
|
|
}
|
|
|
|
void fill_leafs_impl(TreeNode* node, std::vector<TreeNode*>& leafs) const {
|
|
if (node->m_left == nullptr && node->m_right == nullptr)
|
|
leafs.push_back(node);
|
|
if (node->m_left != nullptr) fill_leafs_impl(node->m_left, leafs);
|
|
if (node->m_right != nullptr) fill_leafs_impl(node->m_right, leafs);
|
|
}
|
|
};
|
|
|
|
class SimpleBody {
|
|
public:
|
|
SimpleBody() { }
|
|
template <typename Range>
|
|
void operator()(Range&) const { }
|
|
};
|
|
|
|
class SimpleReduceBody {
|
|
public:
|
|
SimpleReduceBody() { }
|
|
SimpleReduceBody(SimpleReduceBody&, tbb::split) { }
|
|
template <typename Range>
|
|
void operator()(Range&) { }
|
|
void join(SimpleReduceBody&) { }
|
|
};
|
|
|
|
namespace interaction_with_range_and_partitioner {
|
|
|
|
class SplitConstructorAssertedRange {
|
|
mutable bool is_divisible_called;
|
|
mutable bool is_empty_called;
|
|
bool my_assert_in_nonproportional, my_assert_in_proportional;
|
|
public:
|
|
SplitConstructorAssertedRange(bool assert_in_nonproportional, bool assert_in_proportional)
|
|
: is_divisible_called(false),
|
|
is_empty_called(false),
|
|
my_assert_in_nonproportional(assert_in_nonproportional),
|
|
my_assert_in_proportional(assert_in_proportional) { }
|
|
SplitConstructorAssertedRange(SplitConstructorAssertedRange& r, tbb::split) {
|
|
*this = r;
|
|
CHECK_MESSAGE( !my_assert_in_nonproportional, "Disproportional splitting constructor was called but should not been" );
|
|
}
|
|
SplitConstructorAssertedRange(SplitConstructorAssertedRange& r, proportional_split&) {
|
|
*this = r;
|
|
CHECK_MESSAGE( !my_assert_in_proportional, "Proportional splitting constructor was called but should not been" );
|
|
}
|
|
bool is_divisible() const {
|
|
if (!is_divisible_called) {
|
|
is_divisible_called = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool empty() const {
|
|
if (!is_empty_called) {
|
|
is_empty_called = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// proportional_split ctor defined
|
|
class Range1: public SplitConstructorAssertedRange {
|
|
public:
|
|
Range1(bool assert_in_nonproportional, bool assert_in_proportional)
|
|
: SplitConstructorAssertedRange(assert_in_nonproportional, assert_in_proportional) { }
|
|
Range1( Range1& r, tbb::split ) : SplitConstructorAssertedRange(r, tbb::split()) { }
|
|
Range1( Range1& r, proportional_split& proportion ) : SplitConstructorAssertedRange(r, proportion) { }
|
|
};
|
|
|
|
// proportional_split ctor is not defined
|
|
class Range6: public SplitConstructorAssertedRange {
|
|
public:
|
|
Range6(bool assert_in_nonproportional, bool assert_in_proportional)
|
|
: SplitConstructorAssertedRange(assert_in_nonproportional, assert_in_proportional) { }
|
|
Range6(Range6& r, tbb::split) : SplitConstructorAssertedRange(r, tbb::split()) { }
|
|
};
|
|
|
|
} // namespace interaction_with_range_and_partitioner
|
|
|
|
} // namespace test_partitioner_utils
|