Skip to content

Commit c6876e2

Browse files
authored
impl: a benchmark and two small (non-)optimizations for Options (#8487)
With the growing ubiquity of `Options`, it seems like a good idea to keep an eye on its performance. To that end, add a micro benchmark which can be expanded as we see fit. For now, simply benchmark `get<T>()` performance on both present and default option types. Time: The original motivation for this PR was removing the function- static synchronization from the "present" path, however the benchmark indicates that this is only a slight win (<0.5%), and it seems to cost us a similar amount in the "default" path. At least it makes those cases similar in performance, which is a good thing given that it is unclear which is more common. (I guess it is also a good thing to confirm that function-static synchronization is fast.) Space: Template the allocation of the default value for an option on `FooOption::Type` rather than `FooOption`. This means, for example, that we only allocate a single `std::string` default value for all `std::string`-based options. Also add a note about how `unordered_map<std::type_index, ...>` works when indexed by `typeid(T)`.
1 parent 258855c commit c6876e2

4 files changed

Lines changed: 76 additions & 3 deletions

File tree

google/cloud/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ function (google_cloud_cpp_common_define_benchmarks)
177177
find_package(benchmark CONFIG REQUIRED)
178178

179179
set(google_cloud_cpp_common_benchmarks # cmake-format: sort
180-
)
180+
options_benchmark.cc)
181181

182182
# Export the list of benchmarks to a .bzl file so we do not need to maintain
183183
# the list in two places.

google/cloud/google_cloud_cpp_common_benchmarks.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
"""Automatically generated unit tests list - DO NOT EDIT."""
1818

1919
google_cloud_cpp_common_benchmarks = [
20+
"options_benchmark.cc",
2021
]

google/cloud/options.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ namespace internal {
3333
Options MergeOptions(Options, Options);
3434
void CheckExpectedOptionsImpl(std::set<std::type_index> const&, Options const&,
3535
char const*);
36+
template <typename T>
37+
inline T const& DefaultValue() {
38+
static auto const* const kDefaultValue = new T{};
39+
return *kDefaultValue;
40+
}
3641
} // namespace internal
3742

3843
/**
@@ -163,9 +168,8 @@ class Options {
163168
*/
164169
template <typename T>
165170
ValueTypeT<T> const& get() const {
166-
static auto const* const kDefaultValue = new ValueTypeT<T>{};
167171
auto const it = m_.find(typeid(T));
168-
if (it == m_.end()) return *kDefaultValue;
172+
if (it == m_.end()) return internal::DefaultValue<ValueTypeT<T>>();
169173
auto const* value = it->second->data_address();
170174
return *reinterpret_cast<ValueTypeT<T> const*>(value);
171175
}
@@ -232,6 +236,9 @@ class Options {
232236
ValueTypeT<T> value_;
233237
};
234238

239+
// Note that (1) `typeid(T)` returns a `std::type_info const&`, but that
240+
// implicitly converts to a `std::type_index`, and (2) `std::hash<>` is
241+
// specialized for `std::type_index` to use `std::type_index::hash_code()`.
235242
std::unordered_map<std::type_index, std::unique_ptr<DataHolder>> m_;
236243
};
237244

google/cloud/options_benchmark.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/options.h"
16+
#include <benchmark/benchmark.h>
17+
#include <string>
18+
19+
namespace google {
20+
namespace cloud {
21+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
22+
namespace {
23+
24+
// Run on (96 X 2000 MHz CPU s)
25+
// CPU Caches:
26+
// L1 Data 32 KiB (x48)
27+
// L1 Instruction 32 KiB (x48)
28+
// L2 Unified 1024 KiB (x48)
29+
// L3 Unified 39424 KiB (x2)
30+
// Load Average: 0.49, 0.35, 0.89
31+
// ----------------------------------------------------------------------
32+
// Benchmark Time CPU Iterations
33+
// ----------------------------------------------------------------------
34+
// BM_OptionsOneElementDefault 25.6 ns 25.6 ns 27332591
35+
// BM_OptionsOneElementPresent 28.0 ns 28.0 ns 24999101
36+
37+
struct StringOptionDefault {
38+
using Type = std::string;
39+
};
40+
struct StringOptionPresent {
41+
using Type = std::string;
42+
};
43+
44+
void BM_OptionsOneElementDefault(benchmark::State& state) {
45+
auto const opts = Options{}.set<StringOptionPresent>(
46+
"You will do foolish things, but do them with enthusiasm.");
47+
for (auto _ : state) {
48+
benchmark::DoNotOptimize(opts.get<StringOptionDefault>());
49+
}
50+
}
51+
BENCHMARK(BM_OptionsOneElementDefault);
52+
53+
void BM_OptionsOneElementPresent(benchmark::State& state) {
54+
auto const opts = Options{}.set<StringOptionPresent>(
55+
"You will do foolish things, but do them with enthusiasm.");
56+
for (auto _ : state) {
57+
benchmark::DoNotOptimize(opts.get<StringOptionPresent>());
58+
}
59+
}
60+
BENCHMARK(BM_OptionsOneElementPresent);
61+
62+
} // namespace
63+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
64+
} // namespace cloud
65+
} // namespace google

0 commit comments

Comments
 (0)