blob: 6aa47b4c5e60d99ab73f110f0ba4876b682817ea [file] [log] [blame]
// Copyright 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "android/base/Pool.h"
#include "android/base/AlignedBuf.h"
#include <vector>
#define DEBUG_POOL 0
#if DEBUG_POOL
#define D(fmt,...) fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);
#else
#define D(fmt,...)
#endif
namespace android {
namespace base {
// A Pool consists of:
// - Heaps one for each allocation size
// A Heap consists of:
// - Block(s) for servicing allocation requests with actual memory backing
// A Block consists of:
// - A buffer that is the memory backing
// - Chunks that correspond to usable units of the allocation size
//
// Block implementation:
//
// We want to make it fast to alloc new chunks and to free existing chunks from
// this block, while not having to invalidate all existing pointers when
// allocating new objects.
// I'm p sure by now there's no way to do that withtout
// - significant pre allocation
// - linked list of blocks
//
// So that means some block chain (hehehehe, pun v much intended)
// implementation Wherein, each block has fast allocation of chunks
// correponding tot he desired allocation size.
//
// Any low overhead scheme of that kind, like slab alloc or buddy alloc, is
// fine. This one is based on:
//
// Ben Kenwright (b.kenwright@ncl.ac.uk): "Fast Efficient Fixed-Size Memory
// Pool: No Loops and No Overhead" In COMPUTATION TOOLS 2012: The Third
// International Conference on Computational Logics, Algebras, Programming,
// Tools, and Benchmarking
// Interval:
// Make it easy to track all ranges involved so we know which free()
// in which heap to use.
// Assuming this doesn't grow too much past around 100 intervals
// so we dont need to use fancy algorithms to key on it.
struct Interval {
uintptr_t start;
uintptr_t end;
};
static size_t ilog2Floor(size_t n) {
size_t res = 0;
size_t test = 1;
while (test < n) {
test <<= 1;
++res;
}
return res;
}
struct Block {
Block(size_t _chunkSize, size_t _numChunks) {
if (_chunkSize < sizeof(void*)) {
fprintf(
stderr,
"FATAL: Cannot allocate block with chunk size "
"less then %zu (wanted: %zu)!\n",
sizeof(void*),
_chunkSize);
abort();
}
chunkSize = _chunkSize;
chunkSizeLog2 = ilog2Floor(chunkSize);
numChunks = _numChunks;
D("chunk size %zu log2 %zu numChunks %zu",
chunkSize,
chunkSizeLog2,
numChunks);
sizeBytes = chunkSize * numChunks;
storage.resize(sizeBytes);
data = storage.data();
numFree = numChunks;
numAlloced = 0;
nextFree = (size_t*)data;
}
Interval getInterval() const {
uintptr_t start = (uintptr_t)data;
uintptr_t end = (uintptr_t)(data + sizeBytes);
return { start, end };
}
bool isFull() const { return numFree == 0; }
uint8_t* getPtr(size_t element) {
uint8_t* res =
data + (uintptr_t)chunkSize *
(uintptr_t)element;
D("got %p element %zu chunkSize %zu",
res, element, chunkSize);
return res;
}
size_t getElement(void* ptr) {
uintptr_t ptrVal = (uintptr_t)ptr;
ptrVal -= (uintptr_t)data;
return (size_t)(ptrVal >> chunkSizeLog2);
}
void* alloc() {
// Lazily constructs the index to the
// next unallocated chunk.
if (numAlloced < numChunks) {
size_t* nextUnallocPtr =
(size_t*)getPtr(numAlloced);
++numAlloced;
*nextUnallocPtr = numAlloced;
}
// Returns the next free object,
// if there is space remaining.
void* res = nullptr;
if (numFree) {
D("alloc new ptr @ %p\n", nextFree);
res = (void*)nextFree;
--numFree;
if (numFree) {
// Update nextFree to _point_ at the index
// of the next free chunk.
D("store %zu in %p as next free chunk",
*nextFree,
getPtr(*nextFree));
nextFree = (size_t*)getPtr(*nextFree);
} else {
// Signal that there are no more
// chunks available.
nextFree = nullptr;
}
}
return res;
}
void free(void* toFree) {
size_t* toFreeIndexPtr = (size_t*)toFree;
if (nextFree) {
D("freeing %p: still have other chunks available.", toFree);
D("nextFree: %p (end: %p)", nextFree, getPtr(numChunks));
D("nextFreeElt: %zu\n", getElement(nextFree));
// If there is a chunk available,
// point the just-freed chunk to that.
*toFreeIndexPtr = getElement(nextFree);
} else {
D("freeing free %p: no other chunks available.", toFree);
// If there are no chunks available,
// point the just-freed chunk to past the end.
*(size_t*)toFree = numChunks;
}
nextFree = (size_t*)toFree;
D("set next free to %p", nextFree);
++numFree;
}
// To free everything, just reset back to the initial state :p
void freeAll() {
numFree = numChunks;
numAlloced = 0;
nextFree = (size_t*)data;
}
Block* next = nullptr; // Unused for now
size_t chunkSize = 0;
size_t chunkSizeLog2 = 0;
size_t numChunks = 0;
size_t sizeBytes = 0;
AlignedBuf<uint8_t, 64> storage { 0 };
uint8_t* data = nullptr;
size_t numFree = 0;
size_t numAlloced = 0;
size_t* nextFree = 0;
};
// Straight passthrough to Block for now unless
// we find it necessary to track more than |kMaxAllocs|
// allocs per heap.
class Heap {
public:
Heap(size_t allocSize, size_t chunksPerSize) :
mBlock(allocSize, chunksPerSize) {
}
Interval getInterval() const {
return mBlock.getInterval();
}
bool isFull() const {
return mBlock.isFull();
}
void* alloc() {
return mBlock.alloc();
}
void free(void* ptr) {
mBlock.free(ptr);
}
void freeAll() {
mBlock.freeAll();
}
private:
// Just one block for now
Block mBlock;
};
static size_t leastPowerof2(size_t n) {
size_t res = 1;
while (res < n) {
res <<= 1;
}
return res;
}
class Pool::Impl {
public:
Impl(size_t minSize,
size_t maxSize,
size_t chunksPerSize) :
// Need to bump up min alloc size
// because Blocks use free space for bookkeeping
// purposes.
mMinAllocSize(std::max(sizeof(void*), minSize)),
// Compute mMinAllocLog2.
// mMinAllocLog2 stores
// the number of bits to shift
// in order to obtain the heap index
// corresponding to a desired allocation size.
mMinAllocLog2(ilog2Floor(mMinAllocSize)),
mMaxFastSize(maxSize),
mChunksPerSize(chunksPerSize) {
size_t numHeaps =
1 + ilog2Floor(mMaxFastSize >> mMinAllocLog2);
for (size_t i = 0; i < numHeaps; i++) {
size_t allocSize = mMinAllocSize << i;
D("create heap for size %zu", allocSize);
Heap* newHeap = new Heap(allocSize, mChunksPerSize);
HeapInfo info = {
newHeap,
allocSize,
newHeap->getInterval(),
};
mHeapInfos.push_back(info);
}
}
~Impl() {
for (auto& info : mHeapInfos) {
delete info.heap;
}
}
void* alloc(size_t wantedSize) {
if (wantedSize > mMaxFastSize) {
D("requested size %zu too large", wantedSize);
return nullptr;
}
size_t minAllocSizeNeeded =
std::max(mMinAllocSize, leastPowerof2(wantedSize));
size_t index =
ilog2Floor(minAllocSizeNeeded >> mMinAllocLog2);
D("wanted: %zu min serviceable: %zu heap index: %zu",
wantedSize, minAllocSizeNeeded, index);
auto heap = mHeapInfos[index].heap;
if (heap->isFull()) {
D("heap %zu is full", index);
return nullptr;
}
return heap->alloc();
}
bool free(void* ptr) {
D("for %p:", ptr);
uintptr_t ptrVal = (uintptr_t)ptr;
// Scan through linearly to find any matching
// interval. Interval information has been
// brought up to be stored directly in HeapInfo
// so this should be quite easy on the cache
// at least until a match is found.
for (auto& info : mHeapInfos) {
uintptr_t start = info.interval.start;
uintptr_t end = info.interval.end;
if (ptrVal >= start && ptrVal < end) {
D("found heap to free %p.", ptr)
info.heap->free(ptr);
return true;
}
}
D("%p not found in any heap.", ptr);
return false;
}
void freeAll() {
for (auto& info : mHeapInfos) {
info.heap->freeAll();
}
}
private:
size_t mMinAllocSize;
size_t mMinAllocLog2;
size_t mMaxFastSize;
size_t mChunksPerSize;
// No need to get really complex if there are
// not that many heaps.
struct HeapInfo {
Heap* heap;
size_t allocSize;
Interval interval;
};
std::vector<HeapInfo> mHeapInfos;
};
Pool::Pool(size_t minSize,
size_t maxSize,
size_t mChunksPerSize) :
mImpl(new Pool::Impl(minSize,
maxSize,
mChunksPerSize)) {
}
Pool::~Pool() {
delete mImpl;
for (auto ptr : mFallbackPtrs) {
D("delete fallback ptr %p\n", ptr);
::free(ptr);
}
}
// Fall back to normal alloc if it cannot be
// serviced by the implementation.
void* Pool::alloc(size_t wantedSize) {
void* ptr = mImpl->alloc(wantedSize);
if (ptr) return ptr;
D("Fallback to malloc");
ptr = ::malloc(wantedSize);
if (!ptr) {
D("Failed to service allocation for %zu bytes", wantedSize);
abort();
}
mFallbackPtrs.insert(ptr);
D("Fallback to malloc: got ptr %p", ptr);
return ptr;
}
// Fall back to normal free if it cannot be
// serviced by the implementation.
void Pool::free(void* ptr) {
if (mImpl->free(ptr)) return;
D("fallback to free for %p", ptr);
mFallbackPtrs.erase(ptr);
::free(ptr);
}
void Pool::freeAll() {
mImpl->freeAll();
for (auto ptr : mFallbackPtrs) {
::free(ptr);
}
mFallbackPtrs.clear();
}
} // namespace base
} // namespace android