OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #ifndef BASE_TRACE_EVENT_MEMORY_PROFILER_ALLOCATION_REGISTER_H_ | |
6 #define BASE_TRACE_EVENT_MEMORY_PROFILER_ALLOCATION_REGISTER_H_ | |
7 | |
8 #include <stdint.h> | |
9 | |
10 #include "base/logging.h" | |
11 #include "base/trace_event/memory_profiler_allocation_context.h" | |
12 | |
13 namespace base { | |
14 namespace trace_event { | |
15 | |
16 // Returns the size of a page in bytes. | |
17 size_t BASE_EXPORT GetSystemPageSize(); | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
Just use GetPageSize() from base/process_metrics.h
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
18 | |
19 size_t RoundUpToPageSize(size_t size); | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
Just use bits::Align(size, base::GetPageSize()) wh
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
20 | |
21 // Whether to add guard pages around requested virtual address space. | |
22 enum class VirtualMemoryGuard { kNone, kGuardPageAfter }; | |
23 | |
24 // Allocates a region of virtual address space of |min_size| rounded up to the | |
25 // system page size. The memory is zeroed by the system. | |
26 void* AllocateVirtualMemory(size_t min_size, VirtualMemoryGuard guard); | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
I think these (Alloc+free) should be private stati
Ruud van Asseldonk
2015/10/06 09:26:44
This is not possible if |AllocationlessHashTable|
| |
27 | |
28 // Frees a region of virtual address space allocated by a call to | |
29 // |AllocateVirtualMemory|. | |
30 void FreeVirtualMemory(void* address, | |
31 size_t allocated_min_size, | |
32 VirtualMemoryGuard allocated_guard); | |
33 | |
34 // An mmap-backed hash table that stores values indexed by |uintptr_t|. It is | |
35 // intended to be used as the implementation of the allocation register, and it | |
36 // is tailored specifically for this use case. The common case is that a value | |
37 // is inserted and removed after a while, lookup without modifying the table is | |
38 // not an intended use case. The hash table is implemented as an array of | |
39 // linked lists. The size of this array is fixed, but it does not limit the | |
40 // amount of values that can be stored. | |
41 // | |
42 // Replaying a recording of Chrome's allocations and frees against this hash | |
43 // table takes about 15% of the time that it takes to replay them against | |
44 // |std::map|. | |
45 // | |
46 // This is a templated class for clarity; it reads easier when the hash table | |
47 // terminology is not mixed with memory profiler terminology. | |
48 template <typename Value> | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
As you are going to have only one specialization o
| |
49 class AllocationlessHashTable { | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:06
From [1] "Use the specified order of declarations
| |
50 friend class AllocationRegisterTest; | |
51 | |
52 private: | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
Use a using cell_index_type = uint32_t
and the ove
Ruud van Asseldonk
2015/10/06 09:26:44
Great idea, done.
| |
53 // The table size, 2^18, approximately 260 000, has been tuned for Chrome's | |
54 // typical number of outstanding allocations. (This number varies between | |
55 // processes. Most processes have a sustained load of ~30k unfreed | |
56 // allocations, but some processes have peeks around 100k-400k allocations.) | |
57 // Because of the size of the table, it is likely that every |indices_| | |
58 // access and every |cells_| access will incur a cache miss. Microbenchmarks | |
59 // suggest that it is worthwile to use more memory for the table to avoid | |
60 // chasing down the linked list, until the size is 2^18. | |
61 const uint32_t table_size_ = 0x40000; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:06
I think this should be kNumBuckets (regardless of
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
62 const uint32_t table_size_mask_ = 0x3ffff; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:06
kBucketsMask = kNumBuckets - 1?
Also you might add
Ruud van Asseldonk
2015/10/06 09:26:44
Done. Added a comment instead.
| |
63 | |
64 // Reserve address space to store at most this number of entries. High | |
65 // capacity does not imply high memory usage due to the access pattern. The | |
66 // only constraint on the capacity is that on 32-bit systems address space is | |
67 // scarce (i.e. reserving 2GiB of address space for the entries is not an | |
68 // option). A value of ~3M entries is large enough to handle spikes in the | |
69 // number of allocations, and modest enough to require no more than a few | |
70 // dozens of mebibytes of address space. | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
Just s/mebibytes/MiB/?
Ruud van Asseldonk
2015/10/06 09:26:45
Done.
| |
71 const uint32_t capacity_ = table_size_ * 10; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
kNumCells / kNumMaxCells ?
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
72 | |
73 // A cell can store a (key, value) pair. Cells are part of a linked list | |
74 // via the |next| member. This list is either the list for a particular hash, | |
75 // or the free list. All cells are contiguous in memory in one big array. | |
76 // Therefore, on 64-bit systems, space can be saved by storing 32-bit indices | |
77 // instead of pointers as links. Index 0 is used as the list terminator. | |
78 struct Cell { | |
79 uintptr_t key; | |
80 uint32_t next; | |
81 Value value; | |
82 }; | |
83 | |
84 // The array of cells. This array is backed by mmapped memory. Lower indices | |
85 // are accessed first, higher indices are only accessed when required. In | |
86 // this way, even if a huge amount of address space has been mmapped, only | |
87 // the cells that are actually used will be backed by physical memory. | |
88 Cell* const cells_; | |
89 | |
90 // The array of indices into |cells_|. |indices_[Hash(key)]| will contain the | |
91 // index of the head of the linked list for |Hash(key)|. A value of 0 | |
92 // indicates an empty list. This array is backed by mmapped memory. | |
93 uint32_t* const indices_; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:06
This should really be named "buckets"
Ruud van Asseldonk
2015/10/06 09:26:44
I agree. Done.
| |
94 | |
95 // The head of the free list. This is the index of the cell. A value of 0 | |
96 // means that the free list is empty. | |
97 uint32_t free_; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
free_list_
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
98 | |
99 // The index of the first cell that has not been used before. If the free | |
100 // list is empty and a new cell is needed, the cell at this index is used. | |
101 uint32_t fresh_; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
next_unused_cell_
Also say in the comment "from th
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
102 | |
103 uint32_t Hash(uintptr_t key) { | |
104 // The multiplicative hashing scheme from [Knuth 1998]. The value of |a| | |
105 // has been chosen carefully based on measurements with real-word data | |
106 // (addresses recorded from a Chrome trace run). It is the first prime | |
107 // after 2^17. For |shift|, 13, 14 and 15 yield good results. These values | |
108 // are tuned to the table size of 2^18. Microbenchmarks show that this | |
109 // simple scheme outperforms fancy hashes like Murmur3 by 20 to 40 percent. | |
110 const uintptr_t a = 131101; | |
111 const uintptr_t shift = 14; | |
112 const uintptr_t h = (key * a) >> shift; | |
113 return static_cast<uint32_t>(h) & table_size_mask_; | |
114 } | |
115 | |
116 // Returns a pointer to the variable that contains or should contain the | |
117 // index of the cell that stores the entry for |key|. The pointer may point | |
118 // at an element of |indices_| or at the |next| member of an element of | |
119 // |cells_|. If the value pointed at is 0, |key| is not in the table. | |
120 uint32_t* Lookup(uintptr_t key) { | |
121 // The list head is in |indices_| at the hash offset. | |
122 uint32_t* idx_ptr = indices_ + Hash(key); | |
123 | |
124 // Chase down the list until the cell that holds |key| is found, or until | |
125 // the list ends. | |
126 while (*idx_ptr != 0 && cells_[*idx_ptr].key != key) | |
127 idx_ptr = &cells_[*idx_ptr].next; | |
128 | |
129 return idx_ptr; | |
130 } | |
131 | |
132 // Returns the index of a cell that is not being used to store a value. | |
133 uint32_t GetFreeCell() { | |
134 // First try to re-use a cell from the freelist. | |
135 if (free_) { | |
136 uint32_t idx = free_; | |
137 free_ = cells_[idx].next; | |
138 return idx; | |
139 } | |
140 | |
141 // Otherwise pick the next fresh cell. | |
142 uint32_t idx = fresh_; | |
143 fresh_++; | |
144 | |
145 // If the hash table has too little capacity (when too little address space | |
146 // was reserved for |cells_|), |fresh_| can be an index outside of the | |
147 // allocated storage. A guard page is allocated there to crash the program | |
148 // in that case. There are alternative solutions: | |
149 // - Deal with it, increase capacity by reallocating |cells_|. | |
150 // - Refuse to insert and let the caller deal with it. | |
151 // Because free cells are re-used before accessing fresh cells with a | |
152 // higher index, and because reserving address space without touching it is | |
153 // cheap, the simplest solution is to just allocate a humongous chunk of | |
154 // address space. | |
155 | |
156 return idx; | |
157 } | |
158 | |
159 public: | |
160 AllocationlessHashTable() | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
Why this is inline? Move to the .cc
same for the d
Ruud van Asseldonk
2015/10/06 09:26:44
Because the class is templated.
| |
161 // Reserve enough address space to store |capacity_| entries if | |
162 // necessary, with a guard page after it to crash the program when | |
163 // attempting to store more entries. | |
164 : cells_(static_cast<Cell*>( | |
165 AllocateVirtualMemory(capacity_ * sizeof(Cell), | |
166 VirtualMemoryGuard::kGuardPageAfter))), | |
167 | |
168 // Allocate memory for the |indices_| array. There is no need for a | |
169 // guard page because all indexing is masked with the table size. With | |
170 // a table size of 2^18, the array will fill 256 pages of 4 KiB. | |
171 indices_(static_cast<uint32_t*>( | |
172 AllocateVirtualMemory(table_size_ * sizeof(uint32_t), | |
173 VirtualMemoryGuard::kNone))) { | |
174 // The free list is empty. The first fresh cell is cell 1, because index 0 | |
175 // is used as list terminator. | |
176 free_ = 0; | |
177 fresh_ = 1; | |
178 } | |
179 | |
180 ~AllocationlessHashTable() { | |
181 FreeVirtualMemory(indices_, table_size_ * sizeof(uint32_t), | |
182 VirtualMemoryGuard::kNone); | |
183 FreeVirtualMemory(cells_, capacity_ * sizeof(Cell), | |
184 VirtualMemoryGuard::kGuardPageAfter); | |
185 } | |
186 | |
187 // Inserts the key into the table and returns a pointer to the associated | |
188 // value. If the key was present already, a pointer to the current value is | |
189 // returned. |key| must not be 0. (This is because 0 is used to mark free | |
190 // cells, to allow efficient iteration of the hash table.) The value might | |
191 // contain garbage from previous use. | |
192 Value* Insert(uintptr_t key) { | |
193 DCHECK(key != 0); | |
194 | |
195 uint32_t* idx_ptr = Lookup(key); | |
196 | |
197 // If the index is 0, the key is not yet present, so insert it. | |
198 if (*idx_ptr == 0) { | |
199 *idx_ptr = GetFreeCell(); | |
200 | |
201 cells_[*idx_ptr].key = key; | |
202 cells_[*idx_ptr].next = 0; | |
203 } | |
204 | |
205 return &cells_[*idx_ptr].value; | |
206 } | |
207 | |
208 // Removes the key from the table if it is present. It is ok to call this with | |
209 // 0 as key. | |
210 void Remove(uintptr_t key) { | |
211 uint32_t* idx_ptr = Lookup(key); | |
212 | |
213 // If the index is 0, the key was not there in the first place. | |
214 if (*idx_ptr == 0) | |
215 return; | |
216 | |
217 // The cell at the index is now free, remove it from the linked list for | |
218 // |Hash(key)|. | |
219 uint32_t free_idx = *idx_ptr; | |
220 *idx_ptr = cells_[free_idx].next; | |
221 | |
222 // Put the free cell at the front of the free list. | |
223 cells_[free_idx].next = free_; | |
224 free_ = free_idx; | |
225 | |
226 // Reset the key, so that on iteration the free cell is ignored. | |
227 cells_[free_idx].key = 0; | |
228 } | |
229 | |
230 // An iterator that iterates entries in the hash table efficiently, but in no | |
231 // particular order. It can do this by iterating the cells and ignoring the | |
232 // linked lists altogether. Instead of checking whether a cell is in the free | |
233 // list to see if it should be skipped, a key value of 0 is used to indicate | |
234 // that a cell is free. | |
235 class ConstIterator { | |
236 friend class AllocationlessHashTable<Value>; | |
237 | |
238 private: | |
239 const AllocationlessHashTable<Value>& table_; | |
240 uint32_t index_; | |
241 | |
242 ConstIterator(const AllocationlessHashTable<Value>& table, uint32_t index) | |
243 : table_(table), index_(index) {} | |
244 | |
245 public: | |
246 void operator++() { | |
247 // Find the next cell with a nonzero key until all cells that could | |
248 // possibly be used have been iterated. Key 0 indicates a free cell. | |
249 do | |
250 index_++; | |
251 while (index_ < table_.fresh_ && table_.cells_[index_].key == 0); | |
252 }; | |
253 | |
254 bool operator!=(const ConstIterator& other) const { | |
255 return index_ != other.index_; | |
256 } | |
257 | |
258 std::pair<uintptr_t, const Value*> operator*() const { | |
259 return std::make_pair(table_.cells_[index_].key, | |
260 &table_.cells_[index_].value); | |
261 } | |
262 }; | |
263 | |
264 ConstIterator begin() const { | |
265 // Initialize the iterator's index to 0. Cell 0 never stores an entry. | |
266 ConstIterator iterator(*this, 0); | |
267 // Incrementing will advance the iterator to the first used cell. | |
268 ++iterator; | |
269 return iterator; | |
270 } | |
271 | |
272 ConstIterator end() const { | |
273 // Cell |fresh_ - 1| is the last cell that could contain an entry, so | |
274 // index |fresh_| is an iterator past the last element, in line with the | |
275 // STL iterator conventions. | |
276 return ConstIterator(*this, fresh_); | |
277 } | |
278 }; | |
279 | |
280 // The allocation register keeps track of all allocations that have not been | |
281 // freed. It is a thin wrapper around |AllocationlessHashTable|, so it does not | |
282 // call |malloc|. | |
283 class BASE_EXPORT AllocationRegister { | |
284 friend class AllocationRegisterTest; | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:05
move this under private:
Ruud van Asseldonk
2015/10/06 09:26:44
Done.
| |
285 | |
286 private: | |
287 // The value type for the (key, value) pairs stored in the hash table. | |
288 struct Allocation { | |
289 size_t size; | |
290 AllocationContext context; | |
291 }; | |
292 | |
293 AllocationlessHashTable<Allocation> allocations_; | |
294 | |
295 public: | |
Primiano Tucci (use gerrit)
2015/10/05 14:11:06
From [1] "Use the specified order of declarations
Ruud van Asseldonk
2015/10/06 09:26:44
Not possible in this case, but I think it can be d
| |
296 AllocationRegister(); | |
297 ~AllocationRegister(); | |
298 | |
299 // Adds an allocation to the table of non-freed allocations. | |
300 void Insert(void* address, size_t size, AllocationContext context); | |
301 | |
302 // Removes an allocation from the table of non-freed allocations. | |
303 void Remove(void* address); | |
304 | |
305 // The type that an iterator dereferences to. | |
306 struct AllocationRef { | |
307 void* address; | |
308 size_t size; | |
309 const AllocationContext* context; | |
310 }; | |
311 | |
312 class ConstIterator { | |
313 friend class AllocationRegister; | |
314 | |
315 private: | |
316 AllocationlessHashTable<Allocation>::ConstIterator iterator_; | |
317 ConstIterator(AllocationlessHashTable<Allocation>::ConstIterator iterator) | |
318 : iterator_(iterator) {} | |
319 | |
320 public: | |
321 void operator++() { ++iterator_; } | |
322 bool operator!=(const ConstIterator& other) const { | |
323 return iterator_ != other.iterator_; | |
324 }; | |
325 | |
326 AllocationRef operator*() const; | |
327 }; | |
328 | |
329 ConstIterator begin() const { return ConstIterator(allocations_.begin()); } | |
330 ConstIterator end() const { return ConstIterator(allocations_.end()); } | |
331 }; | |
332 | |
333 } // namespace trace_event | |
334 } // namespace base | |
335 | |
336 #endif // BASE_TRACE_EVENT_MEMORY_PROFILER_ALLOCATION_REGISTER_H_ | |
OLD | NEW |