3038054c68
From-SVN: r204173
1001 lines
37 KiB
C++
1001 lines
37 KiB
C++
/*
|
|
* @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 */
|