blob: 8620c052f53e7940bd59f49a36e991c52d3a1f79 [file] [log] [blame]
/*
* @copyright
* Copyright (C) 2011-2013, Intel Corporation
* All rights reserved.
*
* @copyright
* 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.
*
* @copyright
* 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.
*
*/
/*
* holder.h
*
* Purpose: hyperobject to provide different views of an object to each
* parallel strand.
*/
#ifndef HOLDER_H_INCLUDED
#define HOLDER_H_INCLUDED
#include <cilk/reducer.h>
#include <memory>
#include <utility>
#ifdef __cplusplus
/* C++ Interface
*
* Classes: holder<Type>
*
* Description:
* ============
* This component provides a hyperobject that isolates a parallel uses of a
* common variable where it is not necessary to preserve changes from
* different parallel strands. In effect, a holder acts a bit like
* thread-local storage, but has qualities that work better with the
* fork-join structure of Cilk. In particular, a holder has the following
* qualities:
*
* - The view of a holder before the first spawn within a function is the same
* as the view after each sync (as in the case of a reducer).
* - The view of a holder within the first spawned child of a function (or the
* first child spawned after a sync) is the same as the view on entry to the
* function.
* - The view of a holder before entering a _Cilk_for loop is the same as the
* view during the first iteration of the loop and the view at the end of
* the loop.
* - The view of a holder in the continuation of a spawn or in an arbitrary
* iteration of a _Cilk_for loop is *non-deterministic*. It is generally
* recommended that the holder be explicitly put into a known state in these
* situations.
*
* A holder can be used as an alternative to parameter-passing. They are most
* useful for replacing non-local variables without massive refactoring. A
* holder takes advantage of the fact that, most of the time, a holder view
* does not change after a spawn or from one iteration of a parallel for loop
* to the next (i.e., stealing is the exception, not the rule). When the
* holder view is a large object that is expensive to construct, this
* optimization can save significant time versus creating a separate local
* object for each view. In addition, a holder using the "keep last" policy
* will have the same value after a sync as the serialization of the same
* program. The last quality will often allow the program to avoid
* recomputing a value.
*
* Usage Example:
* ==============
* Function 'compute()' is a complex function that computes a value using a
* memoized algorithm, storing intermediate results in a hash table. Compute
* calls several other functions, each of which calls several other functions,
* all of which share a global hash table. In all, there are over a dozen
* functions with a total of about 60 references to the hash table.
*..
* hash_table<int, X> memos;
*
* void h(const X& x); // Uses memos
*
* double compute(const X& x)
* {
* memos.clear();
* // ...
* memos[i] = x;
* ...
* g(i); // Uses memos
* // ...
* std::for_each(c.begin(), c.end(), h); // Call h for each element of c
* }
*
* int main()
* {
* const std::size_t ARRAY_SIZE = 1000000;
* extern X myArray[ARRAY_SIZE];
*
* for (std::size_t i = 0; i < ARRAY_SIZE; ++i)
* {
* compute(myArray[i]);
* }
* }
*..
* We would like to replace the 'for' loop in 'main' with a 'cilk_for'.
* Although the hash table is cleared on entry to each call to 'compute()',
* and although the values stored in the hash table are no longer used after
* 'compute()' returns, the use of the hash table as a global variable
* prevents 'compute()' from being called safely in parallel. One way to do
* this would be to make 'memos' a private variable within the cilk_for loop
* and pass it down to the actual computation, so that each loop iteration has
* its own private copy:
*..
* cilk_for (std::size_t i = 0; i < ARRAY_SIZE; ++i)
* {
* hash_table<int, X> memos;
* compute(myArray[i], memos);
* }
*..
* The problem with this approach is that it requires changing the signature
* of 'compute', 'h', 'g', and every one of the dozen or so functions that
* reference 'memos' as well as any function that calls those functions. This
* may break the abstraction of 'compute' and other functions, exposing an
* implementation detail that was not part of the interface. In addition, the
* function 'h' is called through a templated algorithm, 'for_each', which
* requires a fixed interface. Finally, there is constructor and destructor
* overhead for 'hash_table' each time through the loop.
*
* The alternative approach is to replace 'memos' with a holder. The holder
* would be available to all of the functions involved, but would not cause a
* race between parallel loop iterations. In order to make this work, each
* use of the 'memos' variable must be (mechanically) replaced by a use of the
* holder:
*..
* cilk::holder<hash_table<int, X> > memos_h;
*
* void h(const X& x); // Uses memos_h
*
* double compute(const X& x)
* {
* memos_h().clear(); // operator() used to "dereference" the holder
* // ...
* memos_h()[i] = x; // operator() used to "dereference" the holder
* ...
* g(i); // Uses memos_h
* // ...
* std::for_each(c.begin(), c.end(), h); // Call h for each element of c
* }
*..
* Note that each reference to the holder must be modified with an empty pair
* of parenthesis. This syntax is needed because there is no facility in C++
* for a "smart reference" that would allow 'memos_h' to be a perfect
* replacement for 'memos'. One way that a user can avoid this syntax change
* is to wrap the holder in a class that has the same inteface as
* 'hash_table' but redirects all calls to the holder:
*..
* template <typename K, typename V>
* class hash_table_holder
* {
* private:
* cilk::holder<hash_table<K, V> > m_holder;
* public:
* void clear() { m_holder().clear(); }
* V& operator[](const K& x) { return m_holder()[x]; }
* std::size_t size() const { return m_holder().size(); }
* // etc. ...
* };
*..
* Using the above wrapper, the original code can be left unchanged except for
* replacing 'hash_table' with 'hash_table_holder' and replacing 'for' with
* 'cilk_for':
*..
* hash_table_holder<int, X> memos;
*
* void h(const X& x); // Uses memos
*
* double compute(const X& x)
* {
* memos.clear(); // Calls hash_table_holder::clear().
* // ...
* }
*..
* The above changes have no benefit over the use of thread-local storage.
* What if one of the functions has a 'cilk_spawn', however?
*..
* void h(const X& x)
* {
* Y y = x.nested();
* double d, w;
* if (y)
* {
* w = cilk_spawn compute_width(y); // May use 'memos'
* d = compute_depth(y); // Does not use 'memos'
* cilk_sync;
* compute(y); // recursive call. Uses 'memos'.
* }
* }
*..
* In the above example, the view of the holder within 'compute_width' is the
* same as the view on entry to 'h'. More importantly, the view of the holder
* within the recursive call to 'compute' is the same as the view on entry to
* 'h', even if a different worker is executing the recursive call. Thus, the
* holder view within a Cilk program has useful qualities not found in
* thread-local storage.
*/
namespace cilk {
/**
* After a sync, the value stored in a holder matches the most recent
* value stored into the holder by one of the starnds entering the sync.
* The holder policy used to instantiate the holder determines which of
* the entering strands determines the final value of the holder. A policy
* of 'holder_keep_indeterminate' (the default) is the most efficient, and
* results in an indeterminate value depending on the runtime schedule
* (see below for more specifics). An indeterminate value after a sync is
* often acceptable, especially if the value of the holder is not reused
* after the sync. All of the remaining policies retain the value of the
* last strand that would be executed in the serialization of the program.
* They differ in the mechanism used to move the value from one view to
* another. A policy of 'holder_keep_last_copy' moves values by
* copy-assignment. A policy of 'holder_keep_last_swap' moves values by
* calling 'swap'. A policy of 'holder_keep_last_move' is available only
* for compilers that support C++0x rvalue references and moves values by
* move-assignment. A policy of 'holder_keep_last' attempts to choose the
* most efficient mechanism: member-function 'swap' if the view type
* supports it, otherwise move-assignment if supported, otherwise
* copy-assignment. (The swap member function for a class that provides
* one is almost always as fast or faster than move-assignment or
* copy-assignment.)
*
* The behavior of 'holder_keep_indeterminate', while indeterminate, is
* not random and can be used for advanced programming or debugging. With
* a policy of 'holder_keep_intermediate', values are never copied or
* moved between views. The value of the view after a sync is the same as
* the value set in the last spawned child before a steal occurs or the
* last value set in the continuation if no steal occurs. Using this
* knowledge, a programmer can use a holder to detect the earliest steal
* in a piece of code. An indeterminate holder is also useful for keeping
* cached data similar to the way some applications might use thread-local
* storage.
*/
enum holder_policy {
holder_keep_indeterminate,
holder_keep_last,
holder_keep_last_copy,
holder_keep_last_swap,
#ifdef __CILKRTS_RVALUE_REFERENCES
holder_keep_last_move
#endif
};
namespace internal {
// Private special-case holder policy using the swap member-function
const holder_policy holder_keep_last_member_swap =
(holder_policy) (holder_keep_last_swap | 0x10);
/* The constant, 'has_member_swap<T>::value', will be 'true' if 'T'
* has a non-static member function with prototype 'void swap(T&)'.
* The mechanism used to detect 'swap' is the most portable among
* present-day compilers, but is not the most robust. Specifically,
* the prototype for 'swap' must exactly match 'void swap(T&)'.
* Near-matches like a 'swap' function that returns 'int' instead of
* 'void' will not be detected. Detection will also fail if 'T'
* inherits 'swap' from a base class.
*/
template <typename T>
class has_member_swap
{
// This technique for detecting member functions was described by
// Rani Sharoni in comp.lang.c++.moderated:
// http://groups.google.com/group/comp.lang.c++.moderated/msg/2b06b2432fddfb60
// sizeof(notchar) is guaranteed larger than 1
struct notchar { char x[2]; };
// Instantiationg Q<U, &U::swap> will fail unless U contains a
// non-static member with prototype 'void swap(U&)'.
template <class U, void (U::*)(U&)> struct Q { };
// First 'test' is preferred overload if U::swap exists with the
// correct prototype. Second 'test' is preferred overload
// otherwise.
template <typename U> static char test(Q<U,&U::swap>*);
template <typename U> static notchar test(...);
public:
/// 'value' will be true if T has a non-static member function
/// with prototype 'void swap(T&)'.
static const bool value = (1 == sizeof(test<T>(0)));
};
template <typename T> const bool has_member_swap<T>::value;
/**
* @brief Utility class for exception safety.
*
* The constuctor for this class takes a pointer and an allocator and
* holds on to them. The destructor deallocates the pointed-to
* object, without calling its destructor, typically to recover memory
* in case an exception is thrown. The release member clears the
* pointer so that the deallocation is prevented, i.e., when the
* exception danger has passed. The behavior of this class is similar
* to auto_ptr and unique_ptr.
*/
template <typename Type, typename Allocator = std::allocator<Type> >
class auto_deallocator
{
Allocator m_alloc;
Type* m_ptr;
// Non-copiable
auto_deallocator(const auto_deallocator&);
auto_deallocator& operator=(const auto_deallocator&);
public:
/// Constructor
explicit auto_deallocator(Type* p, const Allocator& a = Allocator())
: m_alloc(a), m_ptr(p) { }
/// Destructor - free allocated resources
~auto_deallocator() { if (m_ptr) m_alloc.deallocate(m_ptr, 1); }
/// Remove reference to resource
void release() { m_ptr = 0; }
};
/**
* Pure-abstract base class to initialize holder views
*/
template <typename Type, typename Allocator>
class init_base
{
public:
virtual ~init_base() { }
virtual init_base* clone_self(Allocator& a) const = 0;
virtual void delete_self(Allocator& a) = 0;
virtual void construct_view(Type* p, Allocator& a) const = 0;
};
/**
* Class to default-initialize a holder view
*/
template <typename Type, typename Allocator>
class default_init : public init_base<Type, Allocator>
{
typedef init_base<Type, Allocator> base;
/// Private constructor (called from static make() function).
default_init() { }
// Non-copiable
default_init(const default_init&);
default_init& operator=(const default_init&);
public:
// Static factory function
static default_init* make(Allocator& a);
// Virtual function overrides
virtual ~default_init();
virtual base* clone_self(Allocator& a) const;
virtual void delete_self(Allocator& a);
virtual void construct_view(Type* p, Allocator& a) const;
};
template <typename Type, typename Allocator>
default_init<Type, Allocator>*
default_init<Type, Allocator>::make(Allocator&)
{
// Return a pointer to a singleton. All instances of this class
// are identical, so we need only one.
static default_init self;
return &self;
}
template <typename Type, typename Allocator>
default_init<Type, Allocator>::~default_init()
{
}
template <typename Type, typename Allocator>
init_base<Type, Allocator>*
default_init<Type, Allocator>::clone_self(Allocator& a) const
{
return make(a);
}
template <typename Type, typename Allocator>
void default_init<Type, Allocator>::delete_self(Allocator&)
{
// Since make() returned a shared singleton, there is nothing to
// delete here.
}
template <typename Type, typename Allocator>
void
default_init<Type, Allocator>::construct_view(Type* p,
Allocator&) const
{
::new((void*) p) Type();
// TBD: In a C++0x library, this should be rewritten
// std::allocator_traits<Allocator>::construct(a, p);
}
/**
* Class to copy-construct a view from a stored exemplar.
*/
template <typename Type, typename Allocator>
class exemplar_init : public init_base<Type, Allocator>
{
typedef init_base<Type, Allocator> base;
Type* m_exemplar;
// Private constructors (called from make() functions).
exemplar_init(const Type& val, Allocator& a);
#ifdef __CILKRTS_RVALUE_REFERENCES
exemplar_init(Type&& val, Allocator& a);
#endif
// Non-copyiable
exemplar_init(const exemplar_init&);
exemplar_init& operator=(const exemplar_init&);
public:
// Static factory functions
static exemplar_init* make(const Type& val,
Allocator& a = Allocator());
#ifdef __CILKRTS_RVALUE_REFERENCES
static exemplar_init* make(Type&& val,
Allocator& a = Allocator());
#endif
// Virtual function overrides
virtual ~exemplar_init();
virtual base* clone_self(Allocator& a) const;
virtual void delete_self(Allocator& a);
virtual void construct_view(Type* p, Allocator& a) const;
};
template <typename Type, typename Allocator>
exemplar_init<Type, Allocator>::exemplar_init(const Type& val,
Allocator& a)
{
m_exemplar = a.allocate(1);
auto_deallocator<Type, Allocator> guard(m_exemplar, a);
a.construct(m_exemplar, val);
guard.release();
}
#ifdef __CILKRTS_RVALUE_REFERENCES
template <typename Type, typename Allocator>
exemplar_init<Type, Allocator>::exemplar_init(Type&& val,
Allocator& a)
{
m_exemplar = a.allocate(1);
auto_deallocator<Type, Allocator> guard(m_exemplar, a);
a.construct(m_exemplar, std::forward<Type>(val));
guard.release();
}
#endif
template <typename Type, typename Allocator>
exemplar_init<Type, Allocator>*
exemplar_init<Type, Allocator>::make(const Type& val,
Allocator& a)
{
typedef typename Allocator::template rebind<exemplar_init>::other
self_alloc_t;
self_alloc_t alloc(a);
exemplar_init *self = alloc.allocate(1);
auto_deallocator<exemplar_init, self_alloc_t> guard(self, alloc);
// Don't use allocator to construct self. Allocator should be
// used only on elements of type 'Type'.
::new((void*) self) exemplar_init(val, a);
guard.release();
return self;
}
#ifdef __CILKRTS_RVALUE_REFERENCES
template <typename Type, typename Allocator>
exemplar_init<Type, Allocator>*
exemplar_init<Type, Allocator>::make(Type&& val,
Allocator& a)
{
typedef typename Allocator::template rebind<exemplar_init>::other
self_alloc_t;
self_alloc_t alloc(a);
exemplar_init *self = alloc.allocate(1);
auto_deallocator<exemplar_init, self_alloc_t> guard(self, alloc);
// Don't use allocator to construct self. Allocator should be
// used only on elements of type 'Type'.
::new((void*) self) exemplar_init(std::forward<Type>(val), a);
guard.release();
return self;
}
#endif
template <typename Type, typename Allocator>
exemplar_init<Type, Allocator>::~exemplar_init()
{
// Called only by delete_self, which deleted the exemplar using an
// allocator.
__CILKRTS_ASSERT(0 == m_exemplar);
}
template <typename Type, typename Allocator>
init_base<Type, Allocator>*
exemplar_init<Type, Allocator>::clone_self(Allocator& a) const
{
return make(*m_exemplar, a);
}
template <typename Type, typename Allocator>
void exemplar_init<Type, Allocator>::delete_self(Allocator& a)
{
typename Allocator::template rebind<exemplar_init>::other alloc(a);
a.destroy(m_exemplar);
a.deallocate(m_exemplar, 1);
m_exemplar = 0;
this->~exemplar_init();
alloc.deallocate(this, 1);
}
template <typename Type, typename Allocator>
void
exemplar_init<Type, Allocator>::construct_view(Type* p,
Allocator& a) const
{
a.construct(p, *m_exemplar);
// TBD: In a C++0x library, this should be rewritten
// std::allocator_traits<Allocator>::construct(a, p, *m_exemplar);
}
/**
* Class to construct a view using a stored functor. The functor,
* 'f', must be be invokable using the expression 'Type x = f()'.
*/
template <typename Func, typename Allocator>
class functor_init :
public init_base<typename Allocator::value_type, Allocator>
{
typedef typename Allocator::value_type value_type;
typedef init_base<value_type, Allocator> base;
typedef typename Allocator::template rebind<Func>::other f_alloc;
Func *m_functor;
/// Private constructors (called from make() functions
functor_init(const Func& f, Allocator& a);
#ifdef __CILKRTS_RVALUE_REFERENCES
functor_init(Func&& f, Allocator& a);
#endif
// Non-copiable
functor_init(const functor_init&);
functor_init& operator=(const functor_init&);
public:
// Static factory functions
static functor_init* make(const Func& val,
Allocator& a = Allocator());
#ifdef __CILKRTS_RVALUE_REFERENCES
static functor_init* make(Func&& val,
Allocator& a = Allocator());
#endif
// Virtual function overrides
virtual ~functor_init();
virtual base* clone_self(Allocator& a) const;
virtual void delete_self(Allocator& a);
virtual void
construct_view(value_type* p, Allocator& a) const;
};
/// Specialization to strip off reference from 'Func&'.
template <typename Func, typename Allocator>
struct functor_init<Func&, Allocator>
: functor_init<Func, Allocator> { };
/// Specialization to strip off reference and cvq from 'const Func&'.
template <typename Func, typename Allocator>
struct functor_init<const Func&, Allocator>
: functor_init<Func, Allocator> { };
template <typename Func, typename Allocator>
functor_init<Func, Allocator>::functor_init(const Func& f,
Allocator& a)
{
f_alloc alloc(a);
m_functor = alloc.allocate(1);
auto_deallocator<Func, f_alloc> guard(m_functor, alloc);
alloc.construct(m_functor, f);
guard.release();
}
#ifdef __CILKRTS_RVALUE_REFERENCES
template <typename Func, typename Allocator>
functor_init<Func, Allocator>::functor_init(Func&& f,
Allocator& a)
{
f_alloc alloc(a);
m_functor = alloc.allocate(1);
auto_deallocator<Func, f_alloc> guard(m_functor, alloc);
alloc.construct(m_functor, std::forward<Func>(f));
guard.release();
}
#endif
template <typename Func, typename Allocator>
functor_init<Func, Allocator>*
functor_init<Func, Allocator>::make(const Func& f, Allocator& a)
{
typedef typename Allocator::template rebind<functor_init>::other
self_alloc_t;
self_alloc_t alloc(a);
functor_init *self = alloc.allocate(1);
auto_deallocator<functor_init, self_alloc_t> guard(self, alloc);
// Don't use allocator to construct self. Allocator should be
// used only on elements of type 'Func'.
::new((void*) self) functor_init(f, a);
guard.release();
return self;
}
#ifdef __CILKRTS_RVALUE_REFERENCES
template <typename Func, typename Allocator>
functor_init<Func, Allocator>*
functor_init<Func, Allocator>::make(Func&& f, Allocator& a)
{
typedef typename Allocator::template rebind<functor_init>::other
self_alloc_t;
self_alloc_t alloc(a);
functor_init *self = alloc.allocate(1);
auto_deallocator<functor_init, self_alloc_t> guard(self, alloc);
// Don't use allocator to construct self. Allocator should be
// used only on elements of type 'Func'.
::new((void*) self) functor_init(std::forward<Func>(f), a);
guard.release();
return self;
}
#endif
template <typename Func, typename Allocator>
functor_init<Func, Allocator>::~functor_init()
{
// Called only by delete_self, which deleted the functor using an
// allocator.
__CILKRTS_ASSERT(0 == m_functor);
}
template <typename Func, typename Allocator>
init_base<typename Allocator::value_type, Allocator>*
functor_init<Func, Allocator>::clone_self(Allocator& a) const
{
return make(*m_functor, a);
}
template <typename Func, typename Allocator>
inline
void functor_init<Func, Allocator>::delete_self(Allocator& a)
{
typename Allocator::template rebind<functor_init>::other alloc(a);
f_alloc fa(a);
fa.destroy(m_functor);
fa.deallocate(m_functor, 1);
m_functor = 0;
this->~functor_init();
alloc.deallocate(this, 1);
}
template <typename Func, typename Allocator>
void functor_init<Func, Allocator>::construct_view(value_type* p,
Allocator& a) const
{
a.construct(p, (*m_functor)());
// In C++0x, the above should be written
// std::allocator_traits<Allocator>::construct(a, p, m_functor());
}
/**
* Functor called to reduce a holder
*/
template <typename Type, holder_policy Policy>
struct holder_reduce_functor;
/**
* Specialization to keep the left (first) value.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_indeterminate>
{
void operator()(Type* left, Type* right) const { }
};
/**
* Specialization to copy-assign from the right (last) value.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_last_copy>
{
void operator()(Type* left, Type* right) const {
*left = *right;
}
};
/*
* Specialization to keep the right (last) value via swap.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_last_swap>
{
void operator()(Type* left, Type* right) const {
using std::swap;
swap(*left, *right);
}
};
#ifdef __CILKRTS_RVALUE_REFERENCES
/*
* Specialization to move-assign from the right (last) value.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_last_move>
{
void operator()(Type* left, Type* right) const {
*left = std::move(*right);
}
};
#endif
/*
* Specialization to keep the right (last) value via the swap member
* function.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_last_member_swap>
{
void operator()(Type* left, Type* right) const {
left->swap(*right);
}
};
/*
* Specialization to keep the right (last) value by the most efficient
* means detectable.
*/
template <typename Type>
struct holder_reduce_functor<Type, holder_keep_last> :
holder_reduce_functor<Type,
(holder_policy)
(has_member_swap<Type>::value ?
holder_keep_last_member_swap :
#ifdef __CILKRTS_RVALUE_REFERENCES
holder_keep_last_move
#else
holder_keep_last_copy
#endif
)>
{
};
} // end namespace internal
/**
* Monoid for holders.
* Allocator type is required to be thread-safe.
*/
template <typename Type,
holder_policy Policy = holder_keep_indeterminate,
typename Allocator = std::allocator<Type> >
class holder_monoid : public monoid_base<Type>
{
// Allocator is mutable because the copy of the monoid inside the
// reducer is const (to avoid races on the shared state). However,
// the allocator is required to be thread-safe, so it is ok (and
// necessary) to modify.
mutable Allocator m_allocator;
internal::init_base<Type, Allocator> *m_initializer;
public:
/// This constructor uses default-initialization for both the leftmost
/// view and each identity view.
holder_monoid(const Allocator& a = Allocator())
: m_allocator(a)
, m_initializer(
internal::default_init<Type, Allocator>::make(m_allocator))
{ }
/// These constructors use 'val' as an exemplar to copy-construct both
/// the leftmost view and each identity view.
holder_monoid(const Type& val, const Allocator& a = Allocator())
: m_allocator(a)
, m_initializer(internal::exemplar_init<Type, Allocator>::make(
val, m_allocator)) { }
/// This constructor uses 'f' as a functor to construct both
/// the leftmost view and each identity view.
template <typename Func>
holder_monoid(const Func& f, const Allocator& a = Allocator())
: m_allocator(a)
, m_initializer(
internal::functor_init<Func, Allocator>::make(f,m_allocator))
{ }
/// Copy constructor
holder_monoid(const holder_monoid& rhs)
: m_allocator(rhs.m_allocator)
, m_initializer(rhs.m_initializer->clone_self(m_allocator)) { }
/// "Extended" copy constructor with allocator
holder_monoid(const holder_monoid& rhs, const Allocator& a)
: m_allocator(a)
, m_initializer(rhs.m_initializer->clone_self(m_allocator)) { }
#ifdef __CILKRTS_RVALUE_REFERENCES
/// Move constructor
holder_monoid(holder_monoid&& rhs)
: m_allocator(rhs.m_allocator)
, m_initializer(rhs.m_initializer) {
rhs.m_initializer =
internal::default_init<Type, Allocator>::make(m_allocator);
}
/// "Extended" move constructor with allocator
holder_monoid(holder_monoid&& rhs, const Allocator& a)
: m_allocator(a)
, m_initializer(0) {
if (a != rhs.m_allocator)
m_initializer = rhs.m_initializer->clone_self(a);
else {
m_initializer = rhs.m_initializer;
rhs.m_initializer =
internal::default_init<Type, Allocator>::make(m_allocator);
}
}
#endif
/// Destructor
~holder_monoid() { m_initializer->delete_self(m_allocator); }
holder_monoid& operator=(const holder_monoid& rhs) {
if (this == &rhs) return *this;
m_initializer->delete_self(m_allocator);
m_initializer = rhs.m_initializer->clone_self(m_allocator);
}
#ifdef __CILKRTS_RVALUE_REFERENCES
holder_monoid& operator=(holder_monoid&& rhs) {
if (m_allocator != rhs.m_allocator)
// Delegate to copy-assignment on unequal allocators
return operator=(static_cast<const holder_monoid&>(rhs));
std::swap(m_initializer, rhs.m_initializer);
return *this;
}
#endif
/// Constructs IDENTITY value into the uninitilized '*p'
void identity(Type* p) const
{ m_initializer->construct_view(p, m_allocator); }
/// Calls the destructor on the object pointed-to by 'p'
void destroy(Type* p) const
{ m_allocator.destroy(p); }
/// Return a pointer to size bytes of raw memory
void* allocate(std::size_t s) const {
__CILKRTS_ASSERT(sizeof(Type) == s);
return m_allocator.allocate(1);
}
/// Deallocate the raw memory at p
void deallocate(void* p) const {
m_allocator.deallocate(static_cast<Type*>(p), sizeof(Type));
}
void reduce(Type* left, Type* right) const {
internal::holder_reduce_functor<Type, Policy>()(left, right);
}
void swap(holder_monoid& other) {
__CILKRTS_ASSERT(m_allocator == other.m_allocator);
std::swap(m_initializer, other.m_initializer);
}
Allocator get_allocator() const {
return m_allocator;
}
};
// Namespace-scope swap
template <typename Type, holder_policy Policy, typename Allocator>
inline void swap(holder_monoid<Type, Policy, Allocator>& a,
holder_monoid<Type, Policy, Allocator>& b)
{
a.swap(b);
}
/**
* Hyperobject to provide different views of an object to each
* parallel strand.
*/
template <typename Type,
holder_policy Policy = holder_keep_indeterminate,
typename Allocator = std::allocator<Type> >
class holder : public reducer<holder_monoid<Type, Policy, Allocator> >
{
typedef holder_monoid<Type, Policy, Allocator> monoid_type;
typedef reducer<monoid_type> imp;
// Return a value of Type constructed using the functor Func.
template <typename Func>
Type make_value(const Func& f) const {
struct obj {
union {
char buf[sizeof(Type)];
void* align1;
double align2;
};
obj(const Func& f) { f(static_cast<Type*>(buf)); }
~obj() { static_cast<Type*>(buf)->~Type(); }
operator Type&() { return *static_cast<Type*>(buf); }
};
return obj(f);
}
public:
/// Default constructor uses default-initialization for both the
/// leftmost view and each identity view.
holder(const Allocator& alloc = Allocator())
: imp(monoid_type(alloc)) { }
/// Construct from an exemplar that is used to initialize both the
/// leftmost view and each identity view.
holder(const Type& v, const Allocator& alloc = Allocator())
// Alas, cannot use an rvalue reference for 'v' because it is used
// twice in the same expression for initializing imp.
: imp(monoid_type(v, alloc), v) { }
/// Construct from a functor that is used to initialize both the
/// leftmost view and each identity view. The functor, 'f', must be be
/// invokable using the expression 'Type x = f()'.
template <typename Func>
holder(const Func& f, const Allocator& alloc = Allocator())
// Alas, cannot use an rvalue for 'f' because it is used twice in
// the same expression for initializing imp.
: imp(monoid_type(f, alloc), make_value(f)) { }
};
} // end namespace cilk
#else /* C */
# error Holders are currently available only for C++
#endif /* __cplusplus */
#endif /* HOLDER_H_INCLUDED */