/* reducer_vector.h -*- C++ -*- * * Copyright (C) 2009-2016, Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * ********************************************************************* * * PLEASE NOTE: This file is a downstream copy of a file mainitained in * a repository at cilkplus.org. Changes made to this file that are not * submitted through the contribution process detailed at * http://www.cilkplus.org/submit-cilk-contribution will be lost the next * time that a new version is released. Changes only submitted to the * GNU compiler collection or posted to the git repository at * https://bitbucket.org/intelcilkruntime/intel-cilk-runtime.git are * not tracked. * * We welcome your contributions to this open source project. Thank you * for your assistance in helping us improve Cilk Plus. */ /** @file reducer_vector.h * * @brief Defines classes for doing parallel vector creation by appending. * * @ingroup ReducersVector * * @see ReducersVector */ #ifndef REDUCER_VECTOR_H_INCLUDED #define REDUCER_VECTOR_H_INCLUDED #include #include #include /** @defgroup ReducersVector Vector Reducers * * Vector reducers allow the creation of a standard vector by * appending a set of elements in parallel. * * @ingroup Reducers * * You should be familiar with @ref pagereducers "Intel(R) Cilk(TM) Plus reducers", * described in file `reducers.md`, and particularly with @ref reducers_using, * before trying to use the information in this file. * * @section redvector_usage Usage Example * * typedef ... SourceData; * typedef ... ResultData; * vector input; * ResultData expensive_computation(const SourceData& x); * cilk::reducer< cilk::op_vector > r; * cilk_for (int i = 0; i != input.size(); ++i) { * r->push_back(expensive_computation(input[i])); * } * vector result; * r.move_out(result); * * @section redvector_monoid The Monoid * * @subsection redvector_monoid_values Value Set * * The value set of a vector reducer is the set of values of the class * `std::vector`, which we refer to as "the reducer's vector * type". * * @subsection redvector_monoid_operator Operator * * The operator of a vector reducer is vector concatenation. * * @subsection redvector_monoid_identity Identity * * The identity value of a vector reducer is the empty vector, which is the * value of the expression `std::vector([allocator])`. * * @section redvector_operations Operations * * In the operation descriptions below, the type name `Vector` refers to * the reducer's vector type, `std::vector`. * * @subsection redvector_constructors Constructors * * Any argument list which is valid for a `std::vector` constructor is valid * for a vector reducer constructor. The usual move-in constructor is also * provided: * * reducer(move_in(Vector& variable)) * * @subsection redvector_get_set Set and Get * * void r.set_value(const Vector& value) * const Vector& = r.get_value() const * void r.move_in(Vector& variable) * void r.move_out(Vector& variable) * * @subsection redvector_initial Initial Values * * A vector reducer with no constructor arguments, or with only an allocator * argument, will initially contain the identity value, an empty vector. * * @subsection redvector_view_ops View Operations * * The view of a vector reducer provides the following member functions: * * void push_back(const Type& element) * void insert_back(const Type& element) * void insert_back(Vector::size_type n, const Type& element) * template void insert_back(Iter first, Iter last) * * The `push_back` functions is the same as the corresponding `std::vector` * function. The `insert_back` function is the same as the `std::vector` * `insert` function, with the first parameter fixed to the end of the vector. * * @section redvector_performance Performance Considerations * * Vector reducers work by creating a vector for each view, collecting those * vectors in a list, and then concatenating them into a single result vector * at the end of the computation. This last step takes place in serial code, * and necessarily takes time proportional to the length of the result vector. * Thus, a parallel vector reducer cannot actually speed up the time spent * directly creating the vector. This trivial example would probably be slower * (because of reducer overhead) than the corresponding serial code: * * vector a; * reducer > r; * cilk_for (int i = 0; i != a.length(); ++i) { * r->push_back(a[i]); * } * vector result; * r.move_out(result); * * What a vector reducer _can_ do is to allow the _remainder_ of the * computation to be done in parallel, without having to worry about * managing the vector computation. * * The vectors for new views are created (by the view identity constructor) * using the same allocator as the vector that was created when the reducer * was constructed. Note that this allocator is determined when the reducer * is constructed. The following two examples may have very different * behavior: * * vector a_vector; * * reducer< op_vector reducer1(move_in(a_vector)); * ... parallel computation ... * reducer1.move_out(a_vector); * * reducer< op_vector reducer2; * reducer2.move_in(a_vector); * ... parallel computation ... * reducer2.move_out(a_vector); * * * `reducer1` will be constructed with the same allocator as `a_vector`, * because the vector was specified in the constructor. The `move_in` * and`move_out` can therefore be done with a `swap` in constant time. * * `reducer2` will be constructed with a _default_ allocator of type * `Allocator`, which may not be the same as the allocator of `a_vector`. * Therefore, the `move_in` and `move_out` may have to be done with a * copy in _O(N)_ time. * * (All instances of an allocator class with no internal state (like * `std::allocator`) are "the same". You only need to worry about the "same * allocator" issue when you create vector reducers with a custom allocator * class that has data members.) * * @section redvector_types Type and Operator Requirements * * `std::vector` must be a valid type. */ namespace cilk { /** @ingroup ReducersVector */ //@{ /** @brief The vector reducer view class. * * This is the view class for reducers created with * `cilk::reducer< cilk::op_vector >`. It holds the * accumulator variable for the reduction, and allows only append operations * to be performed on it. * * @note The reducer "dereference" operation (`reducer::operator *()`) * yields a reference to the view. Thus, for example, the view * class's `push_back` operation would be used in an expression like * `r->push_back(a)`, where `r` is a vector reducer variable. * * @tparam Type The vector element type (not the vector type). * @tparam Alloc The vector allocator type. * * @see @ref ReducersVector * @see op_vector */ template class op_vector_view { typedef std::vector vector_type; typedef std::list::other> list_type; typedef typename vector_type::size_type size_type; // The view's value is represented by a list of vectors and a single // vector. The value is the concatenation of the vectors in the list with // the single vector at the end. All vector operations apply to the single // vector; reduce operations cause lists of partial vectors from multiple // strands to be combined. // mutable vector_type m_vector; mutable list_type m_list; // Before returning the value of the reducer, concatenate all the vectors // in the list with the single vector. // void flatten() const { if (m_list.empty()) return; typename list_type::iterator i; size_type len = m_vector.size(); for (i = m_list.begin(); i != m_list.end(); ++i) len += i->size(); vector_type result(get_allocator()); result.reserve(len); for (i = m_list.begin(); i != m_list.end(); ++i) result.insert(result.end(), i->begin(), i->end()); m_list.clear(); result.insert(result.end(), m_vector.begin(), m_vector.end()); result.swap(m_vector); } public: /** @name Monoid support. */ //@{ /// Required by cilk::monoid_with_view typedef vector_type value_type; /// Required by @ref op_vector Alloc get_allocator() const { return m_vector.get_allocator(); } /** Reduces the views of two strands. * * This function is invoked by the @ref op_vector monoid to combine * the views of two strands when the right strand merges with the left * one. It appends the value contained in the right-strand view to the * value contained in the left-strand view, and leaves the value in the * right-strand view undefined. * * @param other A pointer to the right-strand view. (`this` points to * the left-strand view.) * * @note Used only by the @ref op_vector monoid to implement the * monoid reduce operation. */ void reduce(op_vector_view* other) { if (!other->m_vector.empty() || !other->m_list.empty()) { // (list, string) + (other_list, other_string) => // (list + {string} + other_list, other_string) if (!m_vector.empty()) { // simulate m_list.push_back(std::move(m_vector)) m_list.push_back(vector_type(get_allocator())); m_list.back().swap(m_vector); } m_list.splice(m_list.end(), other->m_list); m_vector.swap(other->m_vector); } } //@} /** @name Passes constructor arguments to the vector constructor. */ //@{ op_vector_view() : m_vector(), m_list(get_allocator()) {} template op_vector_view(const T1& x1) : m_vector(x1), m_list(get_allocator()) {} template op_vector_view(const T1& x1, const T2& x2) : m_vector(x1, x2), m_list(get_allocator()) {} template op_vector_view(const T1& x1, const T2& x2, const T3& x3) : m_vector(x1, x2, x3), m_list(get_allocator()) {} template op_vector_view(const T1& x1, const T2& x2, const T3& x3, const T4& x4) : m_vector(x1, x2, x3, x4), m_list(get_allocator()) {} //@} /** Move-in constructor. */ explicit op_vector_view(cilk::move_in_wrapper w) : m_vector(w.value().get_allocator()), m_list(w.value().get_allocator()) { m_vector.swap(w.value()); } /** @name Reducer support. */ //@{ void view_move_in(vector_type& v) { m_list.clear(); if (get_allocator() == v.get_allocator()) { // Equal allocators. Do a (fast) swap. m_vector.swap(v); } else { // Unequal allocators. Do a (slow) copy. m_vector = v; } v.clear(); } void view_move_out(vector_type& v) { flatten(); if (get_allocator() == v.get_allocator()) { // Equal allocators. Do a (fast) swap. m_vector.swap(v); } else { // Unequal allocators. Do a (slow) copy. v = m_vector; m_vector.clear(); } } void view_set_value(const vector_type& v) { m_list.clear(); m_vector = v; } vector_type const& view_get_value() const { flatten(); return m_vector; } typedef vector_type const& return_type_for_get_value; //@} /** @name View modifier operations. * * @details These simply wrap the corresponding operations on the * underlying vector. */ //@{ /** Adds an element at the end of the list. * * Equivalent to `vector.push_back(…)` */ void push_back(const Type x) { m_vector.push_back(x); } /** @name Insert elements at the end of the vector. * * Equivalent to `vector.insert(vector.end(), …)` */ //@{ void insert_back(const Type& element) { m_vector.insert(m_vector.end(), element); } void insert_back(typename vector_type::size_type n, const Type& element) { m_vector.insert(m_vector.end(), n, element); } template void insert_back(Iter first, Iter last) { m_vector.insert(m_vector.end(), first, last); } //@} //@} }; /** @brief The vector append monoid class. * * Instantiate the cilk::reducer template class with an op_vector monoid to * create a vector reducer class. For example, to concatenate a * collection of integers: * * cilk::reducer< cilk::op_vector > r; * * @tparam Type The vector element type (not the vector type). * @tparam Alloc The vector allocator type. * * @see ReducersVector * @see op_vector_view * @ingroup ReducersVector */ template > class op_vector : public cilk::monoid_with_view< op_vector_view, false > { typedef cilk::monoid_with_view< op_vector_view, false > base; typedef provisional_guard view_guard; // The allocator to be used when constructing new views. Alloc m_allocator; public: /// View type. typedef typename base::view_type view_type; /** Constructor. * * There is no default constructor for vector monoids, because the * allocator must always be specified. * * @param allocator The list allocator to be used when * identity-constructing new views. */ op_vector(const Alloc& allocator = Alloc()) : m_allocator(allocator) {} /** Creates an identity view. * * Vector view identity constructors take the vector allocator as an * argument. * * @param v The address of the uninitialized memory in which the view * will be constructed. */ void identity(view_type *v) const { ::new((void*) v) view_type(m_allocator); } /** @name construct functions * * A vector append monoid must have a copy of the allocator of * the leftmost view's vector, so that it can use it in the `identity` * operation. This, in turn, requires that vector append monoids have a * specialized `construct()` function. * * All vector append monoid `construct()` functions first construct the * leftmost view, using the arguments that were passed in from the reducer * constructor. They then call the view's `get_allocator()` function to * get the vector allocator from the vector in the leftmost view, and pass * that to the monoid constructor. */ //@{ static void construct(op_vector* monoid, view_type* view) { view_guard vg( new((void*) view) view_type() ); vg.confirm_if( new((void*) monoid) op_vector(view->get_allocator()) ); } template static void construct(op_vector* monoid, view_type* view, const T1& x1) { view_guard vg( new((void*) view) view_type(x1) ); vg.confirm_if( new((void*) monoid) op_vector(view->get_allocator()) ); } template static void construct(op_vector* monoid, view_type* view, const T1& x1, const T2& x2) { view_guard vg( new((void*) view) view_type(x1, x2) ); vg.confirm_if( new((void*) monoid) op_vector(view->get_allocator()) ); } template static void construct(op_vector* monoid, view_type* view, const T1& x1, const T2& x2, const T3& x3) { view_guard vg( new((void*) view) view_type(x1, x2, x3) ); vg.confirm_if( new((void*) monoid) op_vector(view->get_allocator()) ); } //@} }; } // namespace cilk #endif // REDUCER_VECTOR_H_INCLUDED