Skip to content

Commit

Permalink
custom parallel_for implementation
Browse files Browse the repository at this point in the history
Adds concurrent_queue, thread_pool, parallel_for, function_ref, move_only_function implementations.

Removes the dependency on the parallel stl algorithms (and thus the
dependency on TBB with stdlibc++).
  • Loading branch information
KRM7 committed Sep 24, 2023
1 parent 9e9089c commit 649075a
Show file tree
Hide file tree
Showing 23 changed files with 542 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} -DGAPP_LINK_TBB=ON -DBUILD_SHARED_LIBS=OFF

- name: build
run: cmake --build . --parallel
run: cmake --build . --parallel 8

- name: run-tests
run: ctest --output-on-failure --schedule-random
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sanitizers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
env:
ASAN_OPTIONS: check_initialization_order=1:strict_init_order=1:detect_stack_use_after_return=1:detect_leaks=1
UBSAN_OPTIONS: print_stacktrace=1:print_summary=1
TSAN_OPTIONS: suppressions=../.tsan-supressions:external_symbolizer_path=/usr/lib/llvm-15/bin/llvm-symbolizer:verbosity=2:force_seq_cst_atomics=0
TSAN_OPTIONS: suppressions=../.tsan-supressions:external_symbolizer_path=/usr/lib/llvm-15/bin/llvm-symbolizer:verbosity=1:force_seq_cst_atomics=0

defaults:
run:
Expand Down
6 changes: 3 additions & 3 deletions build/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ echo -e "\nThe build directory is ${BUILD_DIR}.\n"

echo -e "Installing Debug configuration.\n"
cmake -S $BUILD_DIR/.. -B $BUILD_DIR -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF "$@"
cmake --build $BUILD_DIR --config Debug --parallel
cmake --build $BUILD_DIR --config Debug --parallel 8
cmake --install $BUILD_DIR --config Debug

echo -e "Installing RelWithDebInfo configuration.\n"
cmake -S $BUILD_DIR/.. -B $BUILD_DIR -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF "$@"
cmake --build $BUILD_DIR --config RelWithDebInfo --parallel
cmake --build $BUILD_DIR --config RelWithDebInfo --parallel 8
cmake --install $BUILD_DIR --config RelWithDebInfo

echo -e "Installing Release configuration.\n"
cmake -S $BUILD_DIR/.. -B $BUILD_DIR -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF "$@"
cmake --build $BUILD_DIR --config Release --parallel
cmake --build $BUILD_DIR --config Release --parallel 8
cmake --install $BUILD_DIR --config Release
2 changes: 1 addition & 1 deletion src/algorithm/nd_sort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "../utility/algorithm.hpp"
#include "../utility/functional.hpp"
#include "../utility/iterators.hpp"
#include "../utility/parallel_for.hpp"
#include "../utility/thread_pool.hpp"
#include "../utility/math.hpp"
#include "../utility/utility.hpp"
#include "../utility/matrix.hpp"
Expand Down
2 changes: 1 addition & 1 deletion src/algorithm/nsga3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "../metrics/pop_stats.hpp"
#include "../utility/algorithm.hpp"
#include "../utility/functional.hpp"
#include "../utility/parallel_for.hpp"
#include "../utility/thread_pool.hpp"
#include "../utility/math.hpp"
#include "../utility/rng.hpp"
#include "../utility/utility.hpp"
Expand Down
2 changes: 1 addition & 1 deletion src/core/ga_base.impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "../stop_condition/stop_condition_base.hpp"
#include "../utility/algorithm.hpp"
#include "../utility/functional.hpp"
#include "../utility/parallel_for.hpp"
#include "../utility/thread_pool.hpp"
#include "../utility/scope_exit.hpp"
#include "../utility/utility.hpp"
#include <algorithm>
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/pop_stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "../population/population.hpp"
#include "../utility/algorithm.hpp"
#include "../utility/iterators.hpp"
#include "../utility/parallel_for.hpp"
#include "../utility/thread_pool.hpp"
#include "../utility/utility.hpp"
#include <vector>
#include <span>
Expand Down
2 changes: 1 addition & 1 deletion src/population/population.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace gapp::detail
#include "../utility/algorithm.hpp"
#include "../utility/functional.hpp"
#include "../utility/iterators.hpp"
#include "../utility/parallel_for.hpp"
#include "../utility/thread_pool.hpp"
#include "../utility/utility.hpp"
#include "../utility/math.hpp"
#include <algorithm>
Expand Down
9 changes: 0 additions & 9 deletions src/utility/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,6 @@ namespace gapp::detail
container.erase(last, container.end());
}

