| Index: src/core/SkSmallAllocator.h
|
| diff --git a/src/core/SkSmallAllocator.h b/src/core/SkSmallAllocator.h
|
| index cc50323c3a3580b61e35ddf6609e088478dc5fe7..13b1505821ab588f99a8a28fe21fc0ddd9a16cb9 100644
|
| --- a/src/core/SkSmallAllocator.h
|
| +++ b/src/core/SkSmallAllocator.h
|
| @@ -8,144 +8,132 @@
|
| #ifndef SkSmallAllocator_DEFINED
|
| #define SkSmallAllocator_DEFINED
|
|
|
| -#include "SkTArray.h"
|
| +#include "SkTDArray.h"
|
| #include "SkTypes.h"
|
|
|
| -#include <functional>
|
| +#include <new>
|
| #include <utility>
|
| -
|
| -
|
| -// max_align_t is needed to calculate the alignment for createWithIniterT when the T used is an
|
| -// abstract type. The complication with max_align_t is that it is defined differently for
|
| -// different builds.
|
| -namespace {
|
| -#if defined(SK_BUILD_FOR_WIN32) || defined(SK_BUILD_FOR_MAC)
|
| - // Use std::max_align_t for compiles that follow the standard.
|
| - #include <cstddef>
|
| - using SystemAlignment = std::max_align_t;
|
| -#else
|
| - // Ubuntu compiles don't have std::max_align_t defined, but MSVC does not define max_align_t.
|
| - #include <stddef.h>
|
| - using SystemAlignment = max_align_t;
|
| -#endif
|
| -}
|
|
|
| /*
|
| * Template class for allocating small objects without additional heap memory
|
| - * allocations.
|
| + * allocations. kMaxObjects is a hard limit on the number of objects that can
|
| + * be allocated using this class. After that, attempts to create more objects
|
| + * with this class will assert and return nullptr.
|
| *
|
| * kTotalBytes is the total number of bytes provided for storage for all
|
| * objects created by this allocator. If an object to be created is larger
|
| * than the storage (minus storage already used), it will be allocated on the
|
| * heap. This class's destructor will handle calling the destructor for each
|
| * object it allocated and freeing its memory.
|
| + *
|
| + * Current the class always aligns each allocation to 16-bytes to be safe, but future
|
| + * may reduce this to only the alignment that is required per alloc.
|
| */
|
| -template<uint32_t kExpectedObjects, size_t kTotalBytes>
|
| +template<uint32_t kMaxObjects, size_t kTotalBytes>
|
| class SkSmallAllocator : SkNoncopyable {
|
| public:
|
| + SkSmallAllocator()
|
| + : fStorageUsed(0)
|
| + , fNumObjects(0)
|
| + {}
|
| +
|
| ~SkSmallAllocator() {
|
| // Destruct in reverse order, in case an earlier object points to a
|
| // later object.
|
| - while (fRecs.count() > 0) {
|
| - this->deleteLast();
|
| + while (fNumObjects > 0) {
|
| + fNumObjects--;
|
| + Rec* rec = &fRecs[fNumObjects];
|
| + rec->fKillProc(rec->fObj);
|
| + // Safe to do if fObj is in fStorage, since fHeapStorage will
|
| + // point to nullptr.
|
| + sk_free(rec->fHeapStorage);
|
| }
|
| }
|
|
|
| /*
|
| * Create a new object of type T. Its lifetime will be handled by this
|
| * SkSmallAllocator.
|
| + * Note: If kMaxObjects have been created by this SkSmallAllocator, nullptr
|
| + * will be returned.
|
| */
|
| template<typename T, typename... Args>
|
| T* createT(Args&&... args) {
|
| - void* buf = this->reserve(sizeof(T), DefaultDestructor<T>);
|
| + void* buf = this->reserveT<T>();
|
| + if (nullptr == buf) {
|
| + return nullptr;
|
| + }
|
| return new (buf) T(std::forward<Args>(args)...);
|
| }
|
|
|
| /*
|
| - * Create a new object of size using initer to initialize the memory. The initer function has
|
| - * the signature T* initer(void* storage). If initer is unable to initialize the memory it
|
| - * should return nullptr where SkSmallAllocator will free the memory.
|
| + * Reserve a specified amount of space (must be enough space for one T).
|
| + * The space will be in fStorage if there is room, or on the heap otherwise.
|
| + * Either way, this class will call ~T() in its destructor and free the heap
|
| + * allocation if necessary.
|
| + * Unlike createT(), this method will not call the constructor of T.
|
| */
|
| - template <typename Initer>
|
| - auto createWithIniter(size_t size, Initer initer) -> decltype(initer(nullptr)) {
|
| - using ReturnType = decltype(initer(nullptr));
|
| - SkASSERT(size >= sizeof(ReturnType));
|
| + template<typename T> void* reserveT(size_t storageRequired = sizeof(T)) {
|
| + SkASSERT(fNumObjects < kMaxObjects);
|
| + SkASSERT(storageRequired >= sizeof(T));
|
| + if (kMaxObjects == fNumObjects) {
|
| + return nullptr;
|
| + }
|
| + const size_t storageRemaining = sizeof(fStorage) - fStorageUsed;
|
| + Rec* rec = &fRecs[fNumObjects];
|
| + if (storageRequired > storageRemaining) {
|
| + // Allocate on the heap. Ideally we want to avoid this situation.
|
|
|
| - void* storage = this->reserve(size, DefaultDestructor<ReturnType>);
|
| - auto candidate = initer(storage);
|
| - if (!candidate) {
|
| - // Initializing didn't workout so free the memory.
|
| - this->freeLast();
|
| + // With the gm composeshader_bitmap2, storage required is 4476
|
| + // and storage remaining is 3392. Increasing the base storage
|
| + // causes google 3 tests to fail.
|
| +
|
| + rec->fStorageSize = 0;
|
| + rec->fHeapStorage = sk_malloc_throw(storageRequired);
|
| + rec->fObj = static_cast<void*>(rec->fHeapStorage);
|
| + } else {
|
| + // There is space in fStorage.
|
| + rec->fStorageSize = storageRequired;
|
| + rec->fHeapStorage = nullptr;
|
| + rec->fObj = static_cast<void*>(fStorage + fStorageUsed);
|
| + fStorageUsed += storageRequired;
|
| }
|
| -
|
| - return candidate;
|
| + rec->fKillProc = DestroyT<T>;
|
| + fNumObjects++;
|
| + return rec->fObj;
|
| }
|
|
|
| /*
|
| - * Free the last object allocated and call its destructor. This can be called multiple times
|
| - * removing objects from the pool in reverse order.
|
| + * Free the memory reserved last without calling the destructor.
|
| + * Can be used in a nested way, i.e. after reserving A and B, calling
|
| + * freeLast once will free B and calling it again will free A.
|
| */
|
| - void deleteLast() {
|
| - SkASSERT(fRecs.count() > 0);
|
| - Rec& rec = fRecs.back();
|
| - rec.fDestructor(rec.fObj);
|
| - this->freeLast();
|
| + void freeLast() {
|
| + SkASSERT(fNumObjects > 0);
|
| + Rec* rec = &fRecs[fNumObjects - 1];
|
| + sk_free(rec->fHeapStorage);
|
| + fStorageUsed -= rec->fStorageSize;
|
| +
|
| + fNumObjects--;
|
| }
|
|
|
| private:
|
| - using Destructor = void(*)(void*);
|
| struct Rec {
|
| - char* fObj;
|
| - Destructor fDestructor;
|
| + size_t fStorageSize; // 0 if allocated on heap
|
| + void* fObj;
|
| + void* fHeapStorage;
|
| + void (*fKillProc)(void*);
|
| };
|
|
|
| // Used to call the destructor for allocated objects.
|
| template<typename T>
|
| - static void DefaultDestructor(void* ptr) {
|
| + static void DestroyT(void* ptr) {
|
| static_cast<T*>(ptr)->~T();
|
| }
|
|
|
| - static constexpr size_t kAlignment = alignof(SystemAlignment);
|
| -
|
| - static constexpr size_t AlignSize(size_t size) {
|
| - return (size + kAlignment - 1) & ~(kAlignment - 1);
|
| - }
|
| -
|
| - // Reserve storageRequired from fStorage if possible otherwise allocate on the heap.
|
| - void* reserve(size_t storageRequired, Destructor destructor) {
|
| - // Make sure that all allocations stay aligned by rounding the storageRequired up to the
|
| - // aligned value.
|
| - char* objectStart = fStorageEnd;
|
| - char* objectEnd = objectStart + AlignSize(storageRequired);
|
| - Rec& rec = fRecs.push_back();
|
| - if (objectEnd > &fStorage[kTotalBytes]) {
|
| - // Allocate on the heap. Ideally we want to avoid this situation.
|
| - rec.fObj = new char [storageRequired];
|
| - } else {
|
| - // There is space in fStorage.
|
| - rec.fObj = objectStart;
|
| - fStorageEnd = objectEnd;
|
| - }
|
| - rec.fDestructor = destructor;
|
| - return rec.fObj;
|
| - }
|
| -
|
| - void freeLast() {
|
| - Rec& rec = fRecs.back();
|
| - if (std::less<char*>()(rec.fObj, fStorage)
|
| - || !std::less<char*>()(rec.fObj, &fStorage[kTotalBytes])) {
|
| - delete [] rec.fObj;
|
| - } else {
|
| - fStorageEnd = rec.fObj;
|
| - }
|
| - fRecs.pop_back();
|
| - }
|
| -
|
| - SkSTArray<kExpectedObjects, Rec, true> fRecs;
|
| - char* fStorageEnd {fStorage};
|
| - // Since char have an alignment of 1, it should be forced onto an alignment the compiler
|
| - // expects which is the alignment of std::max_align_t.
|
| - alignas (kAlignment) char fStorage[kTotalBytes];
|
| + alignas(16) char fStorage[kTotalBytes];
|
| + size_t fStorageUsed; // Number of bytes used so far.
|
| + uint32_t fNumObjects;
|
| + Rec fRecs[kMaxObjects];
|
| };
|
|
|
| #endif // SkSmallAllocator_DEFINED
|
|
|