Program Listing for File ThreadVec.hpp

↰ Return to documentation for file (src/utils/ThreadVec.hpp)

#ifndef THREADVEC_HPP
#define THREADVEC_HPP

#ifdef _OPENMP
#   include <algorithm>
#   include <omp.h>
#   include <thread>
#endif
#include <cassert>
#include <cstddef>
#include <vector>

namespace utils {

#ifdef _OPENMP
    // Multiplier applied to std::thread::hardware_concurrency() when
    // clamping ThreadVec's own construction-time size -- see
    // ThreadVec::ThreadVec()'s own comment for why this exists at
    // all. Generous on purpose: every T ThreadVec is instantiated
    // with in this codebase is a small handle (pointer-sized or
    // smaller), so erring on the large side here costs negligible
    // memory, while erring small risks the out-of-bounds access this
    // whole mechanism exists to prevent.
    inline constexpr int threadVecCapOversubscription = 8;
#endif

    template <class T> class ThreadVec {

    public:

        ThreadVec() {
#ifdef _OPENMP
            const int limit = omp_get_thread_limit();
            const int cap = static_cast<int>(std::max<unsigned>(1, std::thread::hardware_concurrency()))
                * threadVecCapOversubscription;
            obj_.resize(static_cast<std::size_t>(limit > 0 && limit < cap ? limit : cap));
#else
            obj_.resize(1);
#endif
        }
        ~ThreadVec() = default;

        // Disable copy and move constructors and assignment operators
        ThreadVec(const ThreadVec&) = delete;
        ThreadVec(ThreadVec&&) = delete;
        auto operator=(const ThreadVec&) -> ThreadVec& = delete;
        auto operator=(ThreadVec&&) -> ThreadVec& = delete;

        [[nodiscard]] auto size() const { return obj_.size(); }

        auto operator()() -> auto& {
#ifdef _OPENMP
            const int ithread = omp_get_thread_num();
#else
            const int ithread = 0;
#endif
            assert(ithread >= 0 && static_cast<std::size_t>(ithread) < obj_.size());
            return obj_[ithread]; // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- hot path, must stay unchecked for performance; bounds asserted just above
        }

        auto operator()() const -> const auto& {
#ifdef _OPENMP
            const int ithread = omp_get_thread_num();
#else
            const int ithread = 0;
#endif
            assert(ithread >= 0 && static_cast<std::size_t>(ithread) < obj_.size());
            return obj_[ithread]; // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- hot path, must stay unchecked for performance; bounds asserted just above
        }

        auto operator[] (const int i) -> auto& {
            assert(i >= 0 && static_cast<std::size_t>(i) < obj_.size());
            return obj_[i]; // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- hot path, must stay unchecked for performance; bounds asserted just above
        }

        auto operator[] (const int i) const -> const auto& {
            assert(i >= 0 && static_cast<std::size_t>(i) < obj_.size());
            return obj_[i]; // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access) -- hot path, must stay unchecked for performance; bounds asserted just above
        }

        auto begin() { return obj_.begin(); }

        auto end() { return obj_.end(); }

    private:

        std::vector<T> obj_;
    };


} // namespace utils

#endif // THREADVEC_HPP