template<std::integral T>
constexpr void increment_mod(T& value, T mod)
{
GAPP_ASSERT(mod > 0);
GAPP_ASSERT(0 <= value && value < mod);

value = (value + 1 == mod) ? T(0) : value + 1;
}

} // namespace gapp::detail

#endif // !GA_UTILITY_ALGORITHM_HPP
63 changes: 63 additions & 0 deletions src/utility/concurrent_queue.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */

#ifndef GA_UTILITY_CONCURRENT_QUEUE_HPP
#define GA_UTILITY_CONCURRENT_QUEUE_HPP

#include "spinlock.hpp"
#include <condition_variable>
#include <mutex>
#include <deque>
#include <optional>
#include <type_traits>

namespace gapp::detail
{
template<typename T>
class concurrent_queue
{
public:
template<typename... Args>
[[nodiscard]] bool emplace(Args&&... args)
{
std::scoped_lock lock{ queue_lock_ };
if (is_closed_) return false;
queue_.emplace_back(std::forward<Args>(args)...);
queue_cv_.notify_one();
return true;
}

[[nodiscard]] std::optional<T> take() noexcept(std::is_nothrow_move_constructible_v<T>)
{
std::unique_lock lock{ queue_lock_ };
queue_cv_.wait(lock, [&]() noexcept { return !queue_.empty() || is_closed_; });

if (is_closed_ && queue_.empty()) return {};

T elem = std::move(queue_.front());
queue_.pop_front();
return elem;
}

void close() noexcept
{
std::scoped_lock lock{ queue_lock_ };
is_closed_ = true;
queue_cv_.notify_all();
}

[[nodiscard]] bool closed() const noexcept
{
std::scoped_lock lock{ queue_lock_ };
return is_closed_;
}

private:
std::deque<T> queue_;
mutable detail::spinlock queue_lock_;
std::condition_variable_any queue_cv_;
bool is_closed_ = false;
};

} // namespace gapp::detail

#endif // !GA_UTILITY_CONCURRENT_QUEUE_HPP
122 changes: 119 additions & 3 deletions src/utility/functional.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
#define GA_UTILITY_FUNCTIONAL_HPP

#include "type_traits.hpp"
#include "utility.hpp"
#include <functional>
#include <bit>
#include <array>
#include <vector>
#include <cmath>
#include <utility>
#include <memory>
#include <limits>
#include <type_traits>
#include <concepts>
#include <utility>
#include <cmath>
#include <cassert>


namespace gapp
{
template<auto F>
Expand Down Expand Up @@ -101,6 +103,7 @@ namespace gapp::detail
return flat;
}


template<typename T>
constexpr auto multiply_by(const T& multiplier)
noexcept(std::is_nothrow_copy_constructible_v<T>)
Expand Down Expand Up @@ -236,6 +239,119 @@ namespace gapp::detail
return container[idx];
};
}


template<typename...>
class function_ref;

