• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // This is a framework to measure the memory overhead of different containers.
6 // Under the hood, it works by logging allocations and frees using an allocator
7 // hook.
8 //
9 // Since the free callback does not report a size, and the allocator hooks run
10 // in the middle of allocation, the logger simply takes the simplest approach
11 // and logs out the raw data, relying on analyze_containers_memory_usage.py to
12 // turn the raw output into useful numbers.
13 //
14 // The output of consists of m (number of different key/value combinations being
15 // tested) x n (number of different map types being tested) sections:
16 //
17 // <key type 1> -> <value type 1>
18 // ===== <map type 1> =====
19 // iteration 0
20 // alloc <address 1> size <size 1>
21 // iteration 1
22 // alloc <address 2> size <size 2>
23 // free <address 1>
24 // iteration 2
25 // alloc <address 3> size <size 3>
26 // free <address 2>
27 // ...
28 // ...
29 // ...
30 // ===== <map type n>
31 // iteration 0
32 // alloc <address 1000> size <size 1000>
33 // iteration 1
34 // alloc <address 1001> size <size 1001>
35 // free <address 1000>
36 // iteration 2
37 // alloc <address 1002> size <size 1002>
38 // free <address 1001>
39 // ...
40 // ...
41 // ...
42 // <key type m> -> <value type m>
43 // ===== <map type 1> =====
44 // ...
45 // ...
46 // ===== <map type n> =====
47 //
48 // Alternate output strategies are possible, but most of them are worse/more
49 // complex, and do not eliminate the postprocessing step.
50 
51 #include <array>
52 #include <atomic>
53 #include <charconv>
54 #include <limits>
55 #include <map>
56 #include <string>
57 #include <unordered_map>
58 #include <utility>
59 
60 #include "base/allocator/dispatcher/dispatcher.h"
61 #include "base/containers/flat_map.h"
62 #include "base/logging.h"
63 #include "base/strings/safe_sprintf.h"
64 #include "base/unguessable_token.h"
65 #include "base/values.h"
66 #include "third_party/abseil-cpp/absl/container/btree_map.h"
67 #include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
68 #include "third_party/abseil-cpp/absl/container/node_hash_map.h"
69 #include "third_party/abseil-cpp/absl/types/optional.h"
70 
71 namespace {
72 
73 std::atomic<bool> log_allocs_and_frees;
74 
75 struct AllocationLogger {
76  public:
OnAllocation__anon68df2b7a0111::AllocationLogger77   void OnAllocation(void* address,
78                     size_t size,
79                     base::allocator::dispatcher::AllocationSubsystem sub_system,
80                     const char* type_name) {
81     if (log_allocs_and_frees.load(std::memory_order_acquire)) {
82       char buffer[128];
83       // Assume success; ignore return value.
84       base::strings::SafeSPrintf(buffer, "alloc address %p size %d\n", address,
85                                  size);
86       RAW_LOG(INFO, buffer);
87     }
88   }
89 
OnFree__anon68df2b7a0111::AllocationLogger90   void OnFree(void* address) {
91     if (log_allocs_and_frees.load(std::memory_order_acquire)) {
92       char buffer[128];
93       // Assume success; ignore return value.
94       base::strings::SafeSPrintf(buffer, "freed address %p\n", address);
95       RAW_LOG(INFO, buffer);
96     }
97   }
98 
Install__anon68df2b7a0111::AllocationLogger99   static void Install() {
100     static AllocationLogger logger;
101     base::allocator::dispatcher::Dispatcher::GetInstance().InitializeForTesting(
102         &logger);
103   }
104 };
105 
106 class ScopedLogAllocAndFree {
107  public:
ScopedLogAllocAndFree()108   ScopedLogAllocAndFree() {
109     log_allocs_and_frees.store(true, std::memory_order_release);
110   }
111 
~ScopedLogAllocAndFree()112   ~ScopedLogAllocAndFree() {
113     log_allocs_and_frees.store(false, std::memory_order_release);
114   }
115 };
116 
117 // Measures the memory usage for a container with type `Container` from 0 to
118 // 6857 elements, using `inserter` to insert a single element at a time.
119 // `inserter` should be a functor that takes a `Container& container` as its
120 // first parameter and a `size_t current_index` as its second parameter.
121 //
122 // Note that `inserter` can't use `base::FunctionRef` since the inserter is
123 // passed through several layers before actually being instantiated below in
124 // this function.
125 template <typename Container, typename Inserter>
MeasureOneContainer(const Inserter & inserter)126 void MeasureOneContainer(const Inserter& inserter) {
127   char buffer[128];
128 
129   RAW_LOG(INFO, "iteration 0");
130   // Record any initial allocations made by an empty container.
131   absl::optional<ScopedLogAllocAndFree> base_size_logger;
132   base_size_logger.emplace();
133   Container c;
134   base_size_logger.reset();
135   // As a hack, also log out sizeof(c) since the initial base size of the
136   // container should be counted too. The exact placeholder used for the address
137   // (in this case "(stack)") isn't important as long as it will not have a
138   // corresponding free line logged for it.
139   base::strings::SafeSPrintf(buffer, "alloc address (stack) size %d",
140                              sizeof(c));
141   RAW_LOG(INFO, buffer);
142 
143   // Swisstables resizes the backing store around 6858 elements.
144   for (size_t i = 1; i <= 6857; ++i) {
145     base::strings::SafeSPrintf(buffer, "iteration %d", i);
146     RAW_LOG(INFO, buffer);
147     inserter(c, i);
148   }
149 }
150 
151 // Measures the memory usage for all the container types under test. `inserter`
152 // is used to insert a single element at a time into the tested container.
153 template <typename K, typename V, typename Inserter>
Measure(const Inserter & inserter)154 void Measure(const Inserter& inserter) {
155   using Hasher = std::conditional_t<std::is_same_v<base::UnguessableToken, K>,
156                                     base::UnguessableTokenHash, std::hash<K>>;
157 
158   RAW_LOG(INFO, "===== base::flat_map =====");
159   MeasureOneContainer<base::flat_map<K, V>>(inserter);
160   RAW_LOG(INFO, "===== std::map =====");
161   MeasureOneContainer<std::map<K, V>>(inserter);
162   RAW_LOG(INFO, "===== std::unordered_map =====");
163   MeasureOneContainer<std::unordered_map<K, V, Hasher>>(inserter);
164   RAW_LOG(INFO, "===== absl::btree_map =====");
165   MeasureOneContainer<absl::btree_map<K, V>>(inserter);
166   RAW_LOG(INFO, "===== absl::flat_hash_map =====");
167   MeasureOneContainer<absl::flat_hash_map<K, V, Hasher>>(inserter);
168   RAW_LOG(INFO, "===== absl::node_hash_map =====");
169   MeasureOneContainer<absl::node_hash_map<K, V, Hasher>>(inserter);
170 }
171 
172 }  // namespace
173 
main()174 int main() {
175   AllocationLogger::Install();
176 
177   RAW_LOG(INFO, "int -> int");
178   Measure<int, int>([](auto& container, size_t i) {
179     ScopedLogAllocAndFree scoped_logging;
180     container.insert({i, 0});
181   });
182   RAW_LOG(INFO, "int -> void*");
183   Measure<int, void*>([](auto& container, size_t i) {
184     ScopedLogAllocAndFree scoped_logging;
185     container.insert({i, nullptr});
186   });
187   RAW_LOG(INFO, "int -> std::string");
188   Measure<int, std::string>([](auto& container, size_t i) {
189     ScopedLogAllocAndFree scoped_logging;
190     container.insert({i, ""});
191   });
192   RAW_LOG(INFO, "size_t -> int");
193   Measure<size_t, int>([](auto& container, size_t i) {
194     ScopedLogAllocAndFree scoped_logging;
195     container.insert({i, 0});
196   });
197   RAW_LOG(INFO, "size_t -> void*");
198   Measure<size_t, void*>([](auto& container, size_t i) {
199     ScopedLogAllocAndFree scoped_logging;
200     container.insert({i, nullptr});
201   });
202   RAW_LOG(INFO, "size_t -> std::string");
203   Measure<size_t, std::string>([](auto& container, size_t i) {
204     ScopedLogAllocAndFree scoped_logging;
205     container.insert({i, ""});
206   });
207   RAW_LOG(INFO, "std::string -> std::string");
208   Measure<std::string, std::string>([](auto& container, size_t i) {
209     std::string key;
210     key.resize(std::numeric_limits<size_t>::digits10 + 1);
211     auto result = std::to_chars(&key.front(), &key.back(), i);
212     key.resize(result.ptr - &key.front());
213     ScopedLogAllocAndFree scoped_logging;
214     container.insert({key, ""});
215   });
216   RAW_LOG(INFO, "base::UnguessableToken -> void*");
217   Measure<base::UnguessableToken, void*>([](auto& container, size_t i) {
218     auto token = base::UnguessableToken::Create();
219     ScopedLogAllocAndFree scoped_logging;
220     container.insert({token, nullptr});
221   });
222   RAW_LOG(INFO, "base::UnguessableToken -> base::Value");
223   Measure<base::UnguessableToken, base::Value>([](auto& container, size_t i) {
224     auto token = base::UnguessableToken::Create();
225     base::Value value;
226     ScopedLogAllocAndFree scoped_logging;
227     container.insert({token, std::move(value)});
228   });
229   RAW_LOG(INFO, "base::UnguessableToken -> std::array<std::string, 4>");
230   Measure<base::UnguessableToken, std::array<std::string, 4>>(
231       [](auto& container, size_t i) {
232         auto token = base::UnguessableToken::Create();
233         ScopedLogAllocAndFree scoped_logging;
234         container.insert({token, {}});
235       });
236   RAW_LOG(INFO, "base::UnguessableToken -> std::array<std::string, 8>");
237   Measure<base::UnguessableToken, std::array<std::string, 8>>(
238       [](auto& container, size_t i) {
239         auto token = base::UnguessableToken::Create();
240         ScopedLogAllocAndFree scoped_logging;
241         container.insert({token, {}});
242       });
243   RAW_LOG(INFO, "base::UnguessableToken -> std::array<std::string, 16>");
244   Measure<base::UnguessableToken, std::array<std::string, 16>>(
245       [](auto& container, size_t i) {
246         auto token = base::UnguessableToken::Create();
247         ScopedLogAllocAndFree scoped_logging;
248         container.insert({token, {}});
249       });
250 
251   return 0;
252 }
253