@@ -185,6 +185,8 @@ distbubbletonic: CFLAGS := $(REL_CFLAGS) -DCODE_VERSION=BUBBLETONIC | |||||
distbubbletonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC | distbubbletonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC | ||||
distbubbletonic: TARGET := distbubbletonic | distbubbletonic: TARGET := distbubbletonic | ||||
distbubbletonic: $(BUILD_DIR)/$(TARGET) | distbubbletonic: $(BUILD_DIR)/$(TARGET) | ||||
@mkdir -p out | |||||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||||
distbitonic: CC := mpicc | distbitonic: CC := mpicc | ||||
distbitonic: CXX := mpic++ | distbitonic: CXX := mpic++ | ||||
@@ -192,6 +194,8 @@ distbitonic: CFLAGS := $(REL_CFLAGS) -DCODE_VERSION=BITONIC | |||||
distbitonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BITONIC | distbitonic: CXXFLAGS := $(REL_CXXFLAGS) -DCODE_VERSION=BITONIC | ||||
distbitonic: TARGET := distbitonic | distbitonic: TARGET := distbitonic | ||||
distbitonic: $(BUILD_DIR)/$(TARGET) | distbitonic: $(BUILD_DIR)/$(TARGET) | ||||
@mkdir -p out | |||||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||||
deb_distbubbletonic: CC := mpicc | deb_distbubbletonic: CC := mpicc | ||||
deb_distbubbletonic: CXX := mpic++ | deb_distbubbletonic: CXX := mpic++ | ||||
@@ -199,6 +203,8 @@ deb_distbubbletonic: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BUBBLETONIC -DDEBUG | |||||
deb_distbubbletonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC -DDEBUG | deb_distbubbletonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BUBBLETONIC -DDEBUG | ||||
deb_distbubbletonic: TARGET := deb_distbubbletonic | deb_distbubbletonic: TARGET := deb_distbubbletonic | ||||
deb_distbubbletonic: $(BUILD_DIR)/$(TARGET) | deb_distbubbletonic: $(BUILD_DIR)/$(TARGET) | ||||
@mkdir -p out | |||||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||||
deb_distbitonic: CC := mpicc | deb_distbitonic: CC := mpicc | ||||
deb_distbitonic: CXX := mpic++ | deb_distbitonic: CXX := mpic++ | ||||
@@ -206,6 +212,8 @@ deb_distbitonic: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BITONIC -DDEBUG | |||||
deb_distbitonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG | deb_distbitonic: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG | ||||
deb_distbitonic: TARGET := deb_distbitonic | deb_distbitonic: TARGET := deb_distbitonic | ||||
deb_distbitonic: $(BUILD_DIR)/$(TARGET) | deb_distbitonic: $(BUILD_DIR)/$(TARGET) | ||||
@mkdir -p out | |||||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||||
tests: CC := mpicc | tests: CC := mpicc | ||||
tests: CXX := mpic++ | tests: CXX := mpic++ | ||||
@@ -213,6 +221,14 @@ tests: CFLAGS := $(DEB_CFLAGS) -DCODE_VERSION=BITONIC -DDEBUG -DTESTING | |||||
tests: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG -DTESTING | tests: CXXFLAGS := $(DEB_CXXFLAGS) -DCODE_VERSION=BITONIC -DDEBUG -DTESTING | ||||
tests: TARGET := tests | tests: TARGET := tests | ||||
tests: $(BUILD_DIR)/$(TARGET) | tests: $(BUILD_DIR)/$(TARGET) | ||||
@mkdir -p out | |||||
cp $(BUILD_DIR)/$(TARGET) out/$(TARGET) | |||||
measurements: | |||||
make clean | |||||
make distbubbletonic | |||||
make clean | |||||
make distbitonic | |||||
all: debug distbubbletonic distbitonic | all: debug distbubbletonic distbitonic | ||||
@@ -25,7 +25,7 @@ | |||||
#endif | #endif | ||||
// Value type selection | // Value type selection | ||||
using distValue_t = uint8_t; | |||||
using distValue_t = uint32_t; | |||||
/*! | /*! | ||||
* Session option for each invocation of the executable | * Session option for each invocation of the executable | ||||
@@ -45,8 +45,8 @@ template <SortMode Mode> inline bool ascending(mpi_id_t, [[maybe_unused]] size_t | |||||
* Returns the ascending or descending configuration of the node's sequence based on | * Returns the ascending or descending configuration of the node's sequence based on | ||||
* the current node (MPI process) and the depth of the sorting network | * the current node (MPI process) and the depth of the sorting network | ||||
* | * | ||||
* @param node The current node (MPI process) | |||||
* @return True if we need ascending configuration, false otherwise | |||||
* @param node [mpi_id_t] The current node (MPI process) | |||||
* @return [bool] True if we need ascending configuration, false otherwise | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { | bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { | ||||
@@ -57,10 +57,9 @@ bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t dep | |||||
* Returns the ascending or descending configuration of the node's sequence based on | * Returns the ascending or descending configuration of the node's sequence based on | ||||
* the current node (MPI process) and the depth of the sorting network | * the current node (MPI process) and the depth of the sorting network | ||||
* | * | ||||
* @param node The current node (MPI process) | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* | |||||
* @return True if we need ascending configuration, false otherwise | |||||
* @param node [mpi_id_t] The current node (MPI process) | |||||
* @param depth [size_t] The total depth of the sorting network (same for each step for a given network) | |||||
* @return [bool] True if we need ascending configuration, false otherwise | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept { | bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept { | ||||
@@ -77,9 +76,9 @@ template <SortMode Mode> inline mpi_id_t partner(mpi_id_t, size_t) noexcept = de | |||||
* Returns the node's partner for data exchange during the sorting network iterations | * Returns the node's partner for data exchange during the sorting network iterations | ||||
* of Bubbletonic | * of Bubbletonic | ||||
* | * | ||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
* @param node [mpi_id_t] The current node | |||||
* @param step [size_t] The step of the sorting network | |||||
* @return [mpi_id_t] The node id of the partner for data exchange | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | ||||
@@ -91,9 +90,9 @@ mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | |||||
* Returns the node's partner for data exchange during the sorting network iterations | * Returns the node's partner for data exchange during the sorting network iterations | ||||
* of Bitonic | * of Bitonic | ||||
* | * | ||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
* @param node [mpi_id_t] The current node | |||||
* @param step [size_t] The step of the sorting network | |||||
* @return [mpi_id_t] The node id of the partner for data exchange | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | ||||
@@ -105,32 +104,34 @@ mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | |||||
* The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized | * The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized | ||||
* for each of the \c SortMode | * for each of the \c SortMode | ||||
*/ | */ | ||||
template<SortMode Mode> inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; | |||||
template<SortMode Mode> inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) = delete; | |||||
/*! | /*! | ||||
* Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. | * Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. | ||||
* | * | ||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @return True if the node should keep the small values, false otherwise | |||||
* @param node [mpi_id_t] The node for which we check | |||||
* @param partner [mpi_id_t] The partner of the data exchange | |||||
* @return [bool] True if the node should keep the small values, false otherwise | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { | |||||
assert(node != partner); | |||||
bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) { | |||||
if (node == partner) | |||||
throw std::runtime_error("(keepSmall) Node and Partner can not be the same\n"); | |||||
return (node < partner); | return (node < partner); | ||||
} | } | ||||
/*! | /*! | ||||
* Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. | * Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. | ||||
* | * | ||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* @return True if the node should keep the small values, false otherwise | |||||
* @param node [mpi_id_t] The node for which we check | |||||
* @param partner [mpi_id_t] The partner of the data exchange | |||||
* @param depth [size_t] The total depth of the sorting network (same for each step for a given network) | |||||
* @return [bool] True if the node should keep the small values, false otherwise | |||||
*/ | */ | ||||
template <> inline | template <> inline | ||||
bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { | |||||
assert(node != partner); | |||||
bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) { | |||||
if (node == partner) | |||||
throw std::runtime_error("(keepSmall) Node and Partner can not be the same\n"); | |||||
return ascending<SortMode::Bitonic>(node, depth) == (node < partner); | return ascending<SortMode::Bitonic>(node, depth) == (node < partner); | ||||
} | } | ||||
@@ -138,24 +139,26 @@ bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) | |||||
* Predicate to check if the node is active in the current iteration of the bubbletonic | * Predicate to check if the node is active in the current iteration of the bubbletonic | ||||
* sort exchange. | * sort exchange. | ||||
* | * | ||||
* @param node The node to check | |||||
* @param nodes The total number of nodes | |||||
* @return True if the node is active, false otherwise | |||||
* @param node [mpi_id_t] The node to check | |||||
* @param nodes [size_t] The total number of nodes | |||||
* @return [bool] True if the node is active, false otherwise | |||||
*/ | */ | ||||
bool isActive(mpi_id_t node, size_t nodes) noexcept; | |||||
bool isActive(mpi_id_t node, size_t nodes); | |||||
/* | /* | ||||
* ============================== Data utilities ============================== | * ============================== Data utilities ============================== | ||||
*/ | */ | ||||
/*! | /*! | ||||
* Sort a range using the build-in O(Nlog(N)) algorithm | |||||
* | |||||
* @tparam RangeT A range type with random access iterator | |||||
* | * | ||||
* @tparam RangeT | |||||
* @param data | |||||
* @param ascending | |||||
* @param data [RangeT] The data to be sorted | |||||
* @param ascending [bool] Flag to indicate the sorting order | |||||
*/ | */ | ||||
template<typename RangeT> | template<typename RangeT> | ||||
void fullSort(RangeT& data, bool ascending) { | |||||
void fullSort(RangeT& data, bool ascending) noexcept { | |||||
// Use introsort from stdlib++ here, unless ... | // Use introsort from stdlib++ here, unless ... | ||||
if (ascending) | if (ascending) | ||||
std::sort(data.begin(), data.end(), std::less<>()); | std::sort(data.begin(), data.end(), std::less<>()); | ||||
@@ -164,66 +167,87 @@ void fullSort(RangeT& data, bool ascending) { | |||||
} | } | ||||
/*! | /*! | ||||
* Core functionality of sort for shadowed buffer types using | |||||
* the "elbow sort" algorithm. | |||||
* | |||||
* @note: | |||||
* This algorithm can not work "in place". | |||||
* We use the active buffer as source and the shadow as target. | |||||
* At the end we switch which buffer is active and which is the shadow. | |||||
* @note | |||||
* This is the core functionality. Use the elbowSort() function instead | |||||
* | |||||
* @tparam ShadowedT A Shadowed buffer type with random access iterator. | |||||
* @tparam CompT A Comparison type for binary operation comparisons | |||||
* | * | ||||
* @tparam ShadowedT | |||||
* @tparam CompT | |||||
* @param data | |||||
* @param comp | |||||
* @param data [ShadowedT] The data to sort | |||||
* @param ascending [bool] Flag to indicate the sorting order | |||||
* @param comp [CompT] The binary operator object | |||||
*/ | */ | ||||
template<typename ShadowedT, typename CompT> | template<typename ShadowedT, typename CompT> | ||||
void elbowSortCore(ShadowedT& data, CompT comp) { | |||||
size_t N = data.size(); | |||||
auto active = data.getActive(); | |||||
auto shadow = data.getShadow(); | |||||
void elbowSortCore(ShadowedT& data, bool ascending, CompT comp) noexcept { | |||||
auto& active = data.getActive(); // Get the source vector (the data to sort) | |||||
auto& shadow = data.getShadow(); // Get the target vector (the sorted data) | |||||
size_t N = data.size(); // The total size is the same or both vectors | |||||
size_t left = std::distance( | size_t left = std::distance( | ||||
active.begin(), | active.begin(), | ||||
std::min_element(active.begin(), active.end()) | |||||
); | |||||
(ascending) ? | |||||
std::min_element(active.begin(), active.end()) : | |||||
std::max_element(active.begin(), active.end()) | |||||
); // start 'left' from elbow of the bitonic | |||||
size_t right = (left == N-1) ? 0 : left + 1; | size_t right = (left == N-1) ? 0 : left + 1; | ||||
// Walk in opposite directions from elbow and insert-sort to target vector | |||||
for (size_t i = 0 ; i<N ; ++i) { | for (size_t i = 0 ; i<N ; ++i) { | ||||
if (comp(active[left], active[right])) { | if (comp(active[left], active[right])) { | ||||
shadow[i] = active[left]; | shadow[i] = active[left]; | ||||
left = (left == 0) ? N-1 : left -1; | |||||
left = (left == 0) ? N-1 : left -1; // cycle decrease | |||||
} | } | ||||
else { | else { | ||||
shadow[i] = active[right]; | shadow[i] = active[right]; | ||||
right = (right + 1) % N; | |||||
right = (right + 1) % N; // cycle increase | |||||
} | } | ||||
} | } | ||||
data.switch_active(); | |||||
data.switch_active(); // Switch active-shadow buffers | |||||
} | } | ||||
/*! | /*! | ||||
* Sort a shadowed buffer using the "elbow sort" algorithm. | |||||
* | |||||
* @tparam ShadowedT A Shadowed buffer type with random access iterator. | |||||
* | * | ||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param ascending | |||||
* @param data [ShadowedT] The data to sort | |||||
* @param ascending [bool] Flag to indicate the sorting order | |||||
*/ | */ | ||||
template<typename ShadowedT> | template<typename ShadowedT> | ||||
void elbowSort(ShadowedT& data, bool ascending) { | |||||
void elbowSort(ShadowedT& data, bool ascending) noexcept { | |||||
if (ascending) | if (ascending) | ||||
elbowSortCore(data, std::less<>()); | |||||
elbowSortCore(data, ascending, std::less<>()); | |||||
else | else | ||||
elbowSortCore(data, std::greater<>()); | |||||
elbowSortCore(data, ascending, std::greater<>()); | |||||
} | } | ||||
/*! | /*! | ||||
* Takes two sorted sequences where one is in increasing and the other is in decreasing order | |||||
* and selects either the larger or the smaller items in one-to-one comparison between them. | |||||
* The result is a bitonic sequence. | |||||
* | |||||
* @tparam RangeT A range type with random access iterator | |||||
* | * | ||||
* @tparam RangeT | |||||
* @param local | |||||
* @param remote | |||||
* @param keepsmall | |||||
* @param local [RangeT] Reference to the local sequence | |||||
* @param remote [const RangeT] Reference to the remote sequence (copied locally by MPI) | |||||
* @param keepSmall [bool] Flag to indicate if we keep the small items in local sequence | |||||
*/ | */ | ||||
template<typename RangeT> | template<typename RangeT> | ||||
void minmax(RangeT& local, RangeT& remote, bool keepsmall) { | |||||
void minmax(RangeT& local, const RangeT& remote, bool keepSmall) noexcept { | |||||
using value_t = typename RangeT::value_type; | using value_t = typename RangeT::value_type; | ||||
std::transform( | std::transform( | ||||
local.begin(), local.end(), | local.begin(), local.end(), | ||||
remote.begin(), | remote.begin(), | ||||
local.begin(), | local.begin(), | ||||
[keepsmall](const value_t& a, const value_t& b){ | |||||
return (keepsmall) ? std::min(a, b) : std::max(a, b); | |||||
[&keepSmall](const value_t& a, const value_t& b){ | |||||
return (keepSmall) ? std::min(a, b) : std::max(a, b); | |||||
}); | }); | ||||
} | } | ||||
@@ -232,27 +256,36 @@ void minmax(RangeT& local, RangeT& remote, bool keepsmall) { | |||||
*/ | */ | ||||
/*! | /*! | ||||
* A distributed version of the Bubbletonic sort algorithm. | |||||
* | * | ||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param Processes | |||||
* @note | |||||
* Each MPI process should run an instance of this function. | |||||
* | |||||
* @tparam ShadowedT A Shadowed buffer type with random access iterator. | |||||
* | |||||
* @param data [ShadowedT] The local to MPI process data to sort | |||||
* @param Processes [mpi_id_t] The total number of MPI processes | |||||
*/ | */ | ||||
template<typename ShadowedT> | template<typename ShadowedT> | ||||
void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | ||||
// Initially sort to create a half part of a bitonic sequence | // Initially sort to create a half part of a bitonic sequence | ||||
fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0)); | fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0)); | ||||
// Sort network | |||||
for (size_t step = 0; step < Processes-1; ++step) { | |||||
// Sort network (O(N) iterations) | |||||
for (size_t step = 0; step < static_cast<size_t>(Processes-1); ++step) { | |||||
// Find out exchange configuration | |||||
auto part = partner<SortMode::Bubbletonic>(mpi.rank(), step); | auto part = partner<SortMode::Bubbletonic>(mpi.rank(), step); | ||||
auto ks = keepSmall<SortMode::Bubbletonic>(mpi.rank(), part, Processes); | auto ks = keepSmall<SortMode::Bubbletonic>(mpi.rank(), part, Processes); | ||||
if (isActive(mpi.rank(), Processes)) { | |||||
if ( isActive(mpi.rank(), Processes) && | |||||
isActive(part, Processes) ) { | |||||
// Exchange with partner, keep nim-or-max and sort - O(N) | |||||
mpi.exchange(part, data.getActive(), data.getShadow(), step); | mpi.exchange(part, data.getActive(), data.getShadow(), step); | ||||
minmax(data.getActive(), data.getShadow(), ks); | minmax(data.getActive(), data.getShadow(), ks); | ||||
elbowSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), Processes)); | elbowSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), Processes)); | ||||
} | } | ||||
} | } | ||||
// Invert if the node was descending. | |||||
if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0)) | if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0)) | ||||
elbowSort(data, true); | elbowSort(data, true); | ||||
@@ -260,27 +293,34 @@ void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | |||||
/*! | /*! | ||||
* A distributed version of the Bitonic sort algorithm. | |||||
* | |||||
* @note | |||||
* Each MPI process should run an instance of this function. | |||||
* | |||||
* @tparam ShadowedT A Shadowed buffer type with random access iterator. | |||||
* | * | ||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param Processes | |||||
* @param data [ShadowedT] The local to MPI process data to sort | |||||
* @param Processes [mpi_id_t] The total number of MPI processes | |||||
*/ | */ | ||||
template<typename ShadowedT> | template<typename ShadowedT> | ||||
void distBitonic(ShadowedT& data, mpi_id_t Processes) { | void distBitonic(ShadowedT& data, mpi_id_t Processes) { | ||||
auto p = static_cast<uint32_t>(std::log2(Processes)); | |||||
// Initially sort to create a half part of a bitonic sequence | // Initially sort to create a half part of a bitonic sequence | ||||
fullSort(data, ascending<SortMode::Bitonic>(mpi.rank(), 0)); | fullSort(data, ascending<SortMode::Bitonic>(mpi.rank(), 0)); | ||||
// Run through sort network using elbow-sort | |||||
// Run through sort network using elbow-sort ( O(LogN * LogN) iterations ) | |||||
auto p = static_cast<uint32_t>(std::log2(Processes)); | |||||
for (size_t depth = 1; depth <= p; ++depth) { | for (size_t depth = 1; depth <= p; ++depth) { | ||||
for (size_t step = depth; step > 0;) { | for (size_t step = depth; step > 0;) { | ||||
--step; | --step; | ||||
// Find out exchange configuration | |||||
auto part = partner<SortMode::Bitonic>(mpi.rank(), step); | auto part = partner<SortMode::Bitonic>(mpi.rank(), step); | ||||
auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth); | auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth); | ||||
// Exchange with partner, keep nim-or-max | |||||
mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); | mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); | ||||
minmax(data.getActive(), data.getShadow(), ks); | minmax(data.getActive(), data.getShadow(), ks); | ||||
} | } | ||||
// sort - O(N) | |||||
elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth)); | elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth)); | ||||
} | } | ||||
} | } | ||||
@@ -23,14 +23,19 @@ template <typename T> struct MPI_TypeMapper; | |||||
// Specializations for supported types | // Specializations for supported types | ||||
template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } }; | template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } }; | ||||
template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; | template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; | ||||
template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } }; | template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } }; | ||||
template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } }; | template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } }; | ||||
template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } }; | template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } }; | ||||
template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; | template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; | ||||
template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; | template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; | ||||
template <> struct MPI_TypeMapper<unsigned int> { static MPI_Datatype getType() { return MPI_UNSIGNED; } }; | |||||
template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; | template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; | ||||
template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; | template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; | ||||
template <> struct MPI_TypeMapper<float> { static MPI_Datatype getType() { return MPI_FLOAT; } }; | |||||
template <> struct MPI_TypeMapper<double> { static MPI_Datatype getType() { return MPI_DOUBLE; } }; | |||||
template<typename TID = int> | template<typename TID = int> | ||||
struct MPI_t { | struct MPI_t { | ||||
using ID_t = TID; // Export TID type (currently int defined by the standard) | using ID_t = TID; // Export TID type (currently int defined by the standard) | ||||
@@ -38,6 +43,7 @@ struct MPI_t { | |||||
void init(int *argc, char ***argv) { | void init(int *argc, char ***argv) { | ||||
// Initialize the MPI environment | // Initialize the MPI environment | ||||
MPI_Init(argc, argv); | MPI_Init(argc, argv); | ||||
initialized_ = true; | |||||
// Get the number of processes | // Get the number of processes | ||||
int size_value, rank_value; | int size_value, rank_value; | ||||
@@ -53,11 +59,6 @@ struct MPI_t { | |||||
name_ = std::string (processor_name, name_len); | name_ = std::string (processor_name, name_len); | ||||
} | } | ||||
void finalize() { | |||||
// Finalize the MPI environment. | |||||
MPI_Finalize(); | |||||
} | |||||
template<typename T> | template<typename T> | ||||
void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | ||||
using namespace std::string_literals; | using namespace std::string_literals; | ||||
@@ -83,10 +84,21 @@ struct MPI_t { | |||||
[[nodiscard]] ID_t size() const noexcept { return size_; } | [[nodiscard]] ID_t size() const noexcept { return size_; } | ||||
[[nodiscard]] const std::string& name() const noexcept { return name_; } | [[nodiscard]] const std::string& name() const noexcept { return name_; } | ||||
void finalize() { | |||||
// Finalize the MPI environment | |||||
initialized_ = false; | |||||
MPI_Finalize(); | |||||
} | |||||
~MPI_t() { | |||||
// Finalize the MPI environment even on unexpected errors | |||||
if (initialized_) | |||||
MPI_Finalize(); | |||||
} | |||||
private: | private: | ||||
ID_t rank_{}; | ID_t rank_{}; | ||||
ID_t size_{}; | ID_t size_{}; | ||||
std::string name_{}; | std::string name_{}; | ||||
bool initialized_{}; | |||||
}; | }; | ||||
extern MPI_t<> mpi; | extern MPI_t<> mpi; | ||||
@@ -100,6 +112,42 @@ struct ShadowedVec_t { | |||||
using const_iterator = typename std::vector<Value_t>::const_iterator; | using const_iterator = typename std::vector<Value_t>::const_iterator; | ||||
using size_type = typename std::vector<Value_t>::size_type; | using size_type = typename std::vector<Value_t>::size_type; | ||||
// Default constructor | |||||
ShadowedVec_t() = default; | |||||
// Constructor from an std::vector | |||||
explicit ShadowedVec_t(const std::vector<Value_t>& vec) | |||||
: North(vec), South(), active(north) { | |||||
South.resize(North.size()); | |||||
} | |||||
explicit ShadowedVec_t(std::vector<Value_t>&& vec) | |||||
: North(std::move(vec)), South(), active(north) { | |||||
South.resize(North.size()); | |||||
} | |||||
// Copy assignment operator | |||||
ShadowedVec_t& operator=(const ShadowedVec_t& other) { | |||||
if (this != &other) { // Avoid self-assignment | |||||
North = other.North; | |||||
South = other.South; | |||||
active = other.active; | |||||
} | |||||
return *this; | |||||
} | |||||
// Move assignment operator | |||||
ShadowedVec_t& operator=(ShadowedVec_t&& other) noexcept { | |||||
if (this != &other) { // Avoid self-assignment | |||||
North = std::move(other.North); | |||||
South = std::move(other.South); | |||||
active = other.active; | |||||
// There is no need to zero out other since it is valid but in a non-defined state | |||||
} | |||||
return *this; | |||||
} | |||||
// Dispatch to active vector | // Dispatch to active vector | ||||
Value_t& operator[](size_type index) { return getActive()[index]; } | Value_t& operator[](size_type index) { return getActive()[index]; } | ||||
const Value_t& operator[](size_type index) const { return getActive()[index]; } | const Value_t& operator[](size_type index) const { return getActive()[index]; } | ||||
@@ -155,10 +203,25 @@ struct ShadowedVec_t { | |||||
const std::vector<Value_t>& getShadow() const { | const std::vector<Value_t>& getShadow() const { | ||||
return (active == north) ? South : North; | return (active == north) ? South : North; | ||||
} | } | ||||
// Comparisons | |||||
bool operator== (const ShadowedVec_t& other) { | |||||
return getActive() == other.getActive(); | |||||
} | |||||
bool operator!= (const ShadowedVec_t& other) { | |||||
return getActive() != other.getActive(); | |||||
} | |||||
bool operator== (const std::vector<value_type>& other) { | |||||
return getActive() == other; | |||||
} | |||||
bool operator!= (const std::vector<value_type>& other) { | |||||
return getActive() != other; | |||||
} | |||||
private: | private: | ||||
enum { north, south } active{north}; | |||||
std::vector<Value_t> North{}; | std::vector<Value_t> North{}; | ||||
std::vector<Value_t> South{}; | std::vector<Value_t> South{}; | ||||
enum { north, south } active{north}; | |||||
}; | }; | ||||
using distBuffer_t = ShadowedVec_t<distValue_t>; | using distBuffer_t = ShadowedVec_t<distValue_t>; | ||||
@@ -6,26 +6,16 @@ | |||||
* Christos Choutouridis AEM:8997 | * Christos Choutouridis AEM:8997 | ||||
* <cchoutou@ece.auth.gr> | * <cchoutou@ece.auth.gr> | ||||
*/ | */ | ||||
#if !defined DEBUG | |||||
#define NDEBUG | |||||
#endif | |||||
#include <cassert> | |||||
#include "utils.hpp" | #include "utils.hpp" | ||||
#include "distsort.hpp" | #include "distsort.hpp" | ||||
/*! | |||||
* Predicate to check if the node is active in the current iteration of the bubbletonic | |||||
* sort exchange. | |||||
* | |||||
* @param node The node to check | |||||
* @param nodes The total number of nodes | |||||
* @return True if the node is active, false otherwise | |||||
*/ | |||||
bool isActive(mpi_id_t node, size_t nodes) noexcept { | |||||
assert(nodes > 0); | |||||
return (node >= 0) && (node < (nodes-1)); | |||||
bool isActive(mpi_id_t node, size_t nodes) { | |||||
if (!((nodes > 0) && | |||||
(nodes <= std::numeric_limits<mpi_id_t>::max()) )) | |||||
throw std::runtime_error("(isActive) Non-acceptable value of MPI Nodes\n"); | |||||
// ^ Assert that mpi_id_t can hold nodes, and thus we can cast without data loss! | |||||
return (node >= 0) && (node < static_cast<mpi_id_t>(nodes)); | |||||
} | } | ||||
@@ -10,6 +10,7 @@ | |||||
#include <exception> | #include <exception> | ||||
#include <iostream> | #include <iostream> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <random> | |||||
#include "utils.hpp" | #include "utils.hpp" | ||||
#include "config.h" | #include "config.h" | ||||
@@ -121,9 +122,15 @@ int main(int argc, char* argv[]) try { | |||||
#endif | #endif | ||||
logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl; | logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl; | ||||
std::srand(unsigned(std::time(nullptr))); | |||||
std::random_device rd; // Mersenne seeded from hw if possible. range: [type_min, type_max] | |||||
std::mt19937 gen(rd()); | |||||
std::uniform_int_distribution<distValue_t > dis( | |||||
std::numeric_limits<distValue_t>::min(), | |||||
std::numeric_limits<distValue_t>::max() | |||||
); | |||||
// Fill vector | |||||
Data.resize(session.arraySize); | Data.resize(session.arraySize); | ||||
std::generate(Data.begin(), Data.end(), std::rand); | |||||
std::generate(Data.begin(), Data.end(), [&]() { return dis(gen); }); | |||||
if (mpi.rank() == 0) | if (mpi.rank() == 0) | ||||
logger << "Starting distributed sorting ... "; | logger << "Starting distributed sorting ... "; | ||||
@@ -139,7 +146,7 @@ int main(int argc, char* argv[]) try { | |||||
std::string timeMsg = "rank " + std::to_string(mpi.rank()); | std::string timeMsg = "rank " + std::to_string(mpi.rank()); | ||||
timer.print_dt(timeMsg.c_str()); | timer.print_dt(timeMsg.c_str()); | ||||
std::cout << "[Data]: Rank " << mpi.rank() << ": [" << (int)Data.front() << " .. " << (int)Data.back() << "]" << std::endl; | |||||
std::cout << "[Data]: Rank " << mpi.rank() << ": [" << +Data.front() << " .. " << +Data.back() << "]" << std::endl; | |||||
mpi.finalize(); | mpi.finalize(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -230,12 +230,13 @@ TEST(TdistBitonic_UT, partner_test3) { | |||||
/* ================================== keepSmall ================================== */ | /* ================================== keepSmall ================================== */ | ||||
/* | /* | ||||
* bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth); | * bool keepSmall(mpi_id_t node, mpi_id_t partner, size_t depth); | ||||
* Assertion check | |||||
* Throw check (Not assert - ASSERT_DEATH) | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test1) { | TEST(TdistBitonic_UT, keepsmall_test1) { | ||||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(0, 0, 0), ""); | |||||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(1, 1, 42), ""); | |||||
ASSERT_DEATH(keepSmall<SortMode::Bitonic>(7, 7, 42), ""); | |||||
// node and partner must differ or else ... | |||||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(0, 0, 0), std::runtime_error); | |||||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(1, 1, 42), std::runtime_error); | |||||
EXPECT_THROW(keepSmall<SortMode::Bitonic>(7, 7, 42), std::runtime_error); | |||||
} | } | ||||
/* | /* | ||||
@@ -95,12 +95,13 @@ TEST(TdistBubbletonic_UT, partner_Bubbletonic_test3) { | |||||
/* ================================== keepSmall ================================== */ | /* ================================== keepSmall ================================== */ | ||||
/* | /* | ||||
* bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, size_t depth); | * bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, size_t depth); | ||||
* Assertion check | |||||
* Throw check (Not assert - ASSERT_DEATH) | |||||
*/ | */ | ||||
TEST(TdistBubbletonic_UT, keepsmall_test1) { | TEST(TdistBubbletonic_UT, keepsmall_test1) { | ||||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(0, 0, 0), ""); | |||||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(1, 1, 42), ""); | |||||
ASSERT_DEATH(keepSmall<SortMode::Bubbletonic>(7, 7, 42), ""); | |||||
// node and partner must differ or else ... | |||||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(0, 0, 0), std::runtime_error); | |||||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(1, 1, 42), std::runtime_error); | |||||
EXPECT_THROW(keepSmall<SortMode::Bubbletonic>(7, 7, 42), std::runtime_error); | |||||
} | } | ||||
/* | /* | ||||
@@ -119,6 +120,30 @@ TEST(TdistBubbletonic_UT, keepsmall_test2) { | |||||
EXPECT_EQ(keepSmall<SortMode::Bubbletonic>(4, 9, 42), true); | EXPECT_EQ(keepSmall<SortMode::Bubbletonic>(4, 9, 42), true); | ||||
} | } | ||||
/* ================================== isActive ================================== */ | |||||
/* | |||||
* bool isActive(mpi_id_t node, size_t nodes); | |||||
* Throw check | |||||
*/ | |||||
TEST(TdistBubbletonic_UT, isActive_test1) { | |||||
EXPECT_THROW(isActive(0, 0), std::runtime_error); | |||||
EXPECT_THROW(isActive(0, static_cast<size_t>(std::numeric_limits<mpi_id_t>::max()) + 1), std::runtime_error); | |||||
} | |||||
/* | |||||
* bool isActive(mpi_id_t node, size_t nodes); | |||||
* Boundary 3 BVA | |||||
*/ | |||||
TEST(TdistBubbletonic_UT, isActive_test2) { | |||||
EXPECT_EQ(isActive(-1, 8), false); | |||||
EXPECT_EQ(isActive(0, 8), true); | |||||
EXPECT_EQ(isActive(1, 8), true); | |||||
EXPECT_EQ(isActive(7, 8), true); | |||||
EXPECT_EQ(isActive(8, 8), false); | |||||
EXPECT_EQ(isActive(9, 8), false); | |||||
} | |||||
#if 0 | #if 0 | ||||
TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | ||||
@@ -0,0 +1,156 @@ | |||||
/** | |||||
* \file | |||||
* \brief PDS HW2 tests | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#include <gtest/gtest.h> | |||||
#include <algorithm> // rand/srand | |||||
#include <ctime> // rand/srand | |||||
#include "distsort.hpp" | |||||
/* ================================== fullSort ================================== */ | |||||
/* | |||||
* | |||||
*/ | |||||
TEST(TdistCommonUT, fullSort_test1) { | |||||
std::vector<uint8_t> ts_data = {3, 2, 1, 4, 5, 7, 8, 6}; | |||||
std::vector<uint8_t> ts_expected = {1, 2, 3, 4, 5, 6, 7, 8}; | |||||
bool ts_ascending = true; | |||||
fullSort(ts_data, ts_ascending); | |||||
EXPECT_EQ((ts_data == ts_expected), true); | |||||
} | |||||
TEST(TdistCommonUT, fullSort_test2) { | |||||
std::vector<uint8_t> ts_data = {3, 2, 1, 4, 5, 7, 8, 6}; | |||||
std::vector<uint8_t> ts_expected = {8, 7, 6, 5, 4, 3, 2, 1}; | |||||
bool ts_ascending = false; | |||||
fullSort(ts_data, ts_ascending); | |||||
EXPECT_EQ((ts_data == ts_expected), true); | |||||
} | |||||
/* ================================== elbowSort ================================== */ | |||||
TEST(TdistCommonUT, elbowSort_test1) { | |||||
ShadowedVec_t<uint8_t> ts_data1(std::vector<uint8_t>{3, 2, 1, 4, 5, 5, 7, 8}); | |||||
ShadowedVec_t<uint8_t> ts_data2(std::vector<uint8_t>{4, 5, 7, 8, 5, 3, 2, 1}); | |||||
ShadowedVec_t<uint8_t> ts_data3(std::vector<uint8_t>{1, 2, 3, 4, 5, 5, 7, 8}); | |||||
ShadowedVec_t<uint8_t> ts_data4(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||||
std::vector<uint8_t> ts_expected = {1, 2, 3, 4, 5, 5, 7, 8}; | |||||
bool ts_ascending = true; | |||||
elbowSort(ts_data1, ts_ascending); | |||||
elbowSort(ts_data2, ts_ascending); | |||||
elbowSort(ts_data3, ts_ascending); | |||||
elbowSort(ts_data4, ts_ascending); | |||||
EXPECT_EQ((ts_data1 == ts_expected), true); | |||||
EXPECT_EQ((ts_data2 == ts_expected), true); | |||||
EXPECT_EQ((ts_data3 == ts_expected), true); | |||||
EXPECT_EQ((ts_data4 == ts_expected), true); | |||||
} | |||||
TEST(TdistCommonUT, elbowSort_test2) { | |||||
ShadowedVec_t<uint8_t> ts_data1(std::vector<uint8_t>{3, 2, 1, 4, 5, 5, 7, 8}); | |||||
ShadowedVec_t<uint8_t> ts_data2(std::vector<uint8_t>{4, 5, 7, 8, 5, 3, 2, 1}); | |||||
ShadowedVec_t<uint8_t> ts_data3(std::vector<uint8_t>{1, 2, 3, 4, 5, 5, 7, 8}); | |||||
ShadowedVec_t<uint8_t> ts_data4(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||||
std::vector<uint8_t> ts_expected = {8, 7, 5, 5, 4, 3, 2, 1}; | |||||
bool ts_ascending = false; | |||||
elbowSort(ts_data1, ts_ascending); | |||||
elbowSort(ts_data2, ts_ascending); | |||||
elbowSort(ts_data3, ts_ascending); | |||||
elbowSort(ts_data4, ts_ascending); | |||||
EXPECT_EQ((ts_data1 == ts_expected), true); | |||||
EXPECT_EQ((ts_data2 == ts_expected), true); | |||||
EXPECT_EQ((ts_data3 == ts_expected), true); | |||||
EXPECT_EQ((ts_data4 == ts_expected), true); | |||||
} | |||||
TEST(TdistCommonUT, elbowSort_test3) { | |||||
ShadowedVec_t<uint8_t> ts_data(std::vector<uint8_t>{8, 7, 5, 5, 4, 3, 2, 1}); | |||||
std::vector<uint8_t> ts_expected_asc = {1, 2, 3, 4, 5, 5, 7, 8}; | |||||
std::vector<uint8_t> ts_expected_des = {8, 7, 5, 5, 4, 3, 2, 1}; | |||||
// Check alternation for active-shadow vector inside Buffer and elbow algorithm | |||||
elbowSort(ts_data, true); | |||||
EXPECT_EQ((ts_data == ts_expected_asc), true); | |||||
elbowSort(ts_data, false); | |||||
EXPECT_EQ((ts_data == ts_expected_des), true); | |||||
elbowSort(ts_data, true); | |||||
EXPECT_EQ((ts_data == ts_expected_asc), true); | |||||
elbowSort(ts_data, false); | |||||
EXPECT_EQ((ts_data == ts_expected_des), true); | |||||
} | |||||
#if 0 | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | |||||
AllData_t ts_Data { | |||||
ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | |||||
std::srand(unsigned(std::time(nullptr))); | |||||
for (auto& v : ts_Data) { | |||||
std::generate(v.begin(), v.end(), std::rand); | |||||
} | |||||
distBubbletonic(2, ts_Data); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | |||||
EXPECT_EQ((max <= v[0]), true); | |||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||||
max = v.back(); | |||||
} | |||||
} | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | |||||
AllData_t ts_Data { | |||||
ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | |||||
std::srand(unsigned(std::time(nullptr))); | |||||
for (auto& v : ts_Data) { | |||||
std::generate(v.begin(), v.end(), std::rand); | |||||
} | |||||
distBubbletonic(4, ts_Data); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | |||||
EXPECT_EQ((max <= v[0]), true); | |||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||||
max = v.back(); | |||||
} | |||||
} | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test3) { | |||||
AllData_t ts_Data { | |||||
ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), | |||||
ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32) | |||||
}; | |||||
std::srand(unsigned(std::time(nullptr))); | |||||
for (auto& v : ts_Data) { | |||||
std::generate(v.begin(), v.end(), std::rand); | |||||
} | |||||
distBubbletonic(8, ts_Data); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | |||||
EXPECT_EQ((max <= v[0]), true); | |||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | |||||
max = v.back(); | |||||
} | |||||
} | |||||
#endif |