/* * @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 #include #include 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 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 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