3038054c68
From-SVN: r204173
294 lines
9.1 KiB
C++
294 lines
9.1 KiB
C++
/*
|
|
* @copyright
|
|
* Copyright (C) 2009-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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* reducer_ostream.h
|
|
*
|
|
* Purpose: Hyper-object to write to 'std::ostream's
|
|
*
|
|
* Classes: reducer_ostream
|
|
*
|
|
* Description:
|
|
* ============
|
|
* Output streams ('std::ostream's) are a convenient means of writing text to
|
|
* files, the user console, or sockets. In a serial program, text is written
|
|
* to an ostream in a specific, logical order. For example, computing while
|
|
* traversing a data structure and printing them to an 'ostream' will result
|
|
* in the values being printed in the order of traversal. In a parallel
|
|
* version of the same program, however, different parts of the data structure
|
|
* may be traversed in a different order, resulting in a non-deterministic
|
|
* ordering of the stream. Worse, multiple strands may write to the same
|
|
* stream simultaneously, resulting in a data race. Replacing the
|
|
* 'std::ostream' with a 'cilk::reducer_ostream' will solve both problems: Data
|
|
* will appeaer in the stream in the same order as it would for the serial
|
|
* program, and there will be no races (no locks) on the common stream.
|
|
*
|
|
* Usage Example:
|
|
* ==============
|
|
* Assume we wish to traverse an array of objects, performing an operation on
|
|
* each object and writing the result to a file. Without a reducer_ostream,
|
|
* we have a race on the 'output' file stream:
|
|
*..
|
|
* void compute(std::ostream& os, double x)
|
|
* {
|
|
* // Perform some significant computation and print the result:
|
|
* os << std::asin(x);
|
|
* }
|
|
*
|
|
* int test()
|
|
* {
|
|
* const std::size_t ARRAY_SIZE = 1000000;
|
|
* extern double myArray[ARRAY_SIZE];
|
|
*
|
|
* std::ofstream output("output.txt");
|
|
* cilk_for (std::size_t i = 0; i < ARRAY_SIZE; ++i)
|
|
* {
|
|
* compute(output, myArray[i]);
|
|
* }
|
|
*
|
|
* return 0;
|
|
* }
|
|
*..
|
|
* The race is solved by using a reducer_ostream to proxy the 'output' file:
|
|
*..
|
|
* void compute(cilk::reducer_ostream& os, double x)
|
|
* {
|
|
* // Perform some significant computation and print the result:
|
|
* *os << std::asin(x);
|
|
* }
|
|
*
|
|
* int test()
|
|
* {
|
|
* const std::size_t ARRAY_SIZE = 1000000;
|
|
* extern double myArray[ARRAY_SIZE];
|
|
*
|
|
* std::ofstream output("output.txt");
|
|
* cilk::reducer_ostream hyper_output(output);
|
|
* cilk_for (std::size_t i = 0; i < ARRAY_SIZE; ++i)
|
|
* {
|
|
* compute(hyper_output, myArray[i]);
|
|
* }
|
|
*
|
|
* return 0;
|
|
* }
|
|
*..
|
|
*
|
|
* Limitations:
|
|
* ============
|
|
* There are two possible values for the formatting flags immediately after a
|
|
* 'cilk_spawn' statement: they may either have the value that was set by the
|
|
* spawn function, or they may have default values. Because of
|
|
* non-determinism in the processor scheduling, there is no way to determine
|
|
* which it will be. Similarly, the formatting flags after a 'cilk_sync' may
|
|
* or may not have the same value as before the sync. Therefore, one must use
|
|
* a disciplined coding style to avoid formatting errors. There are two
|
|
* approaches to mitigating the problem: The first is to eliminate the
|
|
* difference between the two possible outcomes by ensuring that the spawned
|
|
* function always returns the flags to their initial state:
|
|
*..
|
|
* void compute(cilk::reducer_ostream& os, double x)
|
|
* {
|
|
* // Perform some significant computation and print the result:
|
|
* int saveprec = os.precision(5);
|
|
* os << std::asin(x);
|
|
* os.precision(saveprec);
|
|
* }
|
|
*..
|
|
* The second approach is to write your streaming operations such that they
|
|
* don't depend on the previous state of the formatting flags by setting any
|
|
* important flags before every block of output:
|
|
*..
|
|
* cilk_spawn compute(hyper_output, value);
|
|
*
|
|
* hyper_output->precision(2); // Don't depend on previous precision
|
|
* *hyper_output << f();
|
|
* *hyper_output << g();
|
|
*..
|
|
* Another concern is memory usage. A reducer_ostream will buffer as much text
|
|
* as necessary to ensure that the order of output matches that of the serial
|
|
* version of the program. If all spawn branches perform an equal amount of
|
|
* output, then one can expect that half of the output before a sync will be
|
|
* buffered in memory. This hyperobject is therefore not well suited for
|
|
* serializing very large quantities of text output.
|
|
*/
|
|
|
|
#ifndef REDUCER_OSTREAM_H_INCLUDED
|
|
#define REDUCER_OSTREAM_H_INCLUDED
|
|
|
|
#include <cilk/reducer.h>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
namespace cilk {
|
|
|
|
/**
|
|
* @brief Class 'reducer_ostream' is the representation of a hyperobject for
|
|
* output text streaming.
|
|
*/
|
|
class reducer_ostream
|
|
{
|
|
public:
|
|
/// Internal representation of the per-strand view of the data for reducer_ostream
|
|
class View: public std::ostream
|
|
{
|
|
public:
|
|
/// Type of the std::stream reducer_ostream is based on
|
|
typedef std::ostream Base;
|
|
|
|
friend class reducer_ostream;
|
|
|
|
View():
|
|
std::ostream(0)
|
|
{
|
|
Base::rdbuf(&strbuf_);
|
|
};
|
|
|
|
private:
|
|
void use_ostream (const std::ostream &os)
|
|
{
|
|
Base::rdbuf(os.rdbuf());
|
|
Base::flags(os.flags()); // Copy formatting flags
|
|
Base::setstate(os.rdstate()); // Copy error state
|
|
}
|
|
|
|
private:
|
|
std::stringbuf strbuf_;
|
|
};
|
|
|
|
public:
|
|
/// Definition of data view, operation, and identity for reducer_ostream
|
|
struct Monoid: monoid_base< View >
|
|
{
|
|
static void reduce (View *left, View *right);
|
|
};
|
|
|
|
private:
|
|
// Hyperobject to serve up views
|
|
reducer<Monoid> imp_;
|
|
|
|
// Methods that provide the API for the reducer
|
|
public:
|
|
|
|
// Construct an initial 'reducer_ostream' from an 'std::ostream'. The
|
|
// specified 'os' stream is used as the eventual destination for all
|
|
// text streamed to this hyperobject.
|
|
explicit reducer_ostream(const std::ostream &os);
|
|
|
|
// Return a modifiable reference to the underlying 'ostream' object.
|
|
std::ostream& get_reference();
|
|
|
|
/**
|
|
* Append data from some type to the reducer_ostream
|
|
*
|
|
* @param v Value to be appended to the reducer_ostream
|
|
*/
|
|
template<typename T>
|
|
std::ostream &
|
|
operator<< (const T &v)
|
|
{
|
|
return imp_.view() << v;
|
|
}
|
|
|
|
/**
|
|
* Append data from a std::ostream to the reducer_ostream
|
|
*
|
|
* @param _Pfn std::ostream to copy from
|
|
*/
|
|
std::ostream &
|
|
operator<< (std::ostream &(*_Pfn)(std::ostream &))
|
|
{
|
|
View &v = imp_.view();
|
|
|
|
return ((*_Pfn)(v));
|
|
}
|
|
|
|
reducer_ostream& operator*() { return *this; }
|
|
reducer_ostream const& operator*() const { return *this; }
|
|
|
|
reducer_ostream* operator->() { return this; }
|
|
reducer_ostream const* operator->() const { return this; }
|
|
};
|
|
|
|
|
|
// -------------------------------------------
|
|
// class reducer_ostream::Monoid
|
|
// -------------------------------------------
|
|
|
|
/**
|
|
* Appends string from "right" reducer_basic_string onto the end of
|
|
* the "left". When done, the "right" reducer_basic_string is empty.
|
|
*/
|
|
void
|
|
reducer_ostream::Monoid::reduce(View *left, View *right)
|
|
{
|
|
left->operator<< (&right->strbuf_);
|
|
}
|
|
|
|
// --------------------------
|
|
// class reducer_ostream
|
|
// --------------------------
|
|
|
|
/**
|
|
* Construct a reducer_ostream which will write to the specified std::ostream
|
|
*
|
|
* @param os std::ostream to write to
|
|
*/
|
|
inline
|
|
reducer_ostream::reducer_ostream(const std::ostream &os) :
|
|
imp_()
|
|
{
|
|
View &v = imp_.view();
|
|
|
|
v.use_ostream(os);
|
|
}
|
|
|
|
/**
|
|
* Get a reference to the std::ostream
|
|
*/
|
|
inline
|
|
std::ostream &
|
|
reducer_ostream::get_reference()
|
|
{
|
|
View &v = imp_.view();
|
|
|
|
return v;
|
|
}
|
|
|
|
} // namespace cilk
|
|
|
|
#endif // REDUCER_OSTREAM_H_INCLUDED
|
|
|