From: bangerth Date: Fri, 8 Nov 2013 23:20:11 +0000 (+0000) Subject: Reindent the whole file. X-Git-Url: https://gitweb.dealii.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=628ffa08e178e2436df17409ef3452863aab9bae;p=dealii-svn.git Reindent the whole file. git-svn-id: https://svn.dealii.org/trunk@31597 0785d39b-7218-0410-832d-ea1e28bc413d --- diff --git a/deal.II/include/deal.II/base/work_stream.h b/deal.II/include/deal.II/base/work_stream.h index 79bede8565..4feb5bb0f3 100644 --- a/deal.II/include/deal.II/base/work_stream.h +++ b/deal.II/include/deal.II/base/work_stream.h @@ -158,88 +158,228 @@ namespace WorkStream */ namespace Implementation2 { - /** - * A class that creates a sequence of items from a range of iterators. - */ - template - class IteratorRangeToItemStream : public tbb::filter - { - public: /** - * A data type that we use to identify items to be worked on. This is - * the structure that is passed around between the different parts of - * the WorkStream implementation to identify what needs to be done by - * the various stages of the pipeline. + * A class that creates a sequence of items from a range of iterators. */ - struct ItemType + template + class IteratorRangeToItemStream : public tbb::filter { + public: /** - * A structure that contains a pointer to a scratch data object along - * with a flag that indicates whether this object is currently in use. + * A data type that we use to identify items to be worked on. This is + * the structure that is passed around between the different parts of + * the WorkStream implementation to identify what needs to be done by + * the various stages of the pipeline. */ - struct ScratchDataObject + struct ItemType { - std_cxx1x::shared_ptr scratch_data; - bool currently_in_use; - /** - * Default constructor. + * A structure that contains a pointer to a scratch data object along + * with a flag that indicates whether this object is currently in use. */ - ScratchDataObject () - : - currently_in_use (false) - {} - - ScratchDataObject (ScratchData *p, - const bool in_use) - : - scratch_data (p), - currently_in_use (in_use) - {} + struct ScratchDataObject + { + std_cxx1x::shared_ptr scratch_data; + bool currently_in_use; + + /** + * Default constructor. + */ + ScratchDataObject () + : + currently_in_use (false) + {} + + ScratchDataObject (ScratchData *p, + const bool in_use) + : + scratch_data (p), + currently_in_use (in_use) + {} -//TODO: when we push back an object to the list of scratch objects, in -// Worker::operator(), we first create an object and then copy -// it to the end of this list. this involves having two objects +//TODO: when we push back an object to the list of scratch objects, in +// Worker::operator(), we first create an object and then copy +// it to the end of this list. this involves having two objects // of the current type having pointers to it, each with their own // currently_in_use flag. there is probably little harm in this because // the original one goes out of scope right away again, but it's // certainly awkward. one way to avoid this would be to use unique_ptr // but we'd need to figure out a way to use it in non-C++11 mode - ScratchDataObject (const ScratchDataObject &o) + ScratchDataObject (const ScratchDataObject &o) + : + scratch_data (o.scratch_data), + currently_in_use (o.currently_in_use) + {} + }; + + + /** + * Typedef to a list of scratch data objects. The rationale for this + * list is provided in the variables that use these lists. + */ + typedef std::list ScratchDataList; + + /** + * A list of iterators that need to be worked on. Only the first + * n_items are relevant. + */ + std::vector work_items; + + /** + * The CopyData objects that the Worker part of the pipeline + * fills for each work item. Again, only the first n_items + * elements are what we care about. + */ + std::vector copy_datas; + + /** + * Number of items identified by the work_items array that the + * Worker and Copier pipeline stage need to work on. The maximum + * value of this variable will be chunk_size. + */ + unsigned int n_items; + + /** + * Pointer to a thread local variable identifying the scratch data objects + * this thread will use. The initial implementation of this + * class using thread local variables provided only a single + * scratch object per thread. This doesn't work, because + * the worker functions may start tasks itself and then call + * Threads::TaskGroup::join_all() or a similar function, which the + * TBB scheduler may use to run something else on the current + * thread -- for example another instance of the worker function. + * Consequently, there would be two instances of the worker + * function that use the same scratch object if we only + * provided a single scratch object per thread. The solution is + * to provide a list of scratch objects for each thread, together + * with a flag indicating whether this scratch object is currently + * used. If a thread needs a scratch object, it walks this list + * until it finds an unused object, or, if there is none, creates one + * itself. Note that we need not use synchronization primitives + * for this process since the lists are thread-local and + * we are guaranteed that only a single thread accesses them as long + * as we have no yield point in between the accesses to the list. + * + * The pointers to scratch objects stored in each of these lists must + * be so that they are deleted on all threads when the thread + * local object is destroyed. This is achieved by using shared_ptr. + * + * Note that when a worker needs to create a scratch object, it allocates + * it using sample_scratch_data to copy from. This has + * the advantage of a first-touch initialization, i.e., the + * memory for the scratch data object is allocated and initialized + * by the same thread that will later use it. + */ + Threads::ThreadLocalStorage *scratch_data; + + /** + * Pointer to a sample scratch data object, to be used to initialize + * the scratch data objects created for each individual thread. + */ + const ScratchData *sample_scratch_data; + + + /** + * Default constructor. + * Initialize everything that doesn't have a default constructor + * itself. + */ + ItemType () : - scratch_data (o.scratch_data), - currently_in_use (o.currently_in_use) + n_items (0), + scratch_data (0), + sample_scratch_data (0) {} }; /** - * Typedef to a list of scratch data objects. The rationale for this - * list is provided in the variables that use these lists. + * Constructor. Take an iterator range, the size of a buffer that can + * hold items, and the sample additional data object that will be passed + * to each worker and copier function invokation. */ - typedef std::list ScratchDataList; + IteratorRangeToItemStream (const Iterator &begin, + const Iterator &end, + const unsigned int buffer_size, + const unsigned int chunk_size, + const ScratchData &sample_scratch_data, + const CopyData &sample_copy_data) + : + tbb::filter (/*is_serial=*/true), + remaining_iterator_range (begin, end), + ring_buffer (buffer_size), + sample_scratch_data (sample_scratch_data), + n_emitted_items (0), + chunk_size (chunk_size) + { + // initialize the elements of the ring buffer + for (unsigned int element=0; element work_items; + virtual void *operator () (void *) + { + // store the current + // position of the pointer + ItemType *current_item + = &ring_buffer[n_emitted_items % ring_buffer.size()]; + + // initialize the next item. it may + // consist of at most chunk_size + // elements + current_item->n_items = 0; + while ((remaining_iterator_range.first != + remaining_iterator_range.second) + && + (current_item->n_items < chunk_size)) + { + current_item->work_items[current_item->n_items] + = remaining_iterator_range.first; + + ++remaining_iterator_range.first; + ++current_item->n_items; + } + + if (current_item->n_items == 0) + // there were no items + // left. terminate the pipeline + return 0; + else + { + ++n_emitted_items; + return current_item; + } + } + private: /** - * The CopyData objects that the Worker part of the pipeline - * fills for each work item. Again, only the first n_items - * elements are what we care about. + * The interval of iterators still to + * be worked on. This range will shrink + * over time. */ - std::vector copy_datas; + std::pair remaining_iterator_range; /** - * Number of items identified by the work_items array that the - * Worker and Copier pipeline stage need to work on. The maximum - * value of this variable will be chunk_size. + * A ring buffer that will store items. */ - unsigned int n_items; + std::vector ring_buffer; /** * Pointer to a thread local variable identifying the scratch data objects @@ -272,421 +412,281 @@ namespace WorkStream * memory for the scratch data object is allocated and initialized * by the same thread that will later use it. */ - Threads::ThreadLocalStorage *scratch_data; + Threads::ThreadLocalStorage thread_local_scratch; /** - * Pointer to a sample scratch data object, to be used to initialize - * the scratch data objects created for each individual thread. + * A reference to a sample scratch data that will be used to + * initialize the thread-local pointers to a scratch data object + * each of the worker tasks uses. */ - const ScratchData *sample_scratch_data; - + const ScratchData &sample_scratch_data; /** - * Default constructor. - * Initialize everything that doesn't have a default constructor - * itself. + * Counter for the number of emitted + * items. Each item may consist of up + * to chunk_size iterator elements. */ - ItemType () - : - n_items (0), - scratch_data (0), - sample_scratch_data (0) - {} - }; + unsigned int n_emitted_items; + /** + * Number of elements of the + * iterator range that each + * thread should work on + * sequentially; a large number + * makes sure that each thread + * gets a significant amount of + * work before the next task + * switch happens, whereas a + * small number is better for + * load balancing. + */ + const unsigned int chunk_size; - /** - * Constructor. Take an iterator range, the size of a buffer that can - * hold items, and the sample additional data object that will be passed - * to each worker and copier function invokation. - */ - IteratorRangeToItemStream (const Iterator &begin, - const Iterator &end, - const unsigned int buffer_size, - const unsigned int chunk_size, - const ScratchData &sample_scratch_data, - const CopyData &sample_copy_data) - : - tbb::filter (/*is_serial=*/true), - remaining_iterator_range (begin, end), - ring_buffer (buffer_size), - sample_scratch_data (sample_scratch_data), - n_emitted_items (0), - chunk_size (chunk_size) - { - // initialize the elements of the ring buffer - for (unsigned int element=0; element + class Worker : public tbb::filter { - // store the current - // position of the pointer - ItemType *current_item - = &ring_buffer[n_emitted_items % ring_buffer.size()]; - - // initialize the next item. it may - // consist of at most chunk_size - // elements - current_item->n_items = 0; - while ((remaining_iterator_range.first != - remaining_iterator_range.second) - && - (current_item->n_items < chunk_size)) + public: + /** + * Constructor. Takes a + * reference to the object on + * which we will operate as + * well as a pointer to the + * function that will do the + * assembly. + */ + Worker (const std_cxx1x::function &worker) + : + tbb::filter (/* is_serial= */ false), + worker (worker) + {} + + + /** + * Work on an item. + */ + void *operator () (void *item) + { + // first unpack the current item + typedef + typename IteratorRangeToItemStream::ItemType + ItemType; + + ItemType *current_item = static_cast (item); + + // we need to find an unused scratch data object in the list that + // corresponds to the current thread and then mark it as used. if + // we can't find one, create one + // + // as discussed in the discussion of the documentation of the + // IteratorRangeToItemStream::scratch_data variable, there is no + // need to synchronize access to this variable using a mutex + // as long as we have no yield-point in between. this means that + // we can't take an iterator into the list now and expect it to + // still be valid after calling the worker, but we at least do + // not have to lock the following section + ScratchData *scratch_data = 0; { - current_item->work_items[current_item->n_items] - = remaining_iterator_range.first; + typename ItemType::ScratchDataList & + scratch_data_list = current_item->scratch_data->get(); + + // see if there is an unused object. if so, grab it and mark + // it as used + for (typename ItemType::ScratchDataList::iterator + p = scratch_data_list.begin(); + p != scratch_data_list.end(); ++p) + if (p->currently_in_use == false) + { + scratch_data = p->scratch_data.get(); + p->currently_in_use = true; + break; + } + + // if no object was found, create one and mark it as used + if (scratch_data == 0) + { + scratch_data = new ScratchData(*current_item->sample_scratch_data); + + typename ItemType::ScratchDataList::value_type + new_scratch_object (scratch_data, true); + scratch_data_list.push_back (new_scratch_object); + } + } - ++remaining_iterator_range.first; - ++current_item->n_items; + // then call the worker function on each element of the chunk we were + // given. since these worker functions are called on separate threads, + // nothing good can happen if they throw an exception and we are best + // off catching it and showing an error message + for (unsigned int i=0; in_items; ++i) + { + try + { + if (worker) + worker (current_item->work_items[i], + *scratch_data, + current_item->copy_datas[i]); + } + catch (const std::exception &exc) + { + Threads::internal::handle_std_exception (exc); + } + catch (...) + { + Threads::internal::handle_unknown_exception (); + } + } + + // finally mark the scratch object as unused again. as above, there + // is no need to lock anything here since the object we work on + // is thread-local + { + typename ItemType::ScratchDataList & + scratch_data_list = current_item->scratch_data->get(); + + for (typename ItemType::ScratchDataList::iterator p = + scratch_data_list.begin(); p != scratch_data_list.end(); + ++p) + if (p->scratch_data.get() == scratch_data) + { + Assert(p->currently_in_use == true, ExcInternalError()); + p->currently_in_use = false; + } } - if (current_item->n_items == 0) - // there were no items - // left. terminate the pipeline - return 0; - else - { - ++n_emitted_items; - return current_item; - } - } - private: - /** - * The interval of iterators still to - * be worked on. This range will shrink - * over time. - */ - std::pair remaining_iterator_range; - /** - * A ring buffer that will store items. - */ - std::vector ring_buffer; + // then return the original pointer + // to the now modified object + return item; + } - /** - * Pointer to a thread local variable identifying the scratch data objects - * this thread will use. The initial implementation of this - * class using thread local variables provided only a single - * scratch object per thread. This doesn't work, because - * the worker functions may start tasks itself and then call - * Threads::TaskGroup::join_all() or a similar function, which the - * TBB scheduler may use to run something else on the current - * thread -- for example another instance of the worker function. - * Consequently, there would be two instances of the worker - * function that use the same scratch object if we only - * provided a single scratch object per thread. The solution is - * to provide a list of scratch objects for each thread, together - * with a flag indicating whether this scratch object is currently - * used. If a thread needs a scratch object, it walks this list - * until it finds an unused object, or, if there is none, creates one - * itself. Note that we need not use synchronization primitives - * for this process since the lists are thread-local and - * we are guaranteed that only a single thread accesses them as long - * as we have no yield point in between the accesses to the list. - * - * The pointers to scratch objects stored in each of these lists must - * be so that they are deleted on all threads when the thread - * local object is destroyed. This is achieved by using shared_ptr. - * - * Note that when a worker needs to create a scratch object, it allocates - * it using sample_scratch_data to copy from. This has - * the advantage of a first-touch initialization, i.e., the - * memory for the scratch data object is allocated and initialized - * by the same thread that will later use it. - */ - Threads::ThreadLocalStorage thread_local_scratch; - /** - * A reference to a sample scratch data that will be used to - * initialize the thread-local pointers to a scratch data object - * each of the worker tasks uses. - */ - const ScratchData &sample_scratch_data; + private: + /** + * Pointer to the function + * that does the assembling + * on the sequence of cells. + */ + const std_cxx1x::function worker; + }; - /** - * Counter for the number of emitted - * items. Each item may consist of up - * to chunk_size iterator elements. - */ - unsigned int n_emitted_items; - /** - * Number of elements of the - * iterator range that each - * thread should work on - * sequentially; a large number - * makes sure that each thread - * gets a significant amount of - * work before the next task - * switch happens, whereas a - * small number is better for - * load balancing. - */ - const unsigned int chunk_size; /** - * Initialize the pointers and vector - * elements in the specified entry of - * the ring buffer. + * A class that manages calling the + * copier function. Note that it is, in + * the TBB notation, a filter that runs + * sequentially, ensuring that all items + * are copied in the same order in which + * they are created. */ - void init_buffer_elements (const unsigned int element, - const CopyData &sample_copy_data) + template + class Copier : public tbb::filter { - Assert (ring_buffer[element].n_items == 0, - ExcInternalError()); - - ring_buffer[element].work_items - .resize (chunk_size, remaining_iterator_range.second); - ring_buffer[element].scratch_data - = &thread_local_scratch; - ring_buffer[element].sample_scratch_data - = &sample_scratch_data; - ring_buffer[element].copy_datas - .resize (chunk_size, sample_copy_data); - } - }; - - + public: + /** + * Constructor. Takes a + * reference to the object on + * which we will operate as + * well as a pointer to the + * function that will do the + * copying from the + * additional data object to + * the global matrix or + * similar. + */ + Copier (const std_cxx1x::function &copier) + : + tbb::filter (/*is_serial=*/true), + copier (copier) + {} - /** - * A class that manages calling the - * worker function on a number of - * parallel threads. Note that it is, in - * the TBB notation, a filter that can - * run in parallel. - */ - template - class Worker : public tbb::filter - { - public: - /** - * Constructor. Takes a - * reference to the object on - * which we will operate as - * well as a pointer to the - * function that will do the - * assembly. - */ - Worker (const std_cxx1x::function &worker) - : - tbb::filter (/* is_serial= */ false), - worker (worker) - {} - - - /** - * Work on an item. - */ - void *operator () (void *item) - { - // first unpack the current item - typedef - typename IteratorRangeToItemStream::ItemType - ItemType; - - ItemType *current_item = static_cast (item); - - // we need to find an unused scratch data object in the list that - // corresponds to the current thread and then mark it as used. if - // we can't find one, create one - // - // as discussed in the discussion of the documentation of the - // IteratorRangeToItemStream::scratch_data variable, there is no - // need to synchronize access to this variable using a mutex - // as long as we have no yield-point in between. this means that - // we can't take an iterator into the list now and expect it to - // still be valid after calling the worker, but we at least do - // not have to lock the following section - ScratchData *scratch_data = 0; - { - typename ItemType::ScratchDataList & - scratch_data_list = current_item->scratch_data->get(); - - // see if there is an unused object. if so, grab it and mark - // it as used - for (typename ItemType::ScratchDataList::iterator - p = scratch_data_list.begin(); - p != scratch_data_list.end(); ++p) - if (p->currently_in_use == false) - { - scratch_data = p->scratch_data.get(); - p->currently_in_use = true; - break; - } - - // if no object was found, create one and mark it as used - if (scratch_data == 0) - { - scratch_data = new ScratchData(*current_item->sample_scratch_data); - - typename ItemType::ScratchDataList::value_type - new_scratch_object (scratch_data, true); - scratch_data_list.push_back (new_scratch_object); - } - } - - // then call the worker function on each element of the chunk we were - // given. since these worker functions are called on separate threads, - // nothing good can happen if they throw an exception and we are best - // off catching it and showing an error message - for (unsigned int i=0; in_items; ++i) - { - try - { - if (worker) - worker (current_item->work_items[i], - *scratch_data, - current_item->copy_datas[i]); - } - catch (const std::exception &exc) - { - Threads::internal::handle_std_exception (exc); - } - catch (...) - { - Threads::internal::handle_unknown_exception (); - } - } - - // finally mark the scratch object as unused again. as above, there - // is no need to lock anything here since the object we work on - // is thread-local - { - typename ItemType::ScratchDataList & - scratch_data_list = current_item->scratch_data->get(); - - for (typename ItemType::ScratchDataList::iterator p = - scratch_data_list.begin(); p != scratch_data_list.end(); - ++p) - if (p->scratch_data.get() == scratch_data) - { - Assert(p->currently_in_use == true, ExcInternalError()); - p->currently_in_use = false; - } - } - - - - // then return the original pointer - // to the now modified object - return item; - } - - - private: - /** - * Pointer to the function - * that does the assembling - * on the sequence of cells. - */ - const std_cxx1x::function worker; - }; + /** + * Work on a single item. + */ + void *operator () (void *item) + { + // first unpack the current item + typedef + typename IteratorRangeToItemStream::ItemType + ItemType; + + ItemType *current_item = static_cast (item); + + // initiate copying data. for the same reasons as in the worker class + // above, catch exceptions rather than letting it propagate into + // unknown territories + for (unsigned int i=0; in_items; ++i) + { + try + { + if (copier) + copier (current_item->copy_datas[i]); + } + catch (const std::exception &exc) + { + Threads::internal::handle_std_exception (exc); + } + catch (...) + { + Threads::internal::handle_unknown_exception (); + } + } + + + // return an invalid item since we are at the end of the + // pipeline + return 0; + } - /** - * A class that manages calling the - * copier function. Note that it is, in - * the TBB notation, a filter that runs - * sequentially, ensuring that all items - * are copied in the same order in which - * they are created. - */ - template - class Copier : public tbb::filter - { - public: - /** - * Constructor. Takes a - * reference to the object on - * which we will operate as - * well as a pointer to the - * function that will do the - * copying from the - * additional data object to - * the global matrix or - * similar. - */ - Copier (const std_cxx1x::function &copier) - : - tbb::filter (/*is_serial=*/true), - copier (copier) - {} - - - /** - * Work on a single item. - */ - void *operator () (void *item) - { - // first unpack the current item - typedef - typename IteratorRangeToItemStream::ItemType - ItemType; - - ItemType *current_item = static_cast (item); - - // initiate copying data. for the same reasons as in the worker class - // above, catch exceptions rather than letting it propagate into - // unknown territories - for (unsigned int i=0; in_items; ++i) - { - try - { - if (copier) - copier (current_item->copy_datas[i]); - } - catch (const std::exception &exc) - { - Threads::internal::handle_std_exception (exc); - } - catch (...) - { - Threads::internal::handle_unknown_exception (); - } - } - - - // return an invalid item since we are at the end of the - // pipeline - return 0; - } - - - private: - /** - * Pointer to the function that does the copying of data. - */ - const std_cxx1x::function copier; - }; + private: + /** + * Pointer to the function that does the copying of data. + */ + const std_cxx1x::function copier; + }; - } + } /** @@ -697,49 +697,49 @@ namespace WorkStream */ namespace Implementation3 { - /** - * A class that creates a sequence of items from a range of iterators. - */ - template - class IteratorRangeToItemStream : public tbb::filter - { - public: /** - * A data type that we use to identify items to be worked on. This is - * the structure that is passed around between the different parts of - * the WorkStream implementation to identify what needs to be done by - * the various stages of the pipeline. + * A class that creates a sequence of items from a range of iterators. */ - struct ItemType + template + class IteratorRangeToItemStream : public tbb::filter { + public: /** - * A structure that contains a pointer to scratch and copy data objects along - * with a flag that indicates whether this object is currently in use. + * A data type that we use to identify items to be worked on. This is + * the structure that is passed around between the different parts of + * the WorkStream implementation to identify what needs to be done by + * the various stages of the pipeline. */ - struct ScratchAndCopyDataObjects + struct ItemType { - std_cxx1x::shared_ptr scratch_data; - std_cxx1x::shared_ptr copy_data; - bool currently_in_use; - /** - * Default constructor. + * A structure that contains a pointer to scratch and copy data objects along + * with a flag that indicates whether this object is currently in use. */ - ScratchAndCopyDataObjects () - : - currently_in_use (false) - {} - - ScratchAndCopyDataObjects (ScratchData *p, - CopyData *q, - const bool in_use) - : - scratch_data (p), - copy_data (q), - currently_in_use (in_use) - {} + struct ScratchAndCopyDataObjects + { + std_cxx1x::shared_ptr scratch_data; + std_cxx1x::shared_ptr copy_data; + bool currently_in_use; + + /** + * Default constructor. + */ + ScratchAndCopyDataObjects () + : + currently_in_use (false) + {} + + ScratchAndCopyDataObjects (ScratchData *p, + CopyData *q, + const bool in_use) + : + scratch_data (p), + copy_data (q), + currently_in_use (in_use) + {} //TODO: when we push back an object to the list of scratch objects, in // Worker::operator(), we first create an object and then copy @@ -749,377 +749,377 @@ namespace WorkStream // the original one goes out of scope right away again, but it's // certainly awkward. one way to avoid this would be to use unique_ptr // but we'd need to figure out a way to use it in non-C++11 mode - ScratchAndCopyDataObjects (const ScratchAndCopyDataObjects &o) + ScratchAndCopyDataObjects (const ScratchAndCopyDataObjects &o) + : + scratch_data (o.scratch_data), + copy_data (o.copy_data), + currently_in_use (o.currently_in_use) + {} + }; + + + /** + * Typedef to a list of scratch data objects. The rationale for this + * list is provided in the variables that use these lists. + */ + typedef std::list ScratchAndCopyDataList; + + /** + * A list of iterators that need to be worked on. Only the first + * n_items are relevant. + */ + std::vector work_items; + + /** + * Number of items identified by the work_items array that the + * Worker and Copier pipeline stage need to work on. The maximum + * value of this variable will be chunk_size. + */ + unsigned int n_items; + + /** + * Pointer to a thread local variable identifying the scratch + * and copy data objects this thread will use. The same considerations + * apply as documented in the Implementation2::IteratorRangeToItemStream + * class as well as in the paper by Turcksin, Kronbichler and Bangerth. + */ + Threads::ThreadLocalStorage *scratch_and_copy_data; + + /** + * Pointer to a sample scratch data object, to be used to initialize + * the scratch data objects created for each individual thread. + */ + const ScratchData *sample_scratch_data; + + /** + * Pointer to a sample copy data object. + */ + const CopyData *sample_copy_data; + + + /** + * Default constructor. + * Initialize everything that doesn't have a default constructor + * itself. + */ + ItemType () : - scratch_data (o.scratch_data), - copy_data (o.copy_data), - currently_in_use (o.currently_in_use) + n_items (0), + sample_scratch_data (0), + sample_copy_data (0) {} }; /** - * Typedef to a list of scratch data objects. The rationale for this - * list is provided in the variables that use these lists. + * Constructor. Take range of iterators into an array of the kind of object we + * want to loop over, the size of a buffer that can + * hold items, and the sample additional data object that will be passed + * to each worker and copier function invokation. */ - typedef std::list ScratchAndCopyDataList; + IteratorRangeToItemStream (const typename std::vector::const_iterator &begin, + const typename std::vector::const_iterator &end, + const unsigned int buffer_size, + const unsigned int chunk_size, + const ScratchData &sample_scratch_data, + const CopyData &sample_copy_data) + : + tbb::filter (/*is_serial=*/true), + remaining_iterator_range (begin, end), + ring_buffer (buffer_size), + sample_scratch_data (sample_scratch_data), + sample_copy_data (sample_copy_data), + n_emitted_items (0), + chunk_size (chunk_size) + { + // initialize the elements of the ring buffer + for (unsigned int element=0; element work_items; + virtual void *operator () (void *) + { + // store the current + // position of the pointer + ItemType *current_item + = &ring_buffer[n_emitted_items % ring_buffer.size()]; + + // initialize the next item. it may + // consist of at most chunk_size + // elements + current_item->n_items = 0; + while ((remaining_iterator_range.first != + remaining_iterator_range.second) + && + (current_item->n_items < chunk_size)) + { + // initialize the iterators to work on with the elements + // of the vector that remaining_iterator_range + // points into + current_item->work_items[current_item->n_items] + = *remaining_iterator_range.first; + + ++remaining_iterator_range.first; + ++current_item->n_items; + } + + if (current_item->n_items == 0) + // there were no items + // left. terminate the pipeline + return 0; + else + { + ++n_emitted_items; + return current_item; + } + } + private: /** - * Number of items identified by the work_items array that the - * Worker and Copier pipeline stage need to work on. The maximum - * value of this variable will be chunk_size. + * The interval of iterators still to + * be worked on. This range will shrink + * over time. */ - unsigned int n_items; + std::pair::const_iterator,typename std::vector::const_iterator> remaining_iterator_range; /** - * Pointer to a thread local variable identifying the scratch - * and copy data objects this thread will use. The same considerations - * apply as documented in the Implementation2::IteratorRangeToItemStream - * class as well as in the paper by Turcksin, Kronbichler and Bangerth. + * A ring buffer that will store items. */ - Threads::ThreadLocalStorage *scratch_and_copy_data; + std::vector ring_buffer; /** - * Pointer to a sample scratch data object, to be used to initialize - * the scratch data objects created for each individual thread. + * Pointer to a thread local variable identifying the scratch and + * copy data objects each thread will use. The same is true as + * discussed for the implementation in the + * Implementation2::IteratorRangeToItemStream class and the paper + * by Turcksin, Kronbichler and Bangerth. */ - const ScratchData *sample_scratch_data; + Threads::ThreadLocalStorage thread_local_scratch_and_copy; /** - * Pointer to a sample copy data object. + * A reference to a sample scratch data that will be used to + * initialize the thread-local pointers to a scratch data object + * each of the worker tasks uses. */ - const CopyData *sample_copy_data; + const ScratchData &sample_scratch_data; + /** + * A reference to a sample scratch data that will be used to + * initialize the thread-local pointers to a scratch data object + * each of the worker tasks uses. + */ + const CopyData &sample_copy_data; /** - * Default constructor. - * Initialize everything that doesn't have a default constructor - * itself. + * Counter for the number of emitted + * items. Each item may consist of up + * to chunk_size iterator elements. */ - ItemType () - : - n_items (0), - sample_scratch_data (0), - sample_copy_data (0) - {} - }; + unsigned int n_emitted_items; + /** + * Number of elements of the + * iterator range that each + * thread should work on + * sequentially; a large number + * makes sure that each thread + * gets a significant amount of + * work before the next task + * switch happens, whereas a + * small number is better for + * load balancing. + */ + const unsigned int chunk_size; - /** - * Constructor. Take range of iterators into an array of the kind of object we - * want to loop over, the size of a buffer that can - * hold items, and the sample additional data object that will be passed - * to each worker and copier function invokation. - */ - IteratorRangeToItemStream (const typename std::vector::const_iterator &begin, - const typename std::vector::const_iterator &end, - const unsigned int buffer_size, - const unsigned int chunk_size, - const ScratchData &sample_scratch_data, - const CopyData &sample_copy_data) - : - tbb::filter (/*is_serial=*/true), - remaining_iterator_range (begin, end), - ring_buffer (buffer_size), - sample_scratch_data (sample_scratch_data), - sample_copy_data (sample_copy_data), - n_emitted_items (0), - chunk_size (chunk_size) - { - // initialize the elements of the ring buffer - for (unsigned int element=0; element + class WorkerAndCopier : public tbb::filter { - // store the current - // position of the pointer - ItemType *current_item - = &ring_buffer[n_emitted_items % ring_buffer.size()]; - - // initialize the next item. it may - // consist of at most chunk_size - // elements - current_item->n_items = 0; - while ((remaining_iterator_range.first != - remaining_iterator_range.second) - && - (current_item->n_items < chunk_size)) + public: + /** + * Constructor. Takes a + * reference to the object on + * which we will operate as + * well as a pointer to the + * function that will do the + * assembly. + */ + WorkerAndCopier (const std_cxx1x::function &worker, + const std_cxx1x::function &copier) + : + tbb::filter (/* is_serial= */ false), + worker (worker), + copier (copier) + {} + + + /** + * Work on an item. + */ + void *operator () (void *item) + { + // first unpack the current item + typedef + typename IteratorRangeToItemStream::ItemType + ItemType; + + ItemType *current_item = static_cast (item); + + // we need to find an unused scratch and corresponding copy + // data object in the list that + // corresponds to the current thread and then mark it as used. if + // we can't find one, create one + // + // as discussed in the discussion of the documentation of the + // IteratorRangeToItemStream::scratch_data variable, there is no + // need to synchronize access to this variable using a mutex + // as long as we have no yield-point in between. this means that + // we can't take an iterator into the list now and expect it to + // still be valid after calling the worker, but we at least do + // not have to lock the following section + ScratchData *scratch_data = 0; + CopyData *copy_data = 0; { - // initialize the iterators to work on with the elements - // of the vector that remaining_iterator_range - // points into - current_item->work_items[current_item->n_items] - = *remaining_iterator_range.first; - - ++remaining_iterator_range.first; - ++current_item->n_items; + typename ItemType::ScratchAndCopyDataList & + scratch_and_copy_data_list = current_item->scratch_and_copy_data->get(); + + // see if there is an unused object. if so, grab it and mark + // it as used + for (typename ItemType::ScratchAndCopyDataList::iterator + p = scratch_and_copy_data_list.begin(); + p != scratch_and_copy_data_list.end(); ++p) + if (p->currently_in_use == false) + { + scratch_data = p->scratch_data.get(); + copy_data = p->copy_data.get(); + p->currently_in_use = true; + break; + } + + // if no element in the list was found, create one and mark it as used + if (scratch_data == 0) + { + Assert (copy_data==0, ExcInternalError()); + scratch_data = new ScratchData(*current_item->sample_scratch_data); + copy_data = new CopyData(*current_item->sample_copy_data); + + typename ItemType::ScratchAndCopyDataList::value_type + new_scratch_object (scratch_data, copy_data, true); + scratch_and_copy_data_list.push_back (new_scratch_object); + } } - if (current_item->n_items == 0) - // there were no items - // left. terminate the pipeline + // then call the worker and copier function on each element of the chunk we were + // given. since these functions are called on separate threads, + // nothing good can happen if they throw an exception and we are best + // off catching it and showing an error message + for (unsigned int i=0; in_items; ++i) + { + try + { + if (worker) + worker (current_item->work_items[i], + *scratch_data, + *copy_data); + if (copier) + copier (*copy_data); + } + catch (const std::exception &exc) + { + Threads::internal::handle_std_exception (exc); + } + catch (...) + { + Threads::internal::handle_unknown_exception (); + } + } + + // finally mark the scratch object as unused again. as above, there + // is no need to lock anything here since the object we work on + // is thread-local + { + typename ItemType::ScratchAndCopyDataList & + scratch_and_copy_data_list = current_item->scratch_and_copy_data->get(); + + for (typename ItemType::ScratchAndCopyDataList::iterator p = + scratch_and_copy_data_list.begin(); p != scratch_and_copy_data_list.end(); + ++p) + if (p->scratch_data.get() == scratch_data) + { + Assert(p->currently_in_use == true, ExcInternalError()); + p->currently_in_use = false; + } + } + + // return an invalid item since we are at the end of the + // pipeline return 0; - else - { - ++n_emitted_items; - return current_item; } - } - - private: - /** - * The interval of iterators still to - * be worked on. This range will shrink - * over time. - */ - std::pair::const_iterator,typename std::vector::const_iterator> remaining_iterator_range; - - /** - * A ring buffer that will store items. - */ - std::vector ring_buffer; - - /** - * Pointer to a thread local variable identifying the scratch and - * copy data objects each thread will use. The same is true as - * discussed for the implementation in the - * Implementation2::IteratorRangeToItemStream class and the paper - * by Turcksin, Kronbichler and Bangerth. - */ - Threads::ThreadLocalStorage thread_local_scratch_and_copy; - - /** - * A reference to a sample scratch data that will be used to - * initialize the thread-local pointers to a scratch data object - * each of the worker tasks uses. - */ - const ScratchData &sample_scratch_data; - - /** - * A reference to a sample scratch data that will be used to - * initialize the thread-local pointers to a scratch data object - * each of the worker tasks uses. - */ - const CopyData &sample_copy_data; - - /** - * Counter for the number of emitted - * items. Each item may consist of up - * to chunk_size iterator elements. - */ - unsigned int n_emitted_items; - - /** - * Number of elements of the - * iterator range that each - * thread should work on - * sequentially; a large number - * makes sure that each thread - * gets a significant amount of - * work before the next task - * switch happens, whereas a - * small number is better for - * load balancing. - */ - const unsigned int chunk_size; - - /** - * Initialize the pointers and vector - * elements in the specified entry of - * the ring buffer. - */ - void init_buffer_elements (const unsigned int element) - { - Assert (ring_buffer[element].n_items == 0, - ExcInternalError()); - - ring_buffer[element].work_items - .resize (chunk_size, remaining_iterator_range.second); - ring_buffer[element].scratch_and_copy_data - = &thread_local_scratch_and_copy; - ring_buffer[element].sample_scratch_data - = &sample_scratch_data; - ring_buffer[element].sample_copy_data - = &sample_copy_data; - } - }; + private: + /** + * Pointer to the function + * that does the assembling + * on the sequence of cells. + */ + const std_cxx1x::function worker; - /** - * A class that manages calling the - * worker function on a number of - * parallel threads. Note that it is, in - * the TBB notation, a filter that can - * run in parallel. - */ - template - class WorkerAndCopier : public tbb::filter - { - public: - /** - * Constructor. Takes a - * reference to the object on - * which we will operate as - * well as a pointer to the - * function that will do the - * assembly. - */ - WorkerAndCopier (const std_cxx1x::function &worker, - const std_cxx1x::function &copier) - : - tbb::filter (/* is_serial= */ false), - worker (worker), - copier (copier) - {} - - - /** - * Work on an item. - */ - void *operator () (void *item) - { - // first unpack the current item - typedef - typename IteratorRangeToItemStream::ItemType - ItemType; - - ItemType *current_item = static_cast (item); - - // we need to find an unused scratch and corresponding copy - // data object in the list that - // corresponds to the current thread and then mark it as used. if - // we can't find one, create one - // - // as discussed in the discussion of the documentation of the - // IteratorRangeToItemStream::scratch_data variable, there is no - // need to synchronize access to this variable using a mutex - // as long as we have no yield-point in between. this means that - // we can't take an iterator into the list now and expect it to - // still be valid after calling the worker, but we at least do - // not have to lock the following section - ScratchData *scratch_data = 0; - CopyData *copy_data = 0; - { - typename ItemType::ScratchAndCopyDataList & - scratch_and_copy_data_list = current_item->scratch_and_copy_data->get(); - - // see if there is an unused object. if so, grab it and mark - // it as used - for (typename ItemType::ScratchAndCopyDataList::iterator - p = scratch_and_copy_data_list.begin(); - p != scratch_and_copy_data_list.end(); ++p) - if (p->currently_in_use == false) - { - scratch_data = p->scratch_data.get(); - copy_data = p->copy_data.get(); - p->currently_in_use = true; - break; - } - - // if no element in the list was found, create one and mark it as used - if (scratch_data == 0) - { - Assert (copy_data==0, ExcInternalError()); - scratch_data = new ScratchData(*current_item->sample_scratch_data); - copy_data = new CopyData(*current_item->sample_copy_data); - - typename ItemType::ScratchAndCopyDataList::value_type - new_scratch_object (scratch_data, copy_data, true); - scratch_and_copy_data_list.push_back (new_scratch_object); - } - } - - // then call the worker and copier function on each element of the chunk we were - // given. since these functions are called on separate threads, - // nothing good can happen if they throw an exception and we are best - // off catching it and showing an error message - for (unsigned int i=0; in_items; ++i) - { - try - { - if (worker) - worker (current_item->work_items[i], - *scratch_data, - *copy_data); - if (copier) - copier (*copy_data); - } - catch (const std::exception &exc) - { - Threads::internal::handle_std_exception (exc); - } - catch (...) - { - Threads::internal::handle_unknown_exception (); - } - } - - // finally mark the scratch object as unused again. as above, there - // is no need to lock anything here since the object we work on - // is thread-local - { - typename ItemType::ScratchAndCopyDataList & - scratch_and_copy_data_list = current_item->scratch_and_copy_data->get(); - - for (typename ItemType::ScratchAndCopyDataList::iterator p = - scratch_and_copy_data_list.begin(); p != scratch_and_copy_data_list.end(); - ++p) - if (p->scratch_data.get() == scratch_data) - { - Assert(p->currently_in_use == true, ExcInternalError()); - p->currently_in_use = false; - } - } - - // return an invalid item since we are at the end of the - // pipeline - return 0; - } - - - private: - /** - * Pointer to the function - * that does the assembling - * on the sequence of cells. - */ - const std_cxx1x::function worker; - - /** - * Pointer to the function that does the copying from - * local contribution to global object. - */ - const std_cxx1x::function copier; - }; + /** + * Pointer to the function that does the copying from + * local contribution to global object. + */ + const std_cxx1x::function copier; + }; } } @@ -1163,8 +1163,8 @@ namespace WorkStream typename Iterator, typename ScratchData, typename CopyData> - void - run (const Iterator &begin, + void + run (const Iterator &begin, const typename identity::type &end, Worker worker, Copier copier, @@ -1200,13 +1200,13 @@ namespace WorkStream { // need to check if the function is not the zero function. To // check zero-ness, create a C++ function out of it and check that - if (static_cast& >(worker)) - worker (i, scratch_data, copy_data); - if (static_cast& > - (copier)) - copier (copy_data); + if (static_cast& >(worker)) + worker (i, scratch_data, copy_data); + if (static_cast& > + (copier)) + copier (copy_data); } } #ifdef DEAL_II_WITH_THREADS @@ -1215,10 +1215,10 @@ namespace WorkStream // create the three stages of the pipeline internal::Implementation2::IteratorRangeToItemStream iterator_range_to_item_stream (begin, end, - queue_length, - chunk_size, - sample_scratch_data, - sample_copy_data); + queue_length, + chunk_size, + sample_scratch_data, + sample_copy_data); internal::Implementation2::Worker worker_filter (worker); @@ -1244,8 +1244,8 @@ namespace WorkStream typename Iterator, typename ScratchData, typename CopyData> - void - run (const std::vector > &colored_iterators, + void + run (const std::vector > &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, @@ -1273,17 +1273,17 @@ namespace WorkStream for (unsigned int color=0; color::const_iterator p = colored_iterators[color].begin(); - p != colored_iterators[color].end(); ++p) - { - // need to check if the function is not the zero function. To - // check zero-ness, create a C++ function out of it and check that - if (static_cast& >(worker)) - worker (*p, scratch_data, copy_data); - if (static_cast& >(copier)) - copier (copy_data); - } + p != colored_iterators[color].end(); ++p) + { + // need to check if the function is not the zero function. To + // check zero-ness, create a C++ function out of it and check that + if (static_cast& >(worker)) + worker (*p, scratch_data, copy_data); + if (static_cast& >(copier)) + copier (copy_data); + } } #ifdef DEAL_II_WITH_THREADS else // have TBB and use more than one thread @@ -1291,30 +1291,30 @@ namespace WorkStream // loop over the various colors of what we're given for (unsigned int color=0; color 0) - { - // create the three stages of the pipeline - internal::Implementation3::IteratorRangeToItemStream - iterator_range_to_item_stream (colored_iterators[color].begin(), - colored_iterators[color].end(), - queue_length, - chunk_size, - sample_scratch_data, - sample_copy_data); - - - internal::Implementation3::WorkerAndCopier - worker_and_copier_filter (worker, copier); - - // now create a pipeline from these stages - tbb::pipeline assembly_line; - assembly_line.add_filter (iterator_range_to_item_stream); - assembly_line.add_filter (worker_and_copier_filter); - - // and run it - assembly_line.run (queue_length); - - assembly_line.clear (); - } + { + // create the three stages of the pipeline + internal::Implementation3::IteratorRangeToItemStream + iterator_range_to_item_stream (colored_iterators[color].begin(), + colored_iterators[color].end(), + queue_length, + chunk_size, + sample_scratch_data, + sample_copy_data); + + + internal::Implementation3::WorkerAndCopier + worker_and_copier_filter (worker, copier); + + // now create a pipeline from these stages + tbb::pipeline assembly_line; + assembly_line.add_filter (iterator_range_to_item_stream); + assembly_line.add_filter (worker_and_copier_filter); + + // and run it + assembly_line.run (queue_length); + + assembly_line.clear (); + } } #endif }