Skip to content

Commit 414abec

Browse files
pcaspersjenkins
authored andcommitted
QPR-12517 - Enhance class InMemoryReport to support caching of data to disk
1 parent be96102 commit 414abec

3 files changed

Lines changed: 91 additions & 4 deletions

File tree

OREAnalytics/test/cube.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
#include <ql/time/daycounters/actualactual.hpp>
5454
#include <oret/toplevelfixture.hpp>
5555
#include <test/oreatoplevelfixture.hpp>
56+
#include <ored/report/inmemoryreport.hpp>
57+
#include <orea/app/reportwriter.hpp>
5658

5759
#include "testmarket.hpp"
5860

@@ -537,6 +539,49 @@ BOOST_AUTO_TEST_CASE(testDoublePrecisionJaggedCube) {
537539
IndexManager::instance().clearHistories();
538540
}
539541

542+
string writeCube(const QuantLib::ext::shared_ptr<NPVCube>& cube, Size bufferSize) {
543+
auto report = QuantLib::ext::make_shared<InMemoryReport>(bufferSize);
544+
ReportWriter().writeCube(*report, cube);
545+
string fileName = boost::filesystem::unique_path().string();
546+
report->toFile(fileName);
547+
return fileName;
548+
}
549+
550+
void diffFiles(string filename1, string filename2) {
551+
std::ifstream ifs1(filename1);
552+
std::ifstream ifs2(filename2);
553+
554+
std::istream_iterator<char> b1(ifs1), e1;
555+
std::istream_iterator<char> b2(ifs2), e2;
556+
557+
BOOST_CHECK_EQUAL_COLLECTIONS(b1, e1, b2, e2);
558+
}
559+
560+
// Test the functionality of class InMemoryReport to cache data on disk
561+
BOOST_AUTO_TEST_CASE(testInMemoryReportBuffer) {
562+
563+
// Generate a cube
564+
std::set<string> ids{string("id")}; // the overlap doesn't matter
565+
vector<Date> dates(50, Date());
566+
Size samples = 200;
567+
Size depth = 6;
568+
auto c = QuantLib::ext::make_shared<SinglePrecisionInMemoryCubeN>(Date(), ids, dates, samples, depth);
569+
570+
// From the cube, generate multiple copies of the report, each of which which will have ~60K rows.
571+
// Specify different values for the buffer size in InMemoryReport:
572+
string filename_0 = writeCube(c, 0); // no buffering
573+
string filename_100 = writeCube(c, 100);
574+
string filename_1000 = writeCube(c, 1000);
575+
string filename_10000 = writeCube(c, 10000);
576+
string filename_100000 = writeCube(c, 100000); // buffer size > report size, resulting in no buffering
577+
578+
// Verify that buffering generates the same output as no buffering
579+
diffFiles(filename_0, filename_100);
580+
diffFiles(filename_0, filename_1000);
581+
diffFiles(filename_0, filename_10000);
582+
diffFiles(filename_0, filename_100000);
583+
}
584+
540585
BOOST_AUTO_TEST_SUITE_END()
541586

542587
BOOST_AUTO_TEST_SUITE_END()

OREData/ored/report/inmemoryreport.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@
1717
*/
1818

1919
#include <ored/report/inmemoryreport.hpp>
20+
#include <ored/utilities/serializationdate.hpp>
21+
#include <ored/utilities/serializationperiod.hpp>
2022

2123
#include <boost/algorithm/string/join.hpp>
24+
#include <boost/serialization/serialization.hpp>
25+
#include <boost/serialization/vector.hpp>
26+
#include <boost/serialization/variant.hpp>
27+
#include <boost/archive/binary_oarchive.hpp>
28+
#include <boost/archive/binary_iarchive.hpp>
29+
30+
#include <fstream>
2231

