blob: d9addeee89f865f8569a889aafe4dffff2d556ef [file] [log] [blame]
/*
* @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