1 // Copyright 2023 The Abseil Authors.
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 <cstdint>
16
17 #include "absl/base/internal/raw_logging.h"
18 #include "absl/base/no_destructor.h"
19 #include "benchmark/benchmark.h"
20
21 namespace {
22
23 // Number of static-NoDestructor-in-a-function to exercise.
24 // This must be low enough not to hit template instantiation limits
25 // (happens around 1000).
26 constexpr int kNumObjects = 1; // set to 512 when doing benchmarks
27 // 1 is faster to compile: just one templated
28 // function instantiation
29
30 // Size of individual objects to benchmark static-NoDestructor-in-a-function
31 // usage with.
32 constexpr int kObjSize = sizeof(void*)*1;
33
34 // Simple object of kObjSize bytes (rounded to int).
35 // We benchmark complete reading of its state via Verify().
36 class BM_Blob {
37 public:
BM_Blob(int val)38 BM_Blob(int val) { for (auto& d : data_) d = val; }
BM_Blob()39 BM_Blob() : BM_Blob(-1) {}
Verify(int val) const40 void Verify(int val) const { // val must be the c-tor argument
41 for (auto& d : data_) ABSL_INTERNAL_CHECK(d == val, "");
42 }
43 private:
44 int data_[kObjSize / sizeof(int) > 0 ? kObjSize / sizeof(int) : 1];
45 };
46
47 // static-NoDestructor-in-a-function pattern instances.
48 // We'll instantiate kNumObjects of them.
49 template<int i>
NoDestrBlobFunc()50 const BM_Blob& NoDestrBlobFunc() {
51 static absl::NoDestructor<BM_Blob> x(i);
52 return *x;
53 }
54
55 // static-heap-ptr-in-a-function pattern instances
56 // We'll instantiate kNumObjects of them.
57 template<int i>
OnHeapBlobFunc()58 const BM_Blob& OnHeapBlobFunc() {
59 static BM_Blob* x = new BM_Blob(i);
60 return *x;
61 }
62
63 // Type for NoDestrBlobFunc or OnHeapBlobFunc.
64 typedef const BM_Blob& (*FuncType)();
65
66 // ========================================================================= //
67 // Simple benchmarks that read a single BM_Blob over and over, hence
68 // all they touch fits into L1 CPU cache:
69
70 // Direct non-POD global variable (style guide violation) as a baseline.
71 static BM_Blob direct_blob(0);
72
BM_Direct(benchmark::State & state)73 void BM_Direct(benchmark::State& state) {
74 for (auto s : state) {
75 direct_blob.Verify(0);
76 }
77 }
78 BENCHMARK(BM_Direct);
79
BM_NoDestr(benchmark::State & state)80 void BM_NoDestr(benchmark::State& state) {
81 for (auto s : state) {
82 NoDestrBlobFunc<0>().Verify(0);
83 }
84 }
85 BENCHMARK(BM_NoDestr);
86
BM_OnHeap(benchmark::State & state)87 void BM_OnHeap(benchmark::State& state) {
88 for (auto s : state) {
89 OnHeapBlobFunc<0>().Verify(0);
90 }
91 }
92 BENCHMARK(BM_OnHeap);
93
94 // ========================================================================= //
95 // Benchmarks that read kNumObjects of BM_Blob over and over, hence with
96 // appropriate values of sizeof(BM_Blob) and kNumObjects their working set
97 // can exceed a given layer of CPU cache.
98
99 // Type of benchmark to select between NoDestrBlobFunc and OnHeapBlobFunc.
100 enum BM_Type { kNoDestr, kOnHeap, kDirect };
101
102 // BlobFunc<n>(t, i) returns the i-th function of type t.
103 // n must be larger than i (we'll use kNumObjects for n).
104 template<int n>
BlobFunc(BM_Type t,int i)105 FuncType BlobFunc(BM_Type t, int i) {
106 if (i == n) {
107 switch (t) {
108 case kNoDestr: return &NoDestrBlobFunc<n>;
109 case kOnHeap: return &OnHeapBlobFunc<n>;
110 case kDirect: return nullptr;
111 }
112 }
113 return BlobFunc<n-1>(t, i);
114 }
115
116 template<>
BlobFunc(BM_Type t,int i)117 FuncType BlobFunc<0>(BM_Type t, int i) {
118 ABSL_INTERNAL_CHECK(i == 0, "");
119 switch (t) {
120 case kNoDestr: return &NoDestrBlobFunc<0>;
121 case kOnHeap: return &OnHeapBlobFunc<0>;
122 case kDirect: return nullptr;
123 }
124 return nullptr;
125 }
126
127 // Direct non-POD global variables (style guide violation) as a baseline.
128 static BM_Blob direct_blobs[kNumObjects];
129
130 // Helper that cheaply maps benchmark iteration to randomish index in
131 // [0, kNumObjects).
RandIdx(int i)132 int RandIdx(int i) {
133 // int64 is to avoid overflow and generating negative return values:
134 return (static_cast<int64_t>(i) * 13) % kNumObjects;
135 }
136
137 // Generic benchmark working with kNumObjects for any of the possible BM_Type.
138 template <BM_Type t>
BM_Many(benchmark::State & state)139 void BM_Many(benchmark::State& state) {
140 FuncType funcs[kNumObjects];
141 for (int i = 0; i < kNumObjects; ++i) {
142 funcs[i] = BlobFunc<kNumObjects-1>(t, i);
143 }
144 if (t == kDirect) {
145 for (auto s : state) {
146 int idx = RandIdx(state.iterations());
147 direct_blobs[idx].Verify(-1);
148 }
149 } else {
150 for (auto s : state) {
151 int idx = RandIdx(state.iterations());
152 funcs[idx]().Verify(idx);
153 }
154 }
155 }
156
BM_DirectMany(benchmark::State & state)157 void BM_DirectMany(benchmark::State& state) { BM_Many<kDirect>(state); }
BM_NoDestrMany(benchmark::State & state)158 void BM_NoDestrMany(benchmark::State& state) { BM_Many<kNoDestr>(state); }
BM_OnHeapMany(benchmark::State & state)159 void BM_OnHeapMany(benchmark::State& state) { BM_Many<kOnHeap>(state); }
160
161 BENCHMARK(BM_DirectMany);
162 BENCHMARK(BM_NoDestrMany);
163 BENCHMARK(BM_OnHeapMany);
164
165 } // namespace
166