-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
yaml_performance_test.cc
209 lines (182 loc) · 6.16 KB
/
yaml_performance_test.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include <vector>
#include <Eigen/Core>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <unsupported/Eigen/AutoDiff>
#include "drake/common/eigen_types.h"
#include "drake/common/name_value.h"
#include "drake/common/test_utilities/limit_malloc.h"
#include "drake/common/yaml/yaml_io.h"
#include "drake/common/yaml/yaml_read_archive.h"
namespace drake {
namespace yaml {
namespace {
using drake::yaml::SaveYamlString;
using drake::yaml::internal::YamlReadArchive;
struct Inner {
template <typename Archive>
void Serialize(Archive* a) {
a->Visit(DRAKE_NVP(doubles));
}
std::vector<double> doubles;
};
struct Outer {
template <typename Archive>
void Serialize(Archive* a) {
a->Visit(DRAKE_NVP(inners));
}
std::vector<Inner> inners;
};
struct Map {
template <typename Archive>
void Serialize(Archive* a) {
a->Visit(DRAKE_NVP(items));
}
std::map<std::string, Outer> items;
};
GTEST_TEST(YamlPerformanceTest, VectorNesting) {
// Populate a resonably-sized but non-trival set of data -- 50,000 numbers
// arranged into a map with nested vectors.
const int kDim = 100;
Map data;
double dummy = 1.0;
const std::vector keys{"a", "b", "c", "d", "e"};
for (const char* const key : keys) {
Outer& outer = data.items[key];
outer.inners.resize(kDim);
for (Inner& inner : outer.inners) {
inner.doubles.resize(kDim, dummy);
dummy += 1.0;
}
}
// Convert the `Map data` into yaml string format.
const std::string yaml_data = SaveYamlString(data, "doc");
// Parse the yaml string into a node tree while checking that resource
// usage is somewhat bounded.
internal::Node yaml_root = internal::Node::MakeNull();
{
test::LimitMalloc guard({.max_num_allocations = 5'000'000});
yaml_root = YamlReadArchive::LoadStringAsNode(yaml_data, "doc");
}
// Transfer the node tree into a C++ structure while checking that resource
// usage is sane.
Map new_data;
{
// When the performance of parsing was fixed and this test was added, this
// Accept operation used about 51,000 allocations and took about 1 second
// of wall clock time (for both release and debug builds).
//
// The prior implementation with gratuitous copies used over 2.6 billion
// allocations and took more than 10 minutes of wall clock time in a
// release build.
//
// We'll set the hard limit ~20x higher than currently observed to allow
// some flux as library implementations evolve, etc.
test::LimitMalloc guard({.max_num_allocations = 1'000'000});
const LoadYamlOptions default_options;
YamlReadArchive archive(std::move(yaml_root), default_options);
archive.Accept(&new_data);
}
// Double-check that we actually did the work.
ASSERT_EQ(new_data.items.size(), keys.size());
for (const char* const key : keys) {
Outer& outer = new_data.items[key];
ASSERT_EQ(outer.inners.size(), kDim);
for (Inner& inner : outer.inners) {
ASSERT_EQ(inner.doubles.size(), kDim);
}
}
}
} // namespace
} // namespace yaml
} // namespace drake
using ADS1 = Eigen::AutoDiffScalar<drake::VectorX<double>>;
using ADS2 = Eigen::AutoDiffScalar<drake::VectorX<ADS1>>;
// Add ADL Serialize method to Eigen::AutoDiffScalar.
namespace Eigen {
template <typename Archive>
void Serialize(Archive* a, ADS2* x) {
a->Visit(drake::MakeNameValue("value", &(x->value())));
a->Visit(drake::MakeNameValue("derivatives", &(x->derivatives())));
}
template <typename Archive>
void Serialize(Archive* a, ADS1* x) {
a->Visit(drake::MakeNameValue("value", &(x->value())));
a->Visit(drake::MakeNameValue("derivatives", &(x->derivatives())));
}
} // namespace Eigen
namespace drake {
namespace yaml {
namespace {
struct BigEigen {
template <typename Archive>
void Serialize(Archive* a) {
a->Visit(DRAKE_NVP(value));
}
MatrixX<ADS2> value;
};
GTEST_TEST(YamlPerformanceTest, EigenMatrix) {
// Populate a resonably-sized but non-trival set of data, about ~10,000
// numbers stored at various levels of nesting.
BigEigen data;
const int kDim = 10;
data.value.resize(kDim, kDim);
double dummy = 1.0;
for (int i = 0; i < data.value.rows(); ++i) {
for (int j = 0; j < data.value.cols(); ++j) {
ADS2& x = data.value(i, j);
x.value() = dummy;
dummy += 1.0;
x.derivatives().resize(kDim);
for (int k = 0; k < x.derivatives().size(); ++k) {
ADS1& y = x.derivatives()(k);
y.value() = dummy;
dummy += 1.0;
y.derivatives() = Eigen::VectorXd::Zero(kDim);
}
}
}
// Convert the `BigEigen data` into yaml string format.
const std::string yaml_data = SaveYamlString(data, "doc");
// Parse the yaml string into a node tree while checking that resource
// usage is somewhat bounded.
internal::Node yaml_root = internal::Node::MakeNull();
{
test::LimitMalloc guard({.max_num_allocations = 5'000'000});
yaml_root = YamlReadArchive::LoadStringAsNode(yaml_data, "doc");
}
// Transfer the node tree into a C++ structure while checking that resource
// usage is sane.
BigEigen new_data;
{
// When the performance of parsing was fixed and this test was added, this
// Accept operation used about 12,000 allocations and took less than 1
// second of wall clock time (for both release and debug builds).
//
// The prior implementation with gratuitous copies used over 1.6 million
// allocations.
//
// We'll set the hard limit ~20x higher than currently observed to allow
// some flux as library implementations evolve, etc.
test::LimitMalloc guard({.max_num_allocations = 250000});
const LoadYamlOptions default_options;
YamlReadArchive archive(std::move(yaml_root), default_options);
archive.Accept(&new_data);
}
// Double-check that we actually did the work.
ASSERT_EQ(new_data.value.rows(), kDim);
ASSERT_EQ(new_data.value.cols(), kDim);
for (int i = 0; i < kDim; ++i) {
for (int j = 0; j < kDim; ++j) {
ADS2& x = new_data.value(i, j);
ASSERT_EQ(x.derivatives().size(), kDim);
for (int k = 0; k < kDim; ++k) {
ADS1& y = x.derivatives()(k);
ASSERT_EQ(y.derivatives().size(), kDim);
}
}
}
}
} // namespace
} // namespace yaml
} // namespace drake