2332
namespace ore {
2433
namespace data {
@@ -27,15 +36,26 @@ Report& InMemoryReport::addColumn(const string& name, const ReportType& rt, Size
2736
headers_.push_back(name);
2837
columnTypes_.push_back(rt);
2938
columnPrecision_.push_back(precision);
30-
data_.push_back(vector<ReportType>()); // Initialise vector for
39+
data_.push_back(vector<ReportType>()); // Initialise vector for column
3140
i_++;
3241
return *this;
3342
}
3443

3544
Report& InMemoryReport::next() {
36-
QL_REQUIRE(i_ == headers_.size(), "Cannot go to next line, only " << i_ << " entires filled, report headers are: "
45+
QL_REQUIRE(i_ == headers_.size(), "Cannot go to next line, only " << i_ << " entries filled, report headers are: "
3746
<< boost::join(headers_, ","));
3847
i_ = 0;
48+
if (bufferSize_ && data_[0].size() == bufferSize_ && !headers_.empty()) {
49+
std::string s = std::tmpnam(nullptr);
50+
std::ofstream os(s.c_str(), std::ios::binary);
51+
boost::archive::binary_oarchive oa(os, boost::archive::no_header);
52+
for (Size i = 0; i < headers_.size(); i++) {
53+
oa << data_[i];
54+
data_[i].clear();
55+
}
56+
os.close();
57+
files_.push_back(s);
58+
}
3959
return *this;
4060
}
4161

@@ -85,6 +105,8 @@ void InMemoryReport::end() {
85105
}
86106

87107
const vector<Report::ReportType>& InMemoryReport::data(Size i) const {
108+
QL_REQUIRE(files_.empty(), "Member function InMemoryReport::data() is not supported "
109+
"when buffering is active");
88110
QL_REQUIRE(data_[i].size() == rows(), "internal error: report column "
89111
<< i << " (" << header(i) << ") contains " << data_[i].size()
90112
<< " rows, expected are " << rows()
@@ -103,6 +125,24 @@ void InMemoryReport::toFile(const string& filename, const char sep, const bool c
103125

104126
auto numColumns = columns();
105127
if (numColumns > 0) {
128+
129+
for (auto &f : files_) {
130+
vector<vector<ReportType>> data(numColumns);
131+
std::ifstream is(f.c_str(), std::ios::binary);
132+
boost::archive::binary_iarchive ia(is, boost::archive::no_header);
133+
for (Size i = 0; i < numColumns; i++) {
134+
ia >> data[i];
135+
}
136+
is.close();
137+
138+
for (Size i = 0; i < data[0].size(); i++) {
139+
cReport.next();
140+
for (Size j = 0; j < numColumns; j++) {
141+
cReport.add(data[j][i]);
142+
}
143+
}
144+
}
145+
106146
auto numRows = data_[0].size();
107147

108148
for (Size i = 0; i < numRows; i++) {

OREData/ored/report/inmemoryreport.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ using std::vector;
4040
*/
4141
class InMemoryReport : public Report {
4242
public:
43-
InMemoryReport() : i_(0) {}
43+
explicit InMemoryReport(Size bufferSize=100000) : i_(0), bufferSize_(bufferSize) {}
4444

4545
Report& addColumn(const string& name, const ReportType& rt, Size precision = 0) override;
4646
Report& next() override;
@@ -50,7 +50,7 @@ class InMemoryReport : public Report {
5050

5151
// InMemoryInterface
5252
Size columns() const { return headers_.size(); }
53-
Size rows() const { return columns() == 0 ? 0 : data_[0].size(); }
53+
Size rows() const { return columns() == 0 ? 0 : files_.size() * bufferSize_ + data_[0].size(); }
5454
const string& header(Size i) const { return headers_[i]; }
5555
bool hasHeader(string h) const { return std::find(headers_.begin(), headers_.end(), h) != headers_.end(); }
5656
ReportType columnType(Size i) const { return columnTypes_[i]; }
@@ -63,10 +63,12 @@ class InMemoryReport : public Report {
6363

6464
private:
6565
Size i_;
66+
Size bufferSize_;
6667
vector<string> headers_;
6768
vector<ReportType> columnTypes_;
6869
vector<Size> columnPrecision_;
6970
vector<vector<ReportType>> data_;
71+
vector<string> files_;
7072
};
7173

7274
//! InMemoryReport with access to plain types instead of boost::variant<>, to facilitate language bindings

0 commit comments

Comments
 (0)