OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright 2014 Google, Inc | 2 * Copyright 2014 Google, Inc |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #ifndef SkSmallAllocator_DEFINED | 8 #ifndef SkSmallAllocator_DEFINED |
9 #define SkSmallAllocator_DEFINED | 9 #define SkSmallAllocator_DEFINED |
10 | 10 |
11 #include "SkTDArray.h" | 11 #include "SkTArray.h" |
12 #include "SkTypes.h" | 12 #include "SkTypes.h" |
13 | 13 |
14 #include <new> | |
15 #include <utility> | 14 #include <utility> |
16 | 15 |
16 | |
17 // max_align_t is needed to calculate the alignment for createWithIniterT when t he T used is an | |
18 // abstract type. The complication with max_align_t is that it is defined differ ently for | |
19 // different builds. | |
20 namespace { | |
21 #if defined(SK_BUILD_FOR_WIN32) || defined(SK_BUILD_FOR_MAC) | |
22 // Use std::max_align_t for compiles that follow the standard. | |
23 #include <cstddef> | |
24 using SystemAlignment = std::max_align_t; | |
25 #else | |
26 // Ubuntu compiles don't have std::max_align_t defined, but MSVC does not de fine max_align_t. | |
27 #include <stddef.h> | |
28 using SystemAlignment = max_align_t; | |
29 #endif | |
30 } | |
31 | |
17 /* | 32 /* |
18 * Template class for allocating small objects without additional heap memory | 33 * Template class for allocating small objects without additional heap memory |
19 * allocations. kMaxObjects is a hard limit on the number of objects that can | 34 * allocations. |
20 * be allocated using this class. After that, attempts to create more objects | |
21 * with this class will assert and return nullptr. | |
22 * | 35 * |
23 * kTotalBytes is the total number of bytes provided for storage for all | 36 * kTotalBytes is the total number of bytes provided for storage for all |
24 * objects created by this allocator. If an object to be created is larger | 37 * objects created by this allocator. If an object to be created is larger |
25 * than the storage (minus storage already used), it will be allocated on the | 38 * than the storage (minus storage already used), it will be allocated on the |
26 * heap. This class's destructor will handle calling the destructor for each | 39 * heap. This class's destructor will handle calling the destructor for each |
27 * object it allocated and freeing its memory. | 40 * object it allocated and freeing its memory. |
28 * | |
29 * Current the class always aligns each allocation to 16-bytes to be safe, but future | |
30 * may reduce this to only the alignment that is required per alloc. | |
31 */ | 41 */ |
32 template<uint32_t kMaxObjects, size_t kTotalBytes> | 42 template<uint32_t kExpectedObjects, size_t kTotalBytes> |
33 class SkSmallAllocator : SkNoncopyable { | 43 class SkSmallAllocator : SkNoncopyable { |
34 public: | 44 public: |
35 SkSmallAllocator() | |
36 : fStorageUsed(0) | |
37 , fNumObjects(0) | |
38 {} | |
39 | |
40 ~SkSmallAllocator() { | 45 ~SkSmallAllocator() { |
41 // Destruct in reverse order, in case an earlier object points to a | 46 // Destruct in reverse order, in case an earlier object points to a |
42 // later object. | 47 // later object. |
43 while (fNumObjects > 0) { | 48 while (fRecs.count() > 0) { |
44 fNumObjects--; | 49 this->deleteLast(); |
45 Rec* rec = &fRecs[fNumObjects]; | |
46 rec->fKillProc(rec->fObj); | |
47 // Safe to do if fObj is in fStorage, since fHeapStorage will | |
48 // point to nullptr. | |
49 sk_free(rec->fHeapStorage); | |
50 } | 50 } |
51 } | 51 } |
52 | 52 |
53 /* | 53 /* |
54 * Create a new object of type T. Its lifetime will be handled by this | 54 * Create a new object of type T. Its lifetime will be handled by this |
55 * SkSmallAllocator. | 55 * SkSmallAllocator. |
56 * Note: If kMaxObjects have been created by this SkSmallAllocator, nullptr | |
57 * will be returned. | |
58 */ | 56 */ |
59 template<typename T, typename... Args> | 57 template<typename T, typename... Args> |
60 T* createT(Args&&... args) { | 58 T* createT(Args&&... args) { |
61 void* buf = this->reserveT<T>(); | 59 void* buf = this->reserve(sizeof(T), DefaultDestructor<T>); |
62 if (nullptr == buf) { | |
63 return nullptr; | |
64 } | |
65 return new (buf) T(std::forward<Args>(args)...); | 60 return new (buf) T(std::forward<Args>(args)...); |
66 } | 61 } |
67 | 62 |
68 /* | 63 /* |
69 * Reserve a specified amount of space (must be enough space for one T). | 64 * Create a new object of size using initer to initialize the memory. The in iter function has |
70 * The space will be in fStorage if there is room, or on the heap otherwise . | 65 * the signature T* initer(void* storage). If initer is unable to initialize the memory it |
71 * Either way, this class will call ~T() in its destructor and free the hea p | 66 * should return nullptr where SkSmallAllocator will free the memory. |
72 * allocation if necessary. | |
73 * Unlike createT(), this method will not call the constructor of T. | |
74 */ | 67 */ |
75 template<typename T> void* reserveT(size_t storageRequired = sizeof(T)) { | 68 template <typename Initer> |
76 SkASSERT(fNumObjects < kMaxObjects); | 69 auto createWithIniter(size_t size, Initer initer) -> decltype(initer(nullptr )) { |
77 SkASSERT(storageRequired >= sizeof(T)); | 70 using ReturnType = decltype(initer(nullptr)); |
78 if (kMaxObjects == fNumObjects) { | 71 SkASSERT(size >= sizeof(ReturnType)); |
79 return nullptr; | 72 |
73 void* storage = this->reserve(size, DefaultDestructor<ReturnType>); | |
74 auto candidate = initer(storage); | |
75 if (!candidate) { | |
76 // Initializing didn't workout so free the memory. | |
77 this->freeLast(); | |
80 } | 78 } |
81 const size_t storageRemaining = sizeof(fStorage) - fStorageUsed; | |
82 Rec* rec = &fRecs[fNumObjects]; | |
83 if (storageRequired > storageRemaining) { | |
84 // Allocate on the heap. Ideally we want to avoid this situation. | |
85 | 79 |
86 // With the gm composeshader_bitmap2, storage required is 4476 | 80 return candidate; |
87 // and storage remaining is 3392. Increasing the base storage | |
88 // causes google 3 tests to fail. | |
89 | |
90 rec->fStorageSize = 0; | |
91 rec->fHeapStorage = sk_malloc_throw(storageRequired); | |
92 rec->fObj = static_cast<void*>(rec->fHeapStorage); | |
93 } else { | |
94 // There is space in fStorage. | |
95 rec->fStorageSize = storageRequired; | |
96 rec->fHeapStorage = nullptr; | |
97 rec->fObj = static_cast<void*>(fStorage + fStorageUsed); | |
98 fStorageUsed += storageRequired; | |
99 } | |
100 rec->fKillProc = DestroyT<T>; | |
101 fNumObjects++; | |
102 return rec->fObj; | |
103 } | 81 } |
104 | 82 |
105 /* | 83 /* |
106 * Free the memory reserved last without calling the destructor. | 84 * Free the last object allocated and call its destructor. This can be calle d multiple times |
107 * Can be used in a nested way, i.e. after reserving A and B, calling | 85 * removing objects from the pool in reverse order. |
108 * freeLast once will free B and calling it again will free A. | |
109 */ | 86 */ |
110 void freeLast() { | 87 void deleteLast() { |
111 SkASSERT(fNumObjects > 0); | 88 SkASSERT(fRecs.count() > 0); |
112 Rec* rec = &fRecs[fNumObjects - 1]; | 89 Rec& rec = fRecs.back(); |
113 sk_free(rec->fHeapStorage); | 90 rec.fDestructor(rec.fObj); |
114 fStorageUsed -= rec->fStorageSize; | 91 this->freeLast(); |
115 | |
116 fNumObjects--; | |
117 } | 92 } |
118 | 93 |
119 private: | 94 private: |
95 using Destructor = void(*)(void*); | |
120 struct Rec { | 96 struct Rec { |
121 size_t fStorageSize; // 0 if allocated on heap | 97 char* fObj; |
122 void* fObj; | 98 Destructor fDestructor; |
123 void* fHeapStorage; | |
124 void (*fKillProc)(void*); | |
125 }; | 99 }; |
126 | 100 |
127 // Used to call the destructor for allocated objects. | 101 // Used to call the destructor for allocated objects. |
128 template<typename T> | 102 template<typename T> |
129 static void DestroyT(void* ptr) { | 103 static void DefaultDestructor(void* ptr) { |
130 static_cast<T*>(ptr)->~T(); | 104 static_cast<T*>(ptr)->~T(); |
131 } | 105 } |
132 | 106 |
133 alignas(16) char fStorage[kTotalBytes]; | 107 static constexpr size_t kAlignment = alignof(SystemAlignment); |
134 size_t fStorageUsed; // Number of bytes used so far. | 108 |
135 uint32_t fNumObjects; | 109 static constexpr size_t AlignSize(size_t size) { |
136 Rec fRecs[kMaxObjects]; | 110 return (size + kAlignment - 1) & ~(kAlignment - 1); |
111 } | |
112 | |
113 // Reserve storageRequired from fStorage if possible otherwise allocate on t he heap. | |
114 void* reserve(size_t storageRequired, Destructor destructor) { | |
115 // Make sure that all allocations stay aligned by rounding the storageRe quired up to the | |
116 // aligned value. | |
117 char* objectStart = fStorageEnd; | |
118 char* objectEnd = objectStart + AlignSize(storageRequired); | |
119 Rec& rec = fRecs.push_back(); | |
120 if (objectEnd > &fStorage[kTotalBytes]) { | |
121 // Allocate on the heap. Ideally we want to avoid this situation. | |
122 rec.fObj = new char [storageRequired]; | |
123 } else { | |
124 // There is space in fStorage. | |
125 rec.fObj = objectStart; | |
126 fStorageEnd = objectEnd; | |
127 } | |
128 rec.fDestructor = destructor; | |
129 return rec.fObj; | |
130 } | |
131 | |
132 void freeLast() { | |
133 Rec& rec = fRecs.back(); | |
134 if (rec.fObj < fStorage || &fStorage[kTotalBytes] <= rec.fObj) { | |
bungeman-skia
2016/11/11 20:58:23
std::less
herb_g
2016/11/15 20:54:43
Done.
| |
135 delete [] rec.fObj; | |
136 } else { | |
137 fStorageEnd = rec.fObj; | |
138 } | |
139 fRecs.pop_back(); | |
140 } | |
141 | |
142 SkSTArray<kExpectedObjects, Rec, true> fRecs; | |
143 char* fStorageEnd {fStorage}; | |
144 // Since char have an alignment of 1, it should be forced onto an alignment the compiler | |
145 // expects which is the alignment of std::max_align_t. | |
146 alignas (kAlignment) char fStorage[kTotalBytes]; | |
137 }; | 147 }; |
138 | 148 |
139 #endif // SkSmallAllocator_DEFINED | 149 #endif // SkSmallAllocator_DEFINED |
OLD | NEW |