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

Side by Side Diff: net/quic/quic_buffer_pool.h

Issue 1577473002: relnote: QUIC streamable frames can now use a freelist for their packet buffers, Guarded by gfe2_fe… (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@19_CL_111440524
Patch Set: cast to size_t Created 4 years, 11 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
« no previous file with comments | « net/net.gypi ('k') | net/quic/quic_buffer_pool_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 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 // A free list for character buffers, optimized to avoid TLB misses.
6 //
7 // This seeks to solve 2 problems:
8 // 1. Free lists are broadly beneficial to most malloc implementations that QUIC
9 // relies on, but especially TCMalloc. Certain QUIC allocations
10 // disproportionately fill up the TCMalloc caches, which leads to TCMalloc
11 // running in a less efficient manner than usual because its other buckets
12 // end up starved.
13 // 2. TCMalloc will release unused memory back to the system periodically.
14 // However, it does so out of contiguous allocations, which can lead to the
15 // physical memory becoming fragmented between the processes running.
16 //
17 // An ordinary FreeList solves the first problem, essentially by artificially
18 // inflating a particular TCMalloc bucket without impacting TCMalloc directly.
19 // However, it does nothing to solve the second problem.
20 //
21 // This free list instead uses huge blocks of memory (so-called "Regions"). Each
22 // block stores the block's free list and the blocks buffer pool. The huge block
23 // is opportunistically acquired as huge pages (a 2MB page) to avoid physical
24 // memory fragmentation, and to decrease the likelihood of TLB misses.
25 // Additionally, the region pointers are stored in the same storage as the
26 // buffer pool itself, which lends itself to avoiding a pointer dereference
27 // (again, further polluting the TLBs), at the expense of having a compiled-in
28 // maximum number of regions.
29 //
30 // Fundamentally, the QuicBufferPool supercedes a new char[] and delete[] pair.
31 // There is an additional New(size_t, bool), which makes the use of a
32 // QuicBufferPool flag-flippable.
33 //
34 // QuicBufferPool is not thread-safe. Using it without external locking will
35 // cause pool corruption.
36 //
37 // The memory returned by QuicBufferPool is always aligned to at least 16 bytes,
38 // so it can be used with SSE instructions without additional work.
39 //
40 // QuicBufferPool is errno-transparent; it makes system calls, but errno will
41 // always be restored to the original value after them.
42 //
43 // QuicBufferPool does not release memory until the whole object is destroyed.
44 //
45 #ifndef NET_QUIC_QUIC_BUFFER_POOL_H_
46 #define NET_QUIC_QUIC_BUFFER_POOL_H_
47
48 #include <algorithm>
49 #include <array>
50 #include <cstddef>
51 #include <limits>
52 #include <new>
53 #include <type_traits>
54
55 #include "base/logging.h"
56 #include "base/memory/aligned_memory.h"
57 #include "net/quic/quic_bug_tracker.h"
58 #include "net/quic/quic_protocol.h"
59
60 #define PREDICT_FALSE(x) x
61 #define PREDICT_TRUE(x) x
62
63 #define PROT_READ 0x04
64 #define PROT_WRITE 0x02
65 #define MAP_PRIVATE 0x02
66 #define MAP_ANONYMOUS 0x20
67
68 namespace net {
69
70 template <size_t MaxBufferSize,
71 size_t MaxNumRegions,
72 size_t RegionSize = 8 * 1024 * 1024>
73 class QuicBufferPool : public QuicBufferAllocator {
74 public:
75 QuicBufferPool() = default;
76 explicit QuicBufferPool(base::LinkerInitialized linker_initialized);
77 char* New(size_t size) override;
78 char* New(size_t size, bool flag_enable) override;
79 void Delete(char* buf) override;
80 void MarkAllocatorIdle() override;
81
82 private:
83 friend class QuicBufferPoolTest;
84
85 // The minimum alignment for objects in a region.
86 static const size_t kMinAlignment = 16;
87
88 static const size_t kAlignedMaxBufferSize =
89 ((MaxBufferSize + kMinAlignment - 1) / kMinAlignment) * kMinAlignment;
90 static_assert((RegionSize % 4096) == 0, "Regions must be page aligned.");
91 static_assert((kAlignedMaxBufferSize % kMinAlignment) == 0,
92 "Alignment calculation failure: aligned buffer size is not "
93 "aligned to the minimum alignment.");
94 static_assert(kAlignedMaxBufferSize >= MaxBufferSize,
95 "Alignment calculation failure: aligned buffer size smaller "
96 "than the max buffer size.");
97 static_assert(MaxBufferSize + kMinAlignment > kAlignedMaxBufferSize,
98 "Alignment calculation failure: aligned buffer size is larger "
99 "than it needs to be.");
100
101 // The actual size of an allocation within the region.
102 static const size_t kAllocSize = kAlignedMaxBufferSize;
103
104 // The usable region size includes kMinAlignment to ensure there's room to
105 // align up after the free list.
106 static const size_t kUsableRegionSize = RegionSize - kMinAlignment;
107
108 // The number of buffers that can fit in the usable memory space.
109 static const size_t kNumBuffersPerRegion =
110 kUsableRegionSize / (kAllocSize + sizeof(char*));
111
112 // A region of memory that buffers can be allocated from.
113 class Region {
114 public:
115 Region() = default;
116 explicit Region(base::LinkerInitialized linker_initialized);
117 ~Region();
118
119 // Allocates a region of memory and initializes the free list within that
120 // memory. Has no effect if the region has already been initialized. If this
121 // method fails, Initialize can be called again to try again later.
122 bool Initialize();
123
124 // Releases the memory backing a region.
125 void Release();
126
127 // Returns a new buffer of |kAllocSize| bytes.
128 // Returns nullptr if there are no available slots.
129 char* New();
130
131 // Releases a buffer. Must be called with memory within the region.
132 void Delete(char* buf);
133
134 // Returns true if the Region contains |buf|.
135 bool Contains(char* buf) const;
136
137 // Returns true if the Region has no outstanding allocations.
138 bool Empty() const;
139
140 // Swaps one region with another region.
141 void Swap(Region* other);
142
143 private:
144 // The offset of the free list within the region.
145 static const size_t kFreeListOffset = 0;
146
147 // The offset of the first buffer within the region.
148 static const size_t kBufferStartOffset =
149 RegionSize - (kNumBuffersPerRegion * kAllocSize);
150
151 static_assert(
152 (kFreeListOffset % sizeof(char*)) == 0,
153 "kFreeListOffset must be aligned to a free list entry boundary.");
154 static_assert(kAlignedMaxBufferSize < kUsableRegionSize,
155 "MaxBufferSize is too large to fit in a 2MB page.");
156 static_assert(
157 MaxBufferSize > sizeof(char*),
158 "MaxBufferSize must be at least the size of a freelist entry.");
159 static_assert(kNumBuffersPerRegion > 0, "Pool will not hold any buffers.");
160 static_assert(
161 kBufferStartOffset >=
162 kFreeListOffset + (sizeof(char*) * kNumBuffersPerRegion),
163 "Region offset calculation failure; freelist overlaps with buffers.");
164 static_assert(kNumBuffersPerRegion <
165 static_cast<size_t>(std::numeric_limits<int>::max()),
166 "Region buffer calculation failure; more buffers can be "
167 "stored in a region than are indexable by an int.");
168 static_assert(static_cast<int>(kNumBuffersPerRegion * MaxNumRegions) ==
169 kNumBuffersPerRegion * MaxNumRegions,
170 "The number of possible allocations in this pool will "
171 "overflow an int.");
172
173 // Returns a pointer to the linear free list.
174 char** free_list() {
175 return reinterpret_cast<char**>(base_ + kFreeListOffset);
176 }
177
178 // POSIX mmap, except errno is preserved.
179 // clang-format off
180 static void* MmapPreserveErrno(void* addr,
181 size_t length,
182 int prot,
183 int flags,
184 int fd,
185 off_t offset);
186 // clang-format on
187
188 // POSIX munmap, except errno is preserved.
189 static void MunmapPreserveErrno(void* addr, size_t length);
190
191 char* base_ = nullptr;
192 unsigned int next_free_ = 0;
193
194 DISALLOW_COPY_AND_ASSIGN(Region);
195 };
196
197 // Returns heap-allocated memory. Only fails if malloc fails.
198 static char* FallbackNew(size_t size);
199
200 // Releases |buf| to the heap.
201 static void FallbackDelete(char* buf);
202
203 // The current number of buffers in use across all regions.
204 unsigned int current_allocations_ = 0;
205
206 // The maximum region in regions_ that was successfully initialized.
207 unsigned int max_region_ = 0;
208 std::array<Region, MaxNumRegions> regions_;
209
210 DISALLOW_COPY_AND_ASSIGN(QuicBufferPool);
211 };
212
213 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
214 QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::QuicBufferPool(
215 base::LinkerInitialized /* linker_initialized */) {}
216
217 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
218 char* QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::New(
219 size_t size) {
220 if (PREDICT_FALSE(size > kAlignedMaxBufferSize)) {
221 QUIC_BUG << "Request for buffer of size " << size
222 << " greater than pool buffer size " << kAlignedMaxBufferSize;
223 return FallbackNew(size);
224 }
225
226 // Lazy-initialize the first region if we need to.
227 if (PREDICT_FALSE(max_region_ == 0)) {
228 if (PREDICT_FALSE(!regions_[0].Initialize())) {
229 QUIC_BUG << "Failed to initialize first region in buffer pool.";
230 return FallbackNew(size);
231 }
232 max_region_ = 1;
233 }
234
235 // Find the first Region that can allocate some memory. Prioritize the lower
236 // order regions so higher order regions can be released.
237 for (unsigned int current_region = 0; current_region < max_region_;
238 ++current_region) {
239 Region& region = regions_[current_region];
240 char* const buf = region.New();
241 if (buf != nullptr) {
242 ++current_allocations_;
243 return buf;
244 }
245 }
246
247 // If all of the regions are full and we can add more regions, try to add a
248 // new one. Note that we never release regions until QuicBufferPool
249 // destruction.
250 if (PREDICT_TRUE(max_region_ < MaxNumRegions)) {
251 Region& new_region = regions_[max_region_];
252 if (PREDICT_FALSE(!new_region.Initialize())) {
253 QUIC_BUG << "Failed to initialize new region in buffer pool.";
254 return FallbackNew(size);
255 }
256 // If this region was just initialized, it's guaranteed to be able to return
257 // some memory.
258 char* const buf = new_region.New();
259 if (PREDICT_TRUE(buf != nullptr)) {
260 ++current_allocations_;
261 ++max_region_;
262 return buf;
263 } else {
264 QUIC_BUG << "Freshly initialized Region failed to allocate memory";
265 }
266 }
267
268 QUIC_BUG << "Ran out of room in QuicBufferPool.";
269 return FallbackNew(size);
270 }
271
272 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
273 char* QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::New(
274 size_t size,
275 bool flag_enable) {
276 if (flag_enable) {
277 return New(size);
278 } else {
279 return FallbackNew(size);
280 }
281 }
282
283 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
284 void QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Delete(
285 char* buf) {
286 if (buf == nullptr) {
287 return;
288 }
289 for (unsigned int i = 0; i < max_region_; ++i) {
290 Region* region = &regions_[i];
291 if (region->Contains(buf)) {
292 region->Delete(buf);
293 // If the region is empty, move it to the back of the list of regions.
294 if (region->Empty()) {
295 DCHECK_GE(max_region_, 1u);
296 region->Swap(&regions_[max_region_ - 1]);
297 }
298 --current_allocations_;
299 if (max_region_ > 1 && regions_[max_region_ - 1].Empty() &&
300 current_allocations_ < ((max_region_ * kNumBuffersPerRegion) -
301 (kNumBuffersPerRegion >> 1))) {
302 // We have 1.5 Regions worth of memory free, and the last region is
303 // completely empty. Let's release the last Region back to the OS; it
304 // seems like we don't need it right now. Note that this will never
305 // release regions_[0].
306 regions_[--max_region_].Release();
307 }
308 return;
309 }
310 }
311 FallbackDelete(buf);
312 }
313
314 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
315 void QuicBufferPool<MaxBufferSize,
316 MaxNumRegions,
317 RegionSize>::MarkAllocatorIdle() {
318 if (current_allocations_ == 0 && max_region_ == 1) {
319 regions_[0].Release();
320 max_region_ = 0;
321 }
322 }
323
324 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
325 QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::Region(
326 base::LinkerInitialized /* linker_initialized */) {}
327
328 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
329 QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::~Region() {
330 // If the region was allocated but all of the buffers in the region haven't
331 // been freed yet, defer freeing until the last buffer is released.
332 if (base_ != nullptr && next_free_ != 0) {
333 QUIC_BUG << "Freeing " << this << " with " << next_free_
334 << " unfreed allocations.";
335 MunmapPreserveErrno(base_, RegionSize);
336 }
337 }
338
339 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
340 bool QuicBufferPool<MaxBufferSize,
341 MaxNumRegions,
342 RegionSize>::Region::Initialize() {
343 if (base_ != nullptr) {
344 return true;
345 }
346
347 const int prot_flags = PROT_READ | PROT_WRITE;
348 const int map_flags = MAP_PRIVATE | MAP_ANONYMOUS;
349 // char* buf = static_cast<char*>(MAP_FAILED);
350 char* buf = nullptr;
351 #ifdef MAP_HUGETLB
352 // Try to get 2MB pages first.
353 // clang-format off
354 buf = static_cast<char*>(MmapPreserveErrno(
355 static_cast<void*>(nullptr),
356 RegionSize,
357 prot_flags,
358 map_flags | MAP_HUGETLB,
359 -1,
360 0));
361 #endif
362 if (buf == nullptr) {
363 // Otherwise we have to use 4K pages.
364 buf = static_cast<char*>(MmapPreserveErrno(
365 static_cast<void*>(nullptr),
366 RegionSize,
367 prot_flags,
368 map_flags,
369 -1,
370 0));
371 }
372 // clang-format on
373
374 if (PREDICT_FALSE(buf == nullptr)) {
375 return false;
376 }
377
378 base_ = buf;
379
380 // Walk the memory in linear order; the free list comes first, then comes the
381 // memory.
382 for (size_t i = 0; i < kNumBuffersPerRegion; ++i) {
383 free_list()[i] = base_ + kBufferStartOffset + (kAllocSize * i);
384 }
385
386 return true;
387 }
388
389 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
390 void QuicBufferPool<MaxBufferSize,
391 MaxNumRegions,
392 RegionSize>::Region::Release() {
393 DCHECK(base_ != nullptr);
394 DCHECK_EQ(0u, next_free_);
395 if (base_ == nullptr) {
396 return;
397 }
398
399 // We'll continue holding onto virtual address space, but the physical backing
400 // is released. This is faster than munmap, and we have plenty of virtual
401 // address space to burn.
402 MunmapPreserveErrno(base_, RegionSize);
403 base_ = nullptr;
404 next_free_ = 0;
405 }
406
407 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
408 char* QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::New() {
409 if (next_free_ < kNumBuffersPerRegion) {
410 return free_list()[next_free_++];
411 } else {
412 return nullptr;
413 }
414 }
415
416 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
417 void QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::Delete(
418 char* buf) {
419 DCHECK_GT(next_free_, 0u);
420 free_list()[--next_free_] = buf;
421 }
422
423 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
424 bool QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::Contains(
425 char* buf) const {
426 return (base_ <= buf) && (buf + MaxBufferSize <= base_ + RegionSize);
427 }
428
429 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
430 bool QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::Empty()
431 const {
432 return next_free_ == 0;
433 }
434
435 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
436 void QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::Region::Swap(
437 Region* other) {
438 if (this == other) {
439 return;
440 }
441 using std::swap;
442 swap(next_free_, other->next_free_);
443 swap(base_, other->base_);
444 }
445
446 // clang-format off
447 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
448 void* QuicBufferPool<MaxBufferSize, MaxNumRegions,
449 RegionSize>::Region::MmapPreserveErrno(void* addr,
450 size_t length,
451 int prot,
452 int flags,
453 int fd,
454 off_t offset) {
455 // clang-format on
456 const int old_errno = errno;
457 void* ret = base::AlignedAlloc(length, kMinAlignment);
458 errno = old_errno;
459 return ret;
460 }
461
462 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
463 void QuicBufferPool<MaxBufferSize,
464 MaxNumRegions,
465 RegionSize>::Region::MunmapPreserveErrno(void* addr,
466 size_t length) {
467 const int old_errno = errno;
468 base::AlignedFree(addr);
469 errno = old_errno;
470 }
471
472 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
473 char* QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::FallbackNew(
474 size_t size) {
475 return new char[size];
476 }
477
478 template <size_t MaxBufferSize, size_t MaxNumRegions, size_t RegionSize>
479 void QuicBufferPool<MaxBufferSize, MaxNumRegions, RegionSize>::FallbackDelete(
480 char* buf) {
481 delete[] buf;
482 }
483
484 } // namespace net
485
486 #endif // NET_QUIC_QUIC_BUFFER_POOL_H_
OLDNEW
« no previous file with comments | « net/net.gypi ('k') | net/quic/quic_buffer_pool_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698