#ifndef ZFP_CACHE_HPP #define ZFP_CACHE_HPP #include "zfp/internal/array/memory.hpp" #ifdef ZFP_WITH_CACHE_PROFILE // maintain stats on hit and miss rates #include #endif namespace zfp { namespace internal { // direct-mapped or two-way skew-associative write-back cache template class Cache { public: // cache line index (zero is reserved for unused lines) typedef uint Index; // cache tag containing line meta data class Tag { public: Tag() : x(0) {} Tag(Index x, bool d) : x(2 * x + d) {} // cache line index Index index() const { return x >> 1; } // is line dirty? bool dirty() const { return x & 1; } // is line used? bool used() const { return x != 0; } // mark line as dirty void mark() { x |= 1u; } // mark line as unused void clear() { x = 0; } protected: Index x; }; // sequential iterator for looping over cache lines class const_iterator { public: friend class Cache; class Pair { public: Pair(Line* l, Tag t) : line(l), tag(t) {} Line* line; Tag tag; }; const_iterator& operator++() { advance(); return *this; } const_iterator operator++(int) { const_iterator iter = *this; advance(); return iter; } const Pair& operator*() const { return pair; } const Pair* operator->() const { return &pair; } operator const void*() const { return pair.line ? this : 0; } protected: const_iterator(Cache* cache) : c(cache), pair(cache->line, cache->tag[0]) { if (!pair.tag.used()) advance(); } void advance() { if (pair.line) { uint i; for (i = uint(pair.line - c->line) + 1; i <= c->mask && !c->tag[i].used(); i++); pair = (i <= c->mask ? Pair(c->line + i, c->tag[i]) : Pair(0, Tag())); } } Cache* c; Pair pair; }; // allocate cache with at least minsize lines Cache(uint minsize = 0) : mask(0), tag(0), line(0) { resize(minsize); #ifdef ZFP_WITH_CACHE_PROFILE std::cerr << "cache lines=" << size() << std::endl; hit[0][0] = hit[1][0] = miss[0] = back[0] = 0; hit[0][1] = hit[1][1] = miss[1] = back[1] = 0; #endif } // copy constructor--performs a deep copy Cache(const Cache& c) : tag(0), line(0) { deep_copy(c); } // destructor ~Cache() { zfp::internal::deallocate_aligned(tag); zfp::internal::deallocate_aligned(line); #ifdef ZFP_WITH_CACHE_PROFILE std::cerr << "cache R1=" << hit[0][0] << " R2=" << hit[1][0] << " RM=" << miss[0] << " RB=" << back[0] << " W1=" << hit[0][1] << " W2=" << hit[1][1] << " WM=" << miss[1] << " WB=" << back[1] << std::endl; #endif } // assignment operator--performs a deep copy Cache& operator=(const Cache& c) { if (this != &c) deep_copy(c); return *this; } // byte size of cache data structure components indicated by mask size_t size_bytes(uint mask = ZFP_DATA_ALL) const { size_t size = 0; if (mask & ZFP_DATA_CACHE) size += this->size() * (sizeof(*tag) + sizeof(*line)); if (mask & ZFP_DATA_META) size += sizeof(*this); return size; } // cache size in number of lines uint size() const { return mask + 1; } // change cache size to at least minsize lines (all contents will be lost) void resize(uint minsize) { // compute smallest value of mask such that mask + 1 = 2^k >= minsize for (mask = minsize ? minsize - 1 : 1; mask & (mask + 1); mask |= mask + 1); zfp::internal::reallocate_aligned(tag, size() * sizeof(Tag), ZFP_MEMORY_ALIGNMENT); zfp::internal::reallocate_aligned(line, size() * sizeof(Line), ZFP_MEMORY_ALIGNMENT); clear(); } // look up cache line #x and return pointer to it if in the cache; // otherwise return null Line* lookup(Index x, bool write) { uint i = primary(x); if (tag[i].index() == x) { if (write) tag[i].mark(); return line + i; } #ifdef ZFP_WITH_CACHE_TWOWAY uint j = secondary(x); if (tag[j].index() == x) { if (write) tag[i].mark(); return line + j; } #endif return 0; } // look up cache line #x and set ptr to where x is or should be stored; // if the returned tag does not match x, then the caller must implement // write-back (if the line is in use) and then fetch the requested line Tag access(Line*& ptr, Index x, bool write) { uint i = primary(x); if (tag[i].index() == x) { ptr = line + i; if (write) tag[i].mark(); #ifdef ZFP_WITH_CACHE_PROFILE hit[0][write]++; #endif return tag[i]; } #ifdef ZFP_WITH_CACHE_TWOWAY uint j = secondary(x); if (tag[j].index() == x) { ptr = line + j; if (write) tag[j].mark(); #ifdef ZFP_WITH_CACHE_PROFILE hit[1][write]++; #endif return tag[j]; } // cache line not found; prefer primary and not dirty slots i = tag[j].used() && (!tag[i].dirty() || tag[j].dirty()) ? i : j; #endif ptr = line + i; Tag t = tag[i]; tag[i] = Tag(x, write); #ifdef ZFP_WITH_CACHE_PROFILE miss[write]++; if (tag[i].dirty()) back[write]++; #endif return t; } // clear cache without writing back void clear() { for (uint i = 0; i <= mask; i++) tag[i].clear(); } // flush cache line void flush(const Line* l) { uint i = uint(l - line); tag[i].clear(); } // return iterator to first cache line const_iterator first() { return const_iterator(this); } protected: // perform a deep copy void deep_copy(const Cache& c) { mask = c.mask; zfp::internal::clone_aligned(tag, c.tag, size(), ZFP_MEMORY_ALIGNMENT); zfp::internal::clone_aligned(line, c.line, size(), ZFP_MEMORY_ALIGNMENT); #ifdef ZFP_WITH_CACHE_PROFILE hit[0][0] = c.hit[0][0]; hit[0][1] = c.hit[0][1]; hit[1][0] = c.hit[1][0]; hit[1][1] = c.hit[1][1]; miss[0] = c.miss[0]; miss[1] = c.miss[1]; back[0] = c.back[0]; back[1] = c.back[1]; #endif } uint primary(Index x) const { return x & mask; } uint secondary(Index x) const { #ifdef ZFP_WITH_CACHE_FAST_HASH // max entropy hash for 26- to 16-bit mapping (not full avalanche) x -= x << 7; x ^= x >> 16; x -= x << 3; #else // Jenkins hash; see http://burtleburtle.net/bob/hash/integer.html x -= x << 6; x ^= x >> 17; x -= x << 9; x ^= x << 4; x -= x << 3; x ^= x << 10; x ^= x >> 15; #endif return x & mask; } Index mask; // cache line mask Tag* tag; // cache line tags Line* line; // actual decompressed cache lines #ifdef ZFP_WITH_CACHE_PROFILE uint64 hit[2][2]; // number of primary/secondary read/write hits uint64 miss[2]; // number of read/write misses uint64 back[2]; // number of write-backs due to read/writes #endif }; } // internal } // zfp #endif