From 76d94ec94cf86f45b972ada0c85170f569f0dad2 Mon Sep 17 00:00:00 2001 From: Niels Martin Hansen Date: Sat, 15 Aug 2009 21:48:58 +0000 Subject: [PATCH] Add DataBlockCache template class. It parses but I don't know if it compiles yet. Updates #951, #965, #990. Originally committed to SVN as r3412. --- aegisub/src/block_cache.h | 297 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 aegisub/src/block_cache.h diff --git a/aegisub/src/block_cache.h b/aegisub/src/block_cache.h new file mode 100644 index 000000000..353f70d93 --- /dev/null +++ b/aegisub/src/block_cache.h @@ -0,0 +1,297 @@ +// Copyright (c) 2009, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file block_cache.h +/// @ingroup utility +/// @brief Template class for creating caches for blocks of data + + +// Include guard +#ifndef BLOCK_CACHE_H +#define BLOCK_CACHE_H + + +#include + + + +/// @class BasicDataBlockFactory +/// @brief Simple factory for allocating blocks for DataBlockCache +/// @tparam BlockT Type of blocks to produce +/// +/// This is the default block factory class used by DataBlockCache if another isn't specified. +/// It allocates blocks on the heap using operator new and the default constructor, and does +/// nothing special to initialise the blocks. +/// +/// Custom block factories could use a large internally pre-allocated buffer to create the +/// requested blocks in to avoid the default allocator. +template +struct BasicDataBlockFactory { + /// @brief Allocates a block and returns it + /// @param i Index of the block to allocate + /// @return A pointer to the allocated block + /// + /// This default implementation does not use the i parameter. Custom implementations + /// of block factories should use i to determine what data to fill into the block. + BlockT *ProduceBlock(size_t i) + { + (void)i; + return new BlockT; + } + + /// @brief De-allocate a block + /// @param block Pointer to the block to de-allocate + /// + /// It is guaranteed that block was returned by ProduceBlock. + void DisposeBlock(BlockT *block) + { + delete block; + } + + /// @brief Retrieve the amount of memory consumed by a single block + /// @return Number of bytes consumed by a block + /// + /// All blocks must consume the same amount of memory. The size of a block + /// is used to manage and limit the size of the cache. + size_t GetBlockSize() const + { + return sizeof(BlockT); + } +}; + + + +/// @class DataBlockCache +/// @brief Cache for blocks of data in a stream or similar +/// @tparam BlockT Type of blocks to store +/// @tparam MacroblockExponent Controls the number of blocks per macroblock, for tuning memory usage +/// @tparam BlockFactoryT Type of block factory, see BasicDataBlockFactory class for detail on these +template < + class BlockT, + int MacroblockExponent = 6, + class BlockFactoryT = BasicDataBlockFactory +> +class DataBlockCache { + + /// Type of an array of blocks + typedef std::vector BlockArray; + + struct MacroBlock { + int access_count; + BlockArray blocks; + }; + + /// Type of an array of macro blocks + typedef std::vector MacroBlockArray; + + /// The data in the cache + MacroBlockArray data; + + /// Number of blocks per macroblock + size_t macroblock_size; + + /// Bitmask to extract the inside-macroblock index for a block by bitwise and + size_t macroblock_index_mask; + + /// Factory object for blocks + BlockFactoryT factory; + + /// @brief Dispose of all blocks in a macroblock and mark it empty + /// @param mb_index Index of macroblock to clear + void KillMacroBlock(size_t mb_index) + { + MacroBlock &mb = data[mb_index]; + mb.access_count = 0; + + for (size_t bi = 0; bi < blocks.size(); ++bi) + { + BlockT *b = mb.blocks[bi]; + if (!b) + factory.DisposeBlock(b); + } + + mb.blocks.clear(); + } + +public: + /// @brief Constructor + /// @param block_count Total number of blocks the cache will manage + /// @param factory Factory object to use for producing blocks + /// + /// Note that the block_count is the maximum block index the cache will ever see, + /// it is an error to request a block number greater than block_count. + /// + /// The factory object passed must respond well to copying. + DataBlockCache(size_t block_count, BlockFactoryT factory = BlockFactoryT()) + : factory(factory) + { + macroblock_size = 1 << MacroblockExponent; + + macroblock_index_mask = ~(((~0) >> MacroblockExponent) << MacroblockExponent); + + data.resize( (block_count + macroblock_size - 1) >> MacroblockExponent ); + } + + + /// @brief Destructor + /// + /// Disposes of all cached blocks + ~DataBlockCache() + { + // Clear all blocks by aging to zero bytes + Age(0); + } + + + /// @brief Clean up the cache + /// @param max_size Target maximum size of the cache in bytes + /// + /// Passing a max_size of 0 (zero) causes the cache to be completely flushed + /// in a fast manner. + /// + /// The max_size is not a hard limit, the cache size might somewhat exceed the max + /// after the aging operation, though it shouldn't be by much. + void Age(size_t max_size) + { + // Quick way out: get rid of everything + if (max_size == 0) + { + for (size_t mbi = 0; mbi < data.size(); ++mbi) + { + KillMacroBlock(mbi); + } + return; + } + + // Get a list of macro blocks sorted by access count + struct AccessData { + MacroBlockArray::iterator mb; + AccessData(MacroBlockArray::iterator i) : mb(i) { } + // Sort in decreasing order: most accesses first + bool operator < (const AccessData &other) { return mb->access_count > other.mb->access_count; } + }; + std::vector access_data; + access_data.reserve(data.size()); + for (MacroBlockArray::iterator mb = data.begin(); mb != data.end(); ++mb) + access_data.push_back(AccessData(mb)); + access_data.sort(); + + // Sum up data size until we hit the max + size_t cur_size = 0; + size_t block_size = factory.GetBlockSize(); + size_t mbi = 0; + for (; mbi < data.size(); ++mbi) + { + BlockArray &ba = data[mbi].blocks; + for (size_t i = 0; i < ba.size(); ++i) + { + if (ba[i] != 0) + cur_size += block_size; + } + // Cut access count in half for live blocks, so parts that don't get accessed + // a lot will eventually be killed off. + data[mbi].access_count /= 2; + + if (cur_size >= max_size) + { + ++mbi; + break; + } + } + // Hit max, clear all remaining blocks + for (; mbi < data.size(); ++mbi) + { + KillMacroBlock(mbi); + } + } + + + /// @brief Obtain a data block from the cache + /// @param i Index of the block to retrieve + /// @param[out] created On return, tells whether the returned block was created during the operation + /// @return A pointer to the block in cache + /// + /// It is legal to pass 0 (null) for created, in this case nothing is returned in it. + BlockT *Get(size_t i, bool *created = 0) + { + size_t mbi = i >> MacroblockExponent; + assert(mbi < data.size()); + + MacroBlock &mb = data[mbi]; + mb.access_count += 1; + + if (mb.blocks.size() == 0) + { + mb.blocks.resize(macroblock_size); + } + + size_t block_index = i & macroblock_index_mask; + assert(block_index < mb.blocks.size()); + + BlockT *b = mb.blocks[block_index]; + + if (!b) + { + b = factory.ProduceBlock(i); + assert(b != 0); + mb.blocks[block_index] = b; + + if (created) *created = true; + } + else + if (created) *created = false; + + return b; + } + + + /// @brief Speculatively add blocks not present to the cache + /// @param forward Assume forwards linear access is plausible + /// @param backward Assume backwards linear access is plausible + /// @return Number of blocks added to the cache + /// + /// Assuming forwards and/or backwards linear access causes the macroblock access data to be + /// used for speculating in macroblocks that may be accessed soon, and also allocate block + /// in macroblocks that may otherwise not have been accessed recently. + /// + /// @todo Implement this. + size_t Speculate(bool forward, bool backward) + { + (void)forward; + (void)backward; + return 0; + } +}; + + +// Include guard +#endif +