diff --git a/bench/MemoryBench.cpp b/bench/MemoryBench.cpp new file mode 100644 index 0000000000..e732687a96 --- /dev/null +++ b/bench/MemoryBench.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBenchmark.h" +#include "SkCanvas.h" +#include "SkPaint.h" +#include "SkRandom.h" +#include "SkChunkAlloc.h" +#include "SkString.h" + +class ChunkAllocBench : public SkBenchmark { + SkString fName; + size_t fMinSize; + + enum { + N = SkBENCHLOOP(1000) + }; +public: + ChunkAllocBench(void* param, size_t minSize) : INHERITED(param) { + fMinSize = minSize; + fName.printf("chunkalloc_%d", minSize); + } + +protected: + virtual const char* onGetName() SK_OVERRIDE { + return fName.c_str(); + } + + virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE { + size_t inc = fMinSize >> 4; + SkASSERT(inc > 0); + size_t total = fMinSize * 64; + + SkChunkAlloc alloc(fMinSize); + + for (int i = 0; i < N; ++i) { + size_t size = 0; + int calls = 0; + while (size < total) { + alloc.allocThrow(inc); + size += inc; + calls += 1; + } + alloc.reset(); + } + } + +private: + typedef SkBenchmark INHERITED; +}; + +static SkBenchmark* F0(void* p) { return new ChunkAllocBench(p, 64); } +static SkBenchmark* F1(void* p) { return new ChunkAllocBench(p, 8*1024); } + +static BenchRegistry gR0(F0); +static BenchRegistry gR1(F1); + diff --git a/gyp/bench.gypi b/gyp/bench.gypi index 221faed9d8..0489ac655d 100644 --- a/gyp/bench.gypi +++ b/gyp/bench.gypi @@ -28,6 +28,7 @@ '../bench/InterpBench.cpp', '../bench/MathBench.cpp', '../bench/MatrixBench.cpp', + '../bench/MemoryBench.cpp', '../bench/MutexBench.cpp', '../bench/PathBench.cpp', '../bench/PathIterBench.cpp', diff --git a/include/core/SkChunkAlloc.h b/include/core/SkChunkAlloc.h index 28d3c7e713..102962b02a 100644 --- a/include/core/SkChunkAlloc.h +++ b/include/core/SkChunkAlloc.h @@ -17,18 +17,12 @@ public: SkChunkAlloc(size_t minSize); ~SkChunkAlloc(); - /** Free up all allocated blocks. This invalidates all returned - pointers. - */ + /** + * Free up all allocated blocks. This invalidates all returned + * pointers. + */ void reset(); - /** Reuse all allocated blocks. This invalidates all returned - pointers (like reset) but doesn't necessarily free up all - of the privately allocated blocks. This is more efficient - if you plan to reuse the allocator multiple times. - */ - void reuse(); - enum AllocFailType { kReturnNil_AllocFailType, kThrow_AllocFailType @@ -48,6 +42,7 @@ public: size_t unalloc(void* ptr); size_t totalCapacity() const { return fTotalCapacity; } + int blockCount() const { return fBlockCount; } /** * Returns true if the specified address is within one of the chunks, and @@ -58,10 +53,12 @@ public: private: struct Block; + Block* fBlock; size_t fMinSize; - Block* fPool; + size_t fChunkSize; size_t fTotalCapacity; + int fBlockCount; Block* newBlock(size_t bytes, AllocFailType ftype); }; diff --git a/src/core/SkChunkAlloc.cpp b/src/core/SkChunkAlloc.cpp index 56b4abe0dd..8df38cb1ed 100644 --- a/src/core/SkChunkAlloc.cpp +++ b/src/core/SkChunkAlloc.cpp @@ -1,4 +1,3 @@ - /* * Copyright 2006 The Android Open Source Project * @@ -6,9 +5,18 @@ * found in the LICENSE file. */ - #include "SkChunkAlloc.h" +// Don't malloc any chunks smaller than this +#define MIN_CHUNKALLOC_BLOCK_SIZE 1024 + +// Return the new min blocksize given the current value +static size_t increase_next_size(size_t size) { + return size + (size >> 1); +} + +/////////////////////////////////////////////////////////////////////////////// + struct SkChunkAlloc::Block { Block* fNext; size_t fFreeSize; @@ -19,8 +27,7 @@ struct SkChunkAlloc::Block { return reinterpret_cast(this + 1); } - void freeChain() { // this can be null - Block* block = this; + static void FreeChain(Block* block) { while (block) { Block* next = block->fNext; sk_free(block); @@ -28,29 +35,24 @@ struct SkChunkAlloc::Block { } }; - Block* tail() { - Block* block = this; - if (block) { - for (;;) { - Block* next = block->fNext; - if (NULL == next) { - break; - } - block = next; - } - } - return block; - } - bool contains(const void* addr) const { const char* ptr = reinterpret_cast(addr); return ptr >= (const char*)(this + 1) && ptr < fFreePtr; } }; -SkChunkAlloc::SkChunkAlloc(size_t minSize) - : fBlock(NULL), fMinSize(SkAlign4(minSize)), fPool(NULL), fTotalCapacity(0) -{ +/////////////////////////////////////////////////////////////////////////////// + +SkChunkAlloc::SkChunkAlloc(size_t minSize) { + if (minSize < MIN_CHUNKALLOC_BLOCK_SIZE) { + minSize = MIN_CHUNKALLOC_BLOCK_SIZE; + } + + fBlock = NULL; + fMinSize = minSize; + fChunkSize = fMinSize; + fTotalCapacity = 0; + fBlockCount = 0; } SkChunkAlloc::~SkChunkAlloc() { @@ -58,35 +60,20 @@ SkChunkAlloc::~SkChunkAlloc() { } void SkChunkAlloc::reset() { - fBlock->freeChain(); - fBlock = NULL; - fPool->freeChain(); - fPool = NULL; - fTotalCapacity = 0; -} - -void SkChunkAlloc::reuse() { - if (fPool && fBlock) { - fPool->tail()->fNext = fBlock; - } - fPool = fBlock; + Block::FreeChain(fBlock); fBlock = NULL; + fChunkSize = fMinSize; // reset to our initial minSize fTotalCapacity = 0; + fBlockCount = 0; } SkChunkAlloc::Block* SkChunkAlloc::newBlock(size_t bytes, AllocFailType ftype) { - Block* block = fPool; - - if (block && bytes <= block->fFreeSize) { - fPool = block->fNext; - return block; + size_t size = bytes; + if (size < fChunkSize) { + size = fChunkSize; } - size_t size = bytes; - if (size < fMinSize) - size = fMinSize; - - block = (Block*)sk_malloc_flags(sizeof(Block) + size, + Block* block = (Block*)sk_malloc_flags(sizeof(Block) + size, ftype == kThrow_AllocFailType ? SK_MALLOC_THROW : 0); if (block) { @@ -95,6 +82,9 @@ SkChunkAlloc::Block* SkChunkAlloc::newBlock(size_t bytes, AllocFailType ftype) { block->fFreePtr = block->startOfData(); fTotalCapacity += size; + fBlockCount += 1; + + fChunkSize = increase_next_size(fChunkSize); } return block; } @@ -114,10 +104,10 @@ void* SkChunkAlloc::alloc(size_t bytes, AllocFailType ftype) { } SkASSERT(block && bytes <= block->fFreeSize); - void* ptr = block->fFreePtr; + char* ptr = block->fFreePtr; block->fFreeSize -= bytes; - block->fFreePtr += bytes; + block->fFreePtr = ptr + bytes; return ptr; } diff --git a/tests/MemsetTest.cpp b/tests/MemsetTest.cpp index 9c1fc920de..7e80b57ddc 100644 --- a/tests/MemsetTest.cpp +++ b/tests/MemsetTest.cpp @@ -1,13 +1,39 @@ - /* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ + #include "Test.h" +#include "SkChunkAlloc.h" #include "SkUtils.h" +static void test_chunkalloc(skiatest::Reporter* reporter) { + size_t min = 256; + SkChunkAlloc alloc(min); + + REPORTER_ASSERT(reporter, 0 == alloc.totalCapacity()); + REPORTER_ASSERT(reporter, 0 == alloc.blockCount()); + REPORTER_ASSERT(reporter, !alloc.contains(NULL)); + REPORTER_ASSERT(reporter, !alloc.contains(reporter)); + + alloc.reset(); + REPORTER_ASSERT(reporter, 0 == alloc.totalCapacity()); + REPORTER_ASSERT(reporter, 0 == alloc.blockCount()); + + size_t size = min >> 1; + void* ptr = alloc.allocThrow(size); + REPORTER_ASSERT(reporter, alloc.totalCapacity() >= size); + REPORTER_ASSERT(reporter, alloc.blockCount() > 0); + REPORTER_ASSERT(reporter, alloc.contains(ptr)); + + alloc.reset(); + REPORTER_ASSERT(reporter, !alloc.contains(ptr)); +} + +/////////////////////////////////////////////////////////////////////////////// + static void set_zero(void* dst, size_t bytes) { char* ptr = (char*)dst; for (size_t i = 0; i < bytes; ++i) { @@ -85,6 +111,8 @@ static void test_32(skiatest::Reporter* reporter) { static void TestMemset(skiatest::Reporter* reporter) { test_16(reporter); test_32(reporter); + + test_chunkalloc(reporter); }; #include "TestClassDef.h"