Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement parallel logical algorithms. #8845

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 61 additions & 8 deletions src/common/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
*/
#ifndef XGBOOST_COMMON_ALGORITHM_H_
#define XGBOOST_COMMON_ALGORITHM_H_
#include <algorithm> // upper_bound, stable_sort, sort, max
#include <cinttypes> // size_t
#include <functional> // less
#include <iterator> // iterator_traits, distance
#include <vector> // vector
#include <algorithm> // for upper_bound, stable_sort, sort, max, all_of, none_of, min
#include <cstddef> // for size_t
#include <functional> // for less
#include <iterator> // for iterator_traits, distance
#include <type_traits> // for is_same
#include <vector> // for vector

#include "numeric.h" // Iota
#include "xgboost/context.h" // Context
#include "common.h" // for DivRoundUp
#include "numeric.h" // for Iota
#include "threading_utils.h" // for MemStackAllocator, DefaultMaxThreads, ParallelFor
#include "xgboost/context.h" // for Context

// clang with libstdc++ works as well
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__sun) && !defined(sun) && !defined(__APPLE__)
#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__sun) && !defined(sun) && \
!defined(__APPLE__) && defined(_OPENMP)
#define GCC_HAS_PARALLEL 1
#endif // GLIC_VERSION

Expand Down Expand Up @@ -82,6 +86,55 @@ std::vector<Idx> ArgSort(Context const *ctx, Iter begin, Iter end, Comp comp = s
StableSort(ctx, result.begin(), result.end(), op);
return result;
}

namespace detail {
template <typename It, typename Op>
bool Logical(Context const *ctx, It first, It last, Op op) {
auto n = std::distance(first, last);
auto n_threads =
std::max(std::min(n, static_cast<decltype(n)>(ctx->Threads())), static_cast<decltype(n)>(1));
common::MemStackAllocator<bool, common::DefaultMaxThreads()> tloc{
static_cast<std::size_t>(n_threads), false};
CHECK_GE(n, 0);
CHECK_GE(ctx->Threads(), 1);
static_assert(std::is_same<decltype(n), decltype(n_threads)>::value, "");
auto const n_per_thread = common::DivRoundUp(n, ctx->Threads());
common::ParallelFor(static_cast<decltype(n)>(n_threads), n_threads, [&](auto t) {
auto begin = t * n_per_thread;
auto end = std::min(begin + n_per_thread, n);

auto first_tloc = first + begin;
auto last_tloc = first + end;
if (first_tloc >= last_tloc) {
tloc[t] = true;
return;
}
bool result = op(first_tloc, last_tloc);
tloc[t] = result;
});
return std::all_of(tloc.cbegin(), tloc.cend(), [](auto v) { return v; });
}
} // namespace detail

/**
* \brief Parallel version of std::none_of
*/
template <typename It, typename Pred>
bool NoneOf(Context const *ctx, It first, It last, Pred predicate) {
return detail::Logical(ctx, first, last, [&predicate](It first, It last) {
return std::none_of(first, last, predicate);
});
}

/**
* \brief Parallel version of std::all_of
*/
template <typename It, typename Pred>
bool AllOf(Context const *ctx, It first, It last, Pred predicate) {
return detail::Logical(ctx, first, last, [&predicate](It first, It last) {
return std::all_of(first, last, predicate);
});
}
} // namespace common
} // namespace xgboost

Expand Down
61 changes: 58 additions & 3 deletions tests/cpp/common/test_algorithm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
* Copyright 2020-2023 by XGBoost Contributors
*/
#include <gtest/gtest.h>
#include <xgboost/context.h> // Context
#include <xgboost/span.h>
#include <xgboost/context.h> // for Context

#include <algorithm> // is_sorted
#include <algorithm> // for is_sorted
#include <cstdint> // for int32_t
#include <vector> // for vector

#include "../../../src/common/algorithm.h"

Expand All @@ -31,5 +32,59 @@ TEST(Algorithm, Sort) {
StableSort(&ctx, inputs.begin(), inputs.end(), std::less<>{});
ASSERT_TRUE(std::is_sorted(inputs.cbegin(), inputs.cend()));
}

TEST(Algorithm, AllOf) {
Context ctx;
auto is_zero = [](auto v) { return v == 0; };

for (std::size_t n : {0, 3, 16, 128}) {
std::vector<std::size_t> data(n, 0);
for (std::int32_t n_threads : {0, 1, 3, 7}) {
ctx.nthread = n_threads;
auto ret = AllOf(&ctx, data.cbegin(), data.cend(), is_zero);
ASSERT_TRUE(ret);
// same result as std for empty case.
ASSERT_TRUE(std::all_of(data.cbegin(), data.cend(), is_zero));
}

if (n == 0) {
continue;
}

data[n / 2] = 1;
for (std::int32_t n_threads : {0, 1, 3, 7}) {
ctx.nthread = n_threads;
auto ret = AllOf(&ctx, data.cbegin(), data.cend(), is_zero);
ASSERT_FALSE(ret);
}
}
}

TEST(Algorithm, NoneOf) {
Context ctx;
auto is_one = [](auto v) { return v == 1; };

for (std::size_t n : {0, 3, 16, 128}) {
std::vector<std::size_t> data(n, 0);
for (std::int32_t n_threads : {0, 1, 3, 7}) {
ctx.nthread = n_threads;
auto ret = NoneOf(&ctx, data.cbegin(), data.cend(), is_one);
ASSERT_TRUE(ret);
// same result as std for empty case.
ASSERT_TRUE(std::none_of(data.cbegin(), data.cend(), is_one));
}

if (n == 0) {
continue;
}

data[n / 2] = 1;
for (std::int32_t n_threads : {1, 3, 7}) {
ctx.nthread = n_threads;
auto ret = NoneOf(&ctx, data.cbegin(), data.cend(), is_one);
ASSERT_FALSE(ret);
}
}
}
} // namespace common
} // namespace xgboost