Copyright 2012 -> 2013
[folly.git] / folly / Arena.h
1 /*
2  * Copyright 2013 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #ifndef FOLLY_ARENA_H_
18 #define FOLLY_ARENA_H_
19
20 #include <cassert>
21 #include <utility>
22 #include <limits>
23 #include <boost/intrusive/slist.hpp>
24
25 #include "folly/Likely.h"
26 #include "folly/Malloc.h"
27
28 namespace folly {
29
30 /**
31  * Simple arena: allocate memory which gets freed when the arena gets
32  * destroyed.
33  *
34  * The arena itself allocates memory using a custom allocator which provides
35  * the following interface (same as required by StlAllocator in StlAllocator.h)
36  *
37  *   void* allocate(size_t size);
38  *      Allocate a block of size bytes, properly aligned to the maximum
39  *      alignment required on your system; throw std::bad_alloc if the
40  *      allocation can't be satisfied.
41  *
42  *   void deallocate(void* ptr);
43  *      Deallocate a previously allocated block.
44  *
45  * You may also specialize ArenaAllocatorTraits for your allocator type to
46  * provide:
47  *
48  *   size_t goodSize(const Allocator& alloc, size_t size) const;
49  *      Return a size (>= the provided size) that is considered "good" for your
50  *      allocator (for example, if your allocator allocates memory in 4MB
51  *      chunks, size should be rounded up to 4MB).  The provided value is
52  *      guaranteed to be rounded up to a multiple of the maximum alignment
53  *      required on your system; the returned value must be also.
54  *
55  * An implementation that uses malloc() / free() is defined below, see
56  * SysAlloc / SysArena.
57  */
58 template <class Alloc> struct ArenaAllocatorTraits;
59 template <class Alloc>
60 class Arena {
61  public:
62   explicit Arena(const Alloc& alloc,
63                  size_t minBlockSize = kDefaultMinBlockSize)
64     : allocAndSize_(alloc, minBlockSize)
65     , ptr_(nullptr)
66     , end_(nullptr)
67     , totalAllocatedSize_(0)
68     , bytesUsed_(0) {
69   }
70
71   ~Arena();
72
73   void* allocate(size_t size) {
74     size = roundUp(size);
75     bytesUsed_ += size;
76
77     if (LIKELY(end_ - ptr_ >= size)) {
78       // Fast path: there's enough room in the current block
79       char* r = ptr_;
80       ptr_ += size;
81       assert(isAligned(r));
82       return r;
83     }
84
85     // Not enough room in the current block
86     void* r = allocateSlow(size);
87     assert(isAligned(r));
88     return r;
89   }
90
91   void deallocate(void* p) {
92     // Deallocate? Never!
93   }
94
95   // Transfer ownership of all memory allocated from "other" to "this".
96   void merge(Arena&& other);
97
98   // Gets the total memory used by the arena
99   size_t totalSize() const {
100     return totalAllocatedSize_ + sizeof(Arena);
101   }
102
103   // Gets the total number of "used" bytes, i.e. bytes that the arena users
104   // allocated via the calls to `allocate`. Doesn't include fragmentation, e.g.
105   // if block size is 4KB and you allocate 2 objects of 3KB in size,
106   // `bytesUsed()` will be 6KB, while `totalSize()` will be 8KB+.
107   size_t bytesUsed() const {
108     return bytesUsed_;
109   }
110
111  private:
112   // not copyable
113   Arena(const Arena&) = delete;
114   Arena& operator=(const Arena&) = delete;
115
116   // movable
117   Arena(Arena&&) = default;
118   Arena& operator=(Arena&&) = default;
119
120   struct Block;
121   typedef boost::intrusive::slist_member_hook<
122     boost::intrusive::tag<Arena>> BlockLink;
123
124   struct Block {
125     BlockLink link;
126
127     // Allocate a block with at least size bytes of storage.
128     // If allowSlack is true, allocate more than size bytes if convenient
129     // (via ArenaAllocatorTraits::goodSize()) as we'll try to pack small
130     // allocations in this block.
131     static std::pair<Block*, size_t> allocate(
132         Alloc& alloc, size_t size, bool allowSlack);
133     void deallocate(Alloc& alloc);
134
135     char* start() {
136       return reinterpret_cast<char*>(this + 1);
137     }
138
139    private:
140     Block() { }
141     ~Block() { }
142   } __attribute__((aligned));
143   // This should be alignas(std::max_align_t) but neither alignas nor
144   // max_align_t are supported by gcc 4.6.2.
145
146  public:
147   static constexpr size_t kDefaultMinBlockSize = 4096 - sizeof(Block);
148
149  private:
150   static constexpr size_t maxAlign = alignof(Block);
151   static constexpr bool isAligned(uintptr_t address) {
152     return (address & (maxAlign - 1)) == 0;
153   }
154   static bool isAligned(void* p) {
155     return isAligned(reinterpret_cast<uintptr_t>(p));
156   }
157
158   // Round up size so it's properly aligned
159   static constexpr size_t roundUp(size_t size) {
160     return (size + maxAlign - 1) & ~(maxAlign - 1);
161   }
162
163   // cache_last<true> makes the list keep a pointer to the last element, so we
164   // have push_back() and constant time splice_after()
165   typedef boost::intrusive::slist<
166     Block,
167     boost::intrusive::member_hook<Block, BlockLink, &Block::link>,
168     boost::intrusive::constant_time_size<false>,
169     boost::intrusive::cache_last<true>> BlockList;
170
171   void* allocateSlow(size_t size);
172
173   // Empty member optimization: package Alloc with a non-empty member
174   // in case Alloc is empty (as it is in the case of SysAlloc).
175   struct AllocAndSize : public Alloc {
176     explicit AllocAndSize(const Alloc& a, size_t s)
177       : Alloc(a), minBlockSize(s) {
178     }
179
180     size_t minBlockSize;
181   };
182
183   size_t minBlockSize() const {
184     return allocAndSize_.minBlockSize;
185   }
186   Alloc& alloc() { return allocAndSize_; }
187   const Alloc& alloc() const { return allocAndSize_; }
188
189   AllocAndSize allocAndSize_;
190   BlockList blocks_;
191   char* ptr_;
192   char* end_;
193   size_t totalAllocatedSize_;
194   size_t bytesUsed_;
195 };
196
197 /**
198  * By default, don't pad the given size.
199  */
200 template <class Alloc>
201 struct ArenaAllocatorTraits {
202   static size_t goodSize(const Alloc& alloc, size_t size) {
203     return size;
204   }
205 };
206
207 /**
208  * Arena-compatible allocator that calls malloc() and free(); see
209  * goodMallocSize() in Malloc.h for goodSize().
210  */
211 class SysAlloc {
212  public:
213   void* allocate(size_t size) {
214     return checkedMalloc(size);
215   }
216
217   void deallocate(void* p) {
218     free(p);
219   }
220 };
221
222 template <>
223 struct ArenaAllocatorTraits<SysAlloc> {
224   static size_t goodSize(const SysAlloc& alloc, size_t size) {
225     return goodMallocSize(size);
226   }
227 };
228
229 /**
230  * Arena that uses the system allocator (malloc / free)
231  */
232 class SysArena : public Arena<SysAlloc> {
233  public:
234   explicit SysArena(size_t minBlockSize = kDefaultMinBlockSize)
235     : Arena<SysAlloc>(SysAlloc(), minBlockSize) {
236   }
237 };
238
239 }  // namespace folly
240
241 #include "folly/Arena-inl.h"
242
243 #endif /* FOLLY_ARENA_H_ */