no_destructor_benchmark.cc (5089B)
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: 38 BM_Blob(int val) { for (auto& d : data_) d = val; } 39 BM_Blob() : BM_Blob(-1) {} 40 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> 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> 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 73 void BM_Direct(benchmark::State& state) { 74 for (auto s : state) { 75 direct_blob.Verify(0); 76 } 77 } 78 BENCHMARK(BM_Direct); 79 80 void BM_NoDestr(benchmark::State& state) { 81 for (auto s : state) { 82 NoDestrBlobFunc<0>().Verify(0); 83 } 84 } 85 BENCHMARK(BM_NoDestr); 86 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> 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<> 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). 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> 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 157 void BM_DirectMany(benchmark::State& state) { BM_Many<kDirect>(state); } 158 void BM_NoDestrMany(benchmark::State& state) { BM_Many<kNoDestr>(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