// Copyright (c) 2015 Amanieu d'Antras // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #ifndef ASYNCXX_H_ # error "Do not include this header directly, include instead." #endif namespace async { namespace detail { // Partitioners are essentially ranges with an extra split() function. The // split() function returns a partitioner containing a range to be executed in a // child task and modifies the parent partitioner's range to represent the rest // of the original range. If the range cannot be split any more then split() // should return an empty range. // Detect whether a range is a partitioner template().split())> two& is_partitioner_helper(int); template one& is_partitioner_helper(...); template struct is_partitioner: public std::integral_constant(0)) - 1> {}; // Automatically determine a grain size for a sequence length inline std::size_t auto_grain_size(std::size_t dist) { // Determine the grain size automatically using a heuristic std::size_t grain = dist / (8 * hardware_concurrency()); if (grain < 1) grain = 1; if (grain > 2048) grain = 2048; return grain; } template class static_partitioner_impl { Iter iter_begin, iter_end; std::size_t grain; public: static_partitioner_impl(Iter begin_, Iter end_, std::size_t grain_) : iter_begin(begin_), iter_end(end_), grain(grain_) {} Iter begin() const { return iter_begin; } Iter end() const { return iter_end; } static_partitioner_impl split() { // Don't split if below grain size std::size_t length = std::distance(iter_begin, iter_end); static_partitioner_impl out(iter_end, iter_end, grain); if (length <= grain) return out; // Split our range in half iter_end = iter_begin; std::advance(iter_end, (length + 1) / 2); out.iter_begin = iter_end; return out; } }; template class auto_partitioner_impl { Iter iter_begin, iter_end; std::size_t grain; std::size_t num_threads; std::thread::id last_thread; public: // thread_id is initialized to "no thread" and will be set on first split auto_partitioner_impl(Iter begin_, Iter end_, std::size_t grain_) : iter_begin(begin_), iter_end(end_), grain(grain_) {} Iter begin() const { return iter_begin; } Iter end() const { return iter_end; } auto_partitioner_impl split() { // Don't split if below grain size std::size_t length = std::distance(iter_begin, iter_end); auto_partitioner_impl out(iter_end, iter_end, grain); if (length <= grain) return out; // Check if we are in a different thread than we were before std::thread::id current_thread = std::this_thread::get_id(); if (current_thread != last_thread) num_threads = hardware_concurrency(); // If we only have one thread, don't split if (num_threads <= 1) return out; // Split our range in half iter_end = iter_begin; std::advance(iter_end, (length + 1) / 2); out.iter_begin = iter_end; out.last_thread = current_thread; last_thread = current_thread; out.num_threads = num_threads / 2; num_threads -= out.num_threads; return out; } }; } // namespace detail // A simple partitioner which splits until a grain size is reached. If a grain // size is not specified, one is chosen automatically. template detail::static_partitioner_impl()))> static_partitioner(Range&& range, std::size_t grain) { return {std::begin(range), std::end(range), grain}; } template detail::static_partitioner_impl()))> static_partitioner(Range&& range) { std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); return {std::begin(range), std::end(range), grain}; } // A more advanced partitioner which initially divides the range into one chunk // for each available thread. The range is split further if a chunk gets stolen // by a different thread. template detail::auto_partitioner_impl()))> auto_partitioner(Range&& range) { std::size_t grain = detail::auto_grain_size(std::distance(std::begin(range), std::end(range))); return {std::begin(range), std::end(range), grain}; } // Wrap a range in a partitioner. If the input is already a partitioner then it // is returned unchanged. This allows parallel algorithms to accept both ranges // and partitioners as parameters. template typename std::enable_if::type>::value, Partitioner&&>::type to_partitioner(Partitioner&& partitioner) { return std::forward(partitioner); } template typename std::enable_if::type>::value, detail::auto_partitioner_impl()))>>::type to_partitioner(Range&& range) { return async::auto_partitioner(std::forward(range)); } // Overloads with std::initializer_list template detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range) { return async::static_partitioner(async::make_range(range.begin(), range.end())); } template detail::static_partitioner_impl>().begin())> static_partitioner(std::initializer_list range, std::size_t grain) { return async::static_partitioner(async::make_range(range.begin(), range.end()), grain); } template detail::auto_partitioner_impl>().begin())> auto_partitioner(std::initializer_list range) { return async::auto_partitioner(async::make_range(range.begin(), range.end())); } template detail::auto_partitioner_impl>().begin())> to_partitioner(std::initializer_list range) { return async::auto_partitioner(async::make_range(range.begin(), range.end())); } } // namespace async