-
Notifications
You must be signed in to change notification settings - Fork 112
Remove OpenMP dependency #300
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
269f741
Remove OpenMP from CMake build
chyunsu3 0a198a4
Implement parallel for primitive
chyunsu3 d428a43
Add a test
chyunsu3 cb78523
Add test for ParallelFor
chyunsu3 f625999
Fix formatting
chyunsu3 0587614
Require Threads in CMake build
chyunsu3 9be9283
Simplify assertion
chyunsu3 d469dad
Don't run Homebrew commands, to save CI time
chyunsu3 4d1dd86
Install XGBoost and LightGBM without OpenMP
chyunsu3 7756deb
Address reviewer's feedback
chyunsu3 4981627
Install Gtest in Conda to speed up build
chyunsu3 f206ffe
Revert "Install Gtest in Conda to speed up build"
chyunsu3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /*! | ||
| * Copyright (c) 2021 by Contributors | ||
| * \file parallel_for.h | ||
| * \brief Implemenation of parallel for loop | ||
| * \author Hyunsu Cho | ||
| */ | ||
| #ifndef TREELITE_THREADING_UTILS_PARALLEL_FOR_H_ | ||
| #define TREELITE_THREADING_UTILS_PARALLEL_FOR_H_ | ||
|
|
||
| #include <treelite/logging.h> | ||
| #include <future> | ||
| #include <thread> | ||
| #include <algorithm> | ||
| #include <vector> | ||
| #include <cstddef> | ||
|
|
||
| namespace treelite { | ||
| namespace threading_utils { | ||
|
|
||
| template <typename IndexType> | ||
| std::vector<IndexType> ComputeWorkRange(IndexType begin, IndexType end, std::size_t nthread); | ||
|
|
||
| template <typename IndexType, typename FuncType> | ||
| void ParallelFor(IndexType begin, IndexType end, std::size_t nthread, FuncType func) { | ||
| TREELITE_CHECK_GT(nthread, 0) << "nthread must be positive"; | ||
| TREELITE_CHECK_LE(nthread, std::thread::hardware_concurrency()) | ||
| << "nthread cannot exceed " << std::thread::hardware_concurrency(); | ||
| if (begin == end) { | ||
| return; | ||
| } | ||
| /* Divide the range [begin, end) equally among the threads. | ||
| * The i-th thread gets the range [work_range[i], work_range[i+1]). */ | ||
| std::vector<IndexType> work_range = ComputeWorkRange(begin, end, nthread); | ||
|
|
||
| // Launch (nthread - 1) threads, as the main thread should also perform work. | ||
| std::vector<std::future<void>> async_tasks; | ||
| for (std::size_t thread_id = 1; thread_id < nthread; ++thread_id) { | ||
| async_tasks.push_back(std::async(std::launch::async, [&work_range, &func, thread_id]() { | ||
| const IndexType begin_ = work_range[thread_id]; | ||
| const IndexType end_ = work_range[thread_id + 1]; | ||
| for (IndexType i = begin_; i < end_; ++i) { | ||
| func(i, thread_id); | ||
| } | ||
| })); | ||
| } | ||
| { | ||
| const IndexType begin_ = work_range[0]; | ||
| const IndexType end_ = work_range[1]; | ||
| for (IndexType i = begin_; i < end_; ++i) { | ||
| func(i, 0); | ||
| } | ||
| } | ||
| // Join threads | ||
| for (auto& task : async_tasks) { | ||
| task.get(); | ||
| } | ||
| } | ||
|
|
||
| template <typename IndexType> | ||
| std::vector<IndexType> ComputeWorkRange(IndexType begin, IndexType end, std::size_t nthread) { | ||
| TREELITE_CHECK_GE(end, 0) << "end must be 0 or greater"; | ||
| TREELITE_CHECK_GE(begin, 0) << "begin must be 0 or greater"; | ||
| TREELITE_CHECK_GE(end, begin) << "end cannot be less than begin"; | ||
| TREELITE_CHECK_GT(nthread, 0) << "nthread must be positive"; | ||
| IndexType num_elem = end - begin; | ||
| const IndexType portion = num_elem / nthread + !!(num_elem % nthread); | ||
| // integer division, rounded-up | ||
|
|
||
| std::vector<IndexType> work_range(nthread + 1); | ||
| work_range[0] = begin; | ||
| IndexType acc = begin; | ||
| for (std::size_t i = 0; i < nthread; ++i) { | ||
| acc += portion; | ||
| work_range[i + 1] = std::min(acc, end); | ||
| } | ||
| TREELITE_CHECK_EQ(work_range[nthread], end); | ||
|
|
||
| return work_range; | ||
| } | ||
|
|
||
| } // namespace threading_utils | ||
| } // namespace treelite | ||
|
|
||
| #endif // TREELITE_THREADING_UTILS_PARALLEL_FOR_H_ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| /*! | ||
| * Copyright (c) 2021 by Contributors | ||
| * \file test_threading_utils.cc | ||
| * \author Hyunsu Cho | ||
| * \brief C++ tests for threading utilities | ||
| */ | ||
| #include <gtest/gtest.h> | ||
| #include <gmock/gmock.h> | ||
| #include <vector> | ||
| #include <algorithm> | ||
| #include <thread> | ||
| #include <random> | ||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include "threading_utils/parallel_for.h" | ||
|
|
||
| namespace { | ||
|
|
||
| class RandomGenerator { | ||
| public: | ||
| RandomGenerator() | ||
| : rng_(std::random_device()()), | ||
| int_dist_(std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max()), | ||
| real_dist_(0.0, 1.0) {} | ||
|
|
||
| int64_t DrawInteger(int64_t low, int64_t high) { | ||
| TREELITE_CHECK_LT(low, high); | ||
| int64_t out = int_dist_(rng_); | ||
| int64_t rem = out % (high - low); | ||
| int64_t ret; | ||
| if (rem < 0) { | ||
| ret = high + rem; | ||
| } else { | ||
| ret = low + rem; | ||
| } | ||
| TREELITE_CHECK_GE(ret, low); | ||
| TREELITE_CHECK_LT(ret, high); | ||
| return ret; | ||
| } | ||
|
|
||
| double DrawReal(double low, double high) { | ||
| TREELITE_CHECK_LT(low, high); | ||
| return real_dist_(rng_) * (high - low) + low; | ||
| } | ||
|
|
||
| private: | ||
| std::mt19937 rng_; | ||
| std::uniform_int_distribution<int64_t> int_dist_; | ||
| std::uniform_real_distribution<double> real_dist_; | ||
| }; | ||
|
|
||
| } // namespace anonymous | ||
|
|
||
| namespace treelite { | ||
| namespace threading_utils { | ||
|
|
||
| TEST(ThreadingUtils, ComputeWorkRange) { | ||
| /* Test error handling */ | ||
| EXPECT_THROW(ComputeWorkRange(0, 100, 0), treelite::Error); | ||
| EXPECT_THROW(ComputeWorkRange(-100, 100, 3), treelite::Error); | ||
| EXPECT_THROW(ComputeWorkRange(-200, -100, 3), treelite::Error); | ||
| EXPECT_THROW(ComputeWorkRange(200, 100, 3), treelite::Error); | ||
|
|
||
| /* Property-based testing with randomly generated parameters */ | ||
| RandomGenerator rng; | ||
|
|
||
| constexpr int kNumTrial = 200; | ||
| for (int i = 0; i < kNumTrial; ++i) { | ||
| int64_t begin = rng.DrawInteger(0, 10000); | ||
| std::size_t nthread = static_cast<std::size_t>(rng.DrawInteger(1, 100)); | ||
| int64_t end = rng.DrawInteger(begin, 10000); | ||
| auto range = ComputeWorkRange(begin, end, nthread); | ||
| EXPECT_EQ(range.size(), nthread + 1); | ||
| EXPECT_EQ(range[0], begin); | ||
| EXPECT_EQ(range[nthread], end); | ||
| for (std::size_t i = 0; i < nthread; ++i) { | ||
| EXPECT_GE(range[i + 1], range[i]); | ||
| } | ||
| } | ||
| // Test the case with begin == end | ||
| for (int i = 0; i < 10; ++i) { | ||
| int64_t begin = rng.DrawInteger(0, 10000); | ||
| int64_t end = begin; | ||
| std::size_t nthread = static_cast<std::size_t>(rng.DrawInteger(1, 100)); | ||
| auto range = ComputeWorkRange(begin, end, nthread); | ||
| EXPECT_EQ(range.size(), nthread + 1); | ||
| EXPECT_EQ(range[0], begin); | ||
| EXPECT_EQ(range[nthread], begin); | ||
| for (std::size_t i = 0; i < nthread; ++i) { | ||
| EXPECT_EQ(range[i + 1], range[i]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| TEST(ThreadingUtils, ParallelFor) { | ||
| /* Test error handling */ | ||
| const int max_thread = std::thread::hardware_concurrency(); | ||
|
|
||
| auto dummy_func = [](int, std::size_t) {}; | ||
| EXPECT_THROW(ParallelFor(0, 100, 0, dummy_func), treelite::Error); | ||
| EXPECT_THROW(ParallelFor(200, 100, 3, dummy_func), treelite::Error); | ||
| EXPECT_THROW(ParallelFor(-100, 100, 3, dummy_func), treelite::Error); | ||
| EXPECT_THROW(ParallelFor(-200, -100, 3, dummy_func), treelite::Error); | ||
| EXPECT_THROW(ParallelFor(200, 100, 3, dummy_func), treelite::Error); | ||
| EXPECT_THROW(ParallelFor(10, 20, 3 * max_thread, dummy_func), treelite::Error); | ||
|
|
||
| /* Property-based testing with randomly generated parameters */ | ||
| constexpr int kVectorLength = 10000; | ||
| RandomGenerator rng; | ||
| std::vector<double> a(kVectorLength); | ||
| std::vector<double> b(kVectorLength); | ||
| std::generate_n(a.begin(), kVectorLength, [&rng]() { return rng.DrawReal(-1.0, 1.0); }); | ||
| std::generate_n(b.begin(), kVectorLength, [&rng]() { return rng.DrawReal(-10.0, 10.0); }); | ||
|
|
||
| constexpr int kNumTrial = 200; | ||
| for (int i = 0; i < kNumTrial; ++i) { | ||
| std::vector<double> c(kVectorLength); | ||
| // Fill c with dummy values | ||
| std::generate_n(c.begin(), kVectorLength, [&rng]() { return rng.DrawReal(100.0, 200.0); }); | ||
|
|
||
| // Compute c := a + b on range [begin, end) | ||
| int64_t begin = rng.DrawInteger(0, kVectorLength); | ||
| std::size_t nthread = static_cast<std::size_t>(rng.DrawInteger(1, max_thread + 1)); | ||
| int64_t end = rng.DrawInteger(begin, kVectorLength); | ||
|
|
||
| ParallelFor(begin, end, nthread, [&a, &b, &c](int64_t i, std::size_t) { | ||
| c[i] = a[i] + b[i]; | ||
| }); | ||
|
|
||
| for (int64_t i = begin; i < end; ++i) { | ||
| EXPECT_FLOAT_EQ(c[i], a[i] + b[i]) << ", at index " << i; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace threading_utils | ||
| } // namespace treelite |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.