/** * @file cachearray.h * * A cached array based on the THOR distributed cache. */ #ifndef THOR_CACHEARRAY_H #define THOR_CACHEARRAY_H #include "distribcache.h" /** * Array elements may vary in size from run to run. However, we place the * constraint that each array element must be the same size, derived all * from the same default element. */ template class CacheArrayBlockHandler : public BlockHandler { FORBID_ACCIDENTAL_COPIES(CacheArrayBlockHandler); private: ArrayList default_elem_; public: CacheArrayBlockHandler() {} ~CacheArrayBlockHandler() {} /** * Initializes this and sets up the block device's header. * * Do this before setting up the actual SmallCache or LRU cache, because * this allocates a block, circumventing the cache. */ void Init(const T& default_obj); void Serialize(ArrayList* data) const; void Deserialize(const ArrayList& data); void BlockInitFrozen(BlockDevice::blockid_t blockid, BlockDevice::offset_t begin, BlockDevice::offset_t bytes, char *block); void BlockFreeze(BlockDevice::blockid_t blockid, BlockDevice::offset_t begin, BlockDevice::offset_t bytes, const char *old_location, char *block); void BlockThaw(BlockDevice::blockid_t blockid, BlockDevice::offset_t begin, BlockDevice::offset_t bytes, char *block); /** Gets the number of bytes for an element. */ size_t n_elem_bytes() { return default_elem_.size(); } /** Gets the contents of the default initial element. */ void GetDefaultElement(T *default_element_out); }; /** * A cached array. * * The cached array metaphor is an array of objects (which may contain * points as long as they are serializable via object traversal) which are * stored in blocks and brought into RAM as needed. * * A cached array depends on an underlying cache, DistributedCache, which * manages page. The cached array only locks into RAM a small subset, * controlled by FIFO_SIZE, of pages. * * A cached array is purposely not thread-safe, but it is fine to have many * cached arrays accessing the same distributed cache as long as they are * not read-write or write-write dependencies for the same region of elements. */ template class CacheArray { FORBID_ACCIDENTAL_COPIES(CacheArray); public: /** The type of an element. */ typedef T Element; protected: /** Per-block metadata. */ struct Metadata { /** The in-core memory for the block. */ char *data; /** The number of times this block is locked into this cached array. */ int lock_count; /** Default constructor (called during array resizing). */ Metadata() { data = NULL; lock_count = 0; } }; protected: protected: /** The metadata array, but adjusted (see how it is used in code). */ Metadata *adjusted_metadatas_; /** The log base 2 of the number of elements per block. */ unsigned int n_block_elems_log_; /** The bit mask of the number of elements per block. */ unsigned int n_block_elems_mask_; /** The metadatas array. */ ArrayList metadatas_; /** The circular fixed-size FIFO queue of blocks that are locked in memory. */ BlockDevice::blockid_t *fifo_; /** The index in the circular FIFO. */ int fifo_index_; /** Number of bytes per element. */ unsigned int n_elem_bytes_; /** The first element. */ index_t begin_; /** The next element to allocate. */ index_t next_alloc_; /** The last element. */ index_t end_; /** * Number of blocks that are skipped before the beginning of * the metadata array. */ BlockDevice::blockid_t skip_blocks_; /** The mode in which this is being accessed. */ BlockDevice::mode_t mode_; /** The underlying distributed cache. */ DistributedCache *cache_; #ifdef DEBUG /** Number of locks made to this cache. */ int64 n_fifo_locks_; #endif /** * Number of pages in the thread-local FIFO cache. * * This absolutely cannot be less than the maximum number of concurrent * "locked" blocks! This maximum should be 32 for most use cases, with 64 * giving a nice balance between efficiency and memory usage -- given the * worst-case of 32 used blocks, the mean search time for an empty FIFO * entry is about three, and only 48 blocks are forced into RAM. */ int fifo_size_; public: /** * Finds a power-of-two block size that is at most the specified number * of kilobytes. */ static index_t ConvertBlockSize(const Element& element, int kilobytes); /** Helper to help you create a DistributedCache. */ static void CreateCacheMaster(int channel, index_t n_block_elems, const Element& default_elem, double megs, DistributedCache *cache); /** Helper to help you connect a DistributedCache to master. */ static void CreateCacheWorker(int channel, double megs, DistributedCache *cache); /** * Gets the default element of a cache. */ static void GetDefaultElement(DistributedCache *cache, Element *element) { static_cast*>( cache->block_handler())->GetDefaultElement(element); } /** * Gets size of an element. */ static size_t GetNumElementBytes(DistributedCache *cache) { return static_cast*>( cache->block_handler())->n_elem_bytes(); } /** * Gets the number of elements in a block. */ static size_t GetNumBlockElements(DistributedCache *cache) { return cache->n_block_bytes() / GetNumElementBytes(cache); } public: CacheArray() {} ~CacheArray() { Flush(); mem::Free(fifo_); } void InitCreate(int channel, index_t n_block_elems, const Element& default_elem, double megs, DistributedCache *cache_in) { CreateCacheMaster(channel, n_block_elems, default_elem, megs, cache_in); Init(cache_in, BlockDevice::M_CREATE); } /** Reopens another cache array, the same range */ void Init(CacheArray *other, BlockDevice::mode_t mode_in) { Init(other, mode_in, other->begin_index(), other->end_index()); } /** Reopens another cache array, a sub-range only */ void Init(CacheArray *other, BlockDevice::mode_t mode_in, index_t begin_index_in, index_t end_index_in) { Init(other->cache_, mode_in, begin_index_in, end_index_in); } /** Opens an existing DistributedCache, a sub-range only (static use-case). */ void Init(DistributedCache *cache_in, BlockDevice::mode_t mode_in, index_t begin_index_in, index_t end_index_in); /** * Opens an existing DistributedCache, a sub-range only. * * Behavior is inferred via the mode. */ void Init(DistributedCache *cache_in, BlockDevice::mode_t mode_in) { Init(cache_in, mode_in, 0, 0); Grow(); } /** * Grows to at least the specified size. */ void Grow(index_t end_element); /** * Grows to the size of the underlying cache. * * Warning: Some of the edge elements might still be uninitialized. */ void Grow(); /** * Gets the first element index this cache array is working on. */ index_t begin_index() const { return begin_; } /** * Gets one past the last index this cache array is working on. */ index_t end_index() const { return end_; } /** * Gets the size in bytes of an element. */ unsigned int n_elem_bytes() const { return n_elem_bytes_; } /** * Gets the log base 2 of the number of elements in a block. */ unsigned int n_block_elems_log() const { return n_block_elems_log_; } /** * Gets the number of elements in a block. */ index_t n_block_elems() const { return n_block_elems_mask_ + 1; } /** * Gets the bit-mask that for figuring out the block offset of an element. */ unsigned int n_block_elems_mask() const { return n_block_elems_mask_; } /** * Gets the underlying distributed cache. */ DistributedCache *cache() const { return cache_; } /** * Checks out an element for reading. */ const Element *StartRead(index_t element_id) { return CheckoutElement_(element_id); } /** * Checks out an element for writing. */ Element *StartWrite(index_t element_id) { DEBUG_ASSERT(BlockDevice::can_write(mode_)); return CheckoutElement_(element_id); } void StopRead(index_t element_id) { DEBUG_ONLY(BoundsCheck_(element_id)); ReleaseElement(element_id); } void StopWrite(index_t element_id) { DEBUG_ONLY(BoundsCheck_(element_id)); DEBUG_ASSERT(BlockDevice::can_write(mode_)); ReleaseElement(element_id); } /** * Unlocks the blocks currently held in the FIFO so that the underlying * cache is free to flush them. */ void Flush(); /** * Swaps two elements (and what they point to). * * @param index_a the index of one element to swap * @param index_b the index of the other element to swap */ void Swap(index_t index_a, index_t index_b); /** * Copies an element to another element, overwriting. * * @param index_src the index to copy from * @param index_dest the index to copy to, overwriting */ void Copy(index_t index_src, index_t index_dest); /** * Allocates elements, with a preferred owner. * * If a new block must be allocated it will be assigned to the specified * owner. * * @param owner the owner to assign any new block to * @param count the number of elements to allocate */ index_t AllocD(int owner, index_t count); /** * Allocates a single element, with a preferred owner. * * If a new block must be allocated it will be assigned to the specified * owner. * * @param owner the owner to assign any new block to */ index_t AllocD(int owner); /** Releases a block explicitly, like StopRead or StopWrite. */ void ReleaseBlock(BlockDevice::blockid_t blockid) { --adjusted_metadatas_[blockid].lock_count; } /** Gets the first element from a block ID. */ index_t BlockElement(BlockDevice::blockid_t blockid) { return blockid << n_block_elems_log(); } /** Gets the block ID from an element. */ BlockDevice::blockid_t Blockid(index_t element_id) { return element_id >> n_block_elems_log(); } /** Gets the within-block byte offset of an element. */ BlockDevice::offset_t Offset(index_t element_id) { return (element_id & n_block_elems_mask_) * n_elem_bytes_; } /** Releases an element, same as StopRead or StopWrite. */ void ReleaseElement(index_t element_id) { DEBUG_ONLY(BoundsCheck_(element_id)); ReleaseBlock(Blockid(element_id)); } private: /** Does a bounds check on an element ID. */ void BoundsCheck_(index_t element_id) { DEBUG_BOUNDS(element_id - begin_, end_ - begin_); } /** Handles a miss from the internal FIFO. */ COMPILER_NO_INLINE Element *HandleCacheMiss_(index_t element_id); /** Checks out an element (happy-path). */ Element *CheckoutElement_(index_t element_id) { DEBUG_ONLY(BoundsCheck_(element_id)); Metadata *metadata = (element_id >> n_block_elems_log()) + adjusted_metadatas_; char *data = metadata->data; BlockDevice::offset_t offset = Offset(element_id); DEBUG_ONLY(n_fifo_locks_++); ++metadata->lock_count; if (likely(data != NULL)) { return reinterpret_cast(data + offset); } else { return HandleCacheMiss_(element_id); } } /** * Marks our write ranges if we're in a mode that requires partially dirty * ranges (see documentation for DistributedCache). */ void MarkRanges_(); }; //------------------------------------------------------------------------ template class CacheRead { FORBID_ACCIDENTAL_COPIES(CacheRead); private: const Element *element_; CacheArray *cache_; BlockDevice::blockid_t blockid_; public: CacheRead(CacheArray* cache_in, index_t id) { element_ = cache_in->StartRead(id); cache_ = cache_in; blockid_ = cache_->Blockid(id); } ~CacheRead() { cache_->ReleaseBlock(blockid_); } const Element *get() const { return element_; } operator const Element * () const { return element_; } const Element * operator -> () const { return element_; } const Element & operator * () const { return *element_; } }; //------------------------------------------------------------------------ template class CacheWrite { FORBID_ACCIDENTAL_COPIES(CacheWrite); private: Element *element_; CacheArray *cache_; BlockDevice::blockid_t blockid_; public: CacheWrite(CacheArray* cache_in, index_t id) { element_ = cache_in->StartWrite(id); cache_ = cache_in; blockid_ = cache_->Blockid(id); } ~CacheWrite() { cache_->ReleaseBlock(blockid_); } operator const Element * () const { return element_; } const Element * operator -> () const { return element_; } const Element & operator * () const { return *element_; } operator Element * () { return element_; } Element * operator -> () { return element_; } Element & operator * () { return *element_; } }; //------------------------------------------------------------------------ /** * Condensed-RAM array. * * This stores data in a flattened format, but there is no caching that * goes on because it is assumed this will fit in RAM. * * Only handles object serializable via object traversal (i.e. with OT_DEF). */ template class SubsetArray { FORBID_ACCIDENTAL_COPIES(SubsetArray); public: /** Element type. */ typedef T Element; private: /** Size of an element, in bytes. */ size_t n_elem_bytes_; /** Adjusted beginning of the array. */ char *adjusted_; /** First index. */ index_t begin_; /** One past the last index. */ index_t end_; public: SubsetArray() {} ~SubsetArray() { mem::Free(&(*this)[begin_]); } /** * Initializes this array to a specific range of indices with the specified * default element. */ void Init(const Element& default_elem, index_t begin, index_t end); /** * Gets the size of an element, in bytes. */ index_t n_elem_bytes() const { return n_elem_bytes_; } /** * Accesses an element. */ const Element& operator[] (index_t i) const { return *reinterpret_cast(adjusted_ + i * n_elem_bytes_); } /** * Accesses an element. */ Element& operator[] (index_t i) { return *reinterpret_cast(adjusted_ + i * n_elem_bytes_); } }; #include "cachearray_impl.h" //------------------------------------------------------------------------ template class CacheWriteIter : public ZCacheIterImpl_, Element, Element> { public: /** * Starts writing to the cache at the specified index. * * (Chains to parent constructor). */ CacheWriteIter(CacheArray* cache_in, index_t begin_index) : ZCacheIterImpl_, Element, Element> (cache_in, begin_index) {} }; template class CacheReadIter : public ZCacheIterImpl_, const Element, Element> { public: /** * Starts reading the cache at the specified index. * * (Chains to parent constructor). */ CacheReadIter(CacheArray* cache_in, index_t begin_index) : ZCacheIterImpl_, const Element, Element> (cache_in, begin_index) {} }; #endif