template<typename Ret, typename... Args>
class function_ref<Ret(Args...)>
{
public:
template<typename Callable>
requires(!std::is_same_v<std::remove_const_t<Callable>, function_ref> && std::is_invocable_r_v<Ret, Callable&, Args...>)
function_ref(Callable& f) noexcept :
callable_(std::bit_cast<void*>(std::addressof(f))),
invoke_(invoke_fn<Callable>)
{}

template<typename Callable>
requires(!std::is_same_v<std::remove_const_t<Callable>, function_ref> && std::is_invocable_r_v<Ret, Callable&, Args...>)
function_ref& operator=(Callable& f) noexcept
{
callable_ = std::bit_cast<void*>(std::addressof(f));
invoke_ = invoke_fn<Callable>;
return *this;
}

Ret operator()(Args... args)
{
GAPP_ASSERT(callable_, "Attempting to invoke an empty function_ref.");
return invoke_(callable_, std::forward<Args>(args)...);
}

explicit operator bool() const noexcept { return bool(callable_); }

private:
using InvokeFn = Ret(void*, Args...);

template<typename Callable>
static Ret invoke_fn(void* f, Args... args)
{
return std::invoke(*std::bit_cast<Callable*>(f), std::forward<Args>(args)...);
}

void* callable_ = nullptr;
InvokeFn* invoke_ = nullptr;
};


template<typename...>
class move_only_function;

template<typename R, typename... Args>
class move_only_function<R(Args...)>
{
public:
constexpr move_only_function() noexcept :
fptr_(nullptr)
{}

template<typename F>
requires(!std::is_same_v<F, move_only_function> && std::is_invocable_r_v<R, F&, Args...>)
move_only_function(F f) :
fptr_(std::make_unique<Impl<F, R, Args...>>(std::move(f)))
{}

template<typename F>
requires(!std::is_same_v<F, move_only_function> && std::is_invocable_r_v<R, F&, Args...>)
move_only_function& operator=(F f)
{
fptr_ = std::make_unique<Impl<F, R, Args...>>(std::move(f));
return *this;
}

move_only_function(move_only_function&&) = default;
move_only_function& operator=(move_only_function&& other) = default;

R operator()(Args... args)
{
GAPP_ASSERT(fptr_, "Attempting to invoke an empty move_only_function.");
return fptr_->invoke(std::forward<Args>(args)...);
}

void swap(move_only_function& other) noexcept
{
fptr_.swap(other.fptr_);
}

explicit operator bool() const noexcept { return bool(fptr_); }

private:
template<typename Ret, typename... IArgs>
struct ImplBase
{
virtual Ret invoke(IArgs...) = 0;
virtual ~ImplBase() = default;
};

template<typename Callable, typename Ret, typename... IArgs>
struct Impl : public ImplBase<Ret, IArgs...>
{
constexpr explicit Impl(Callable func) :
func_(std::move(func))
{}

Ret invoke(Args... args) override
{
return std::invoke(func_, std::forward<Args>(args)...);
}

Callable func_;
};

std::unique_ptr<ImplBase<R, Args...>> fptr_;
};

} // namespace gapp::detail

Expand Down
6 changes: 2 additions & 4 deletions src/utility/indestructible.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ namespace gapp::detail
{
public:
template<typename... Args>
constexpr Indestructible(Args&&... args)
noexcept(std::is_nothrow_constructible_v<T, Args...>)
Indestructible(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
{
::new(std::addressof(data_)) T(std::forward<Args>(args)...);
std::construct_at(std::addressof(get()), std::forward<Args>(args)...);
}

Indestructible(const Indestructible&) = delete;
Expand All @@ -29,7 +28,6 @@ namespace gapp::detail

~Indestructible() = default;

// These can't be constexpr because bit_cast between ptr types isn't constexpr
T& get() noexcept { return *std::bit_cast<T*>(std::addressof(data_)); }
const T& get() const noexcept { return *std::bit_cast<const T*>(std::addressof(data_)); }

Expand Down
14 changes: 14 additions & 0 deletions src/utility/iterators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@

namespace gapp::detail
{
/* Increment an iterator by n or until it reaches the end of the range. */
template<typename Iter>
constexpr void advance_in_range(Iter& it, Iter last, size_t n)
{
if constexpr (std::random_access_iterator<Iter>)
{
it = (std::distance(it, last) <= ptrdiff_t(n)) ? last : it + n;
}
else
{
while (n-- && it != last) ++it;
}
}

/*
* The following should be implemented in Derived:
* begin(), end() with const overloads
Expand Down
Loading

0 comments on commit 649075a

Please sign in to comment.