Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(54)

Side by Side Diff: base/trace_event/memory_profiler_allocation_register.h

Issue 1371053002: [Tracing] Add allocation register for heap profiling (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@backtrace
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698