// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // // GENCACHE.H // // Header for generic cache classes. // // Copyright 1997-1998 Microsoft Corporation, All Rights Reserved // // Documenting my dependencies // These must be included BEFORE this file is included. //#include "caldbg.h" //#include "autoptr.h" #ifndef _EX_GENCACHE_H_ #define _EX_GENCACHE_H_ #pragma warning(disable:4200) // zero-sized array // Exdav-safe allocators -------------------------------------------------- // // The classed declared here use the EXDAV-safe allocators (ExAlloc, ExFree) // for all allocations. // NOTE: These allocators can FAIL. You must check for failure on // ExAlloc and ExRealloc! // #include #include // ======================================================================== // // TEMPLATE CLASS CPoolAllocator // // A generic type-specific pool allocator template. Items in the pool // are allocated in chunks and handed out upon request. // Items are recycled on a free chain. // All items are the same size, so reuse is relatively easy. // // NOTE: I have NOT optimized the heck out of this thing. To really // optimize locality of mem usage, we'd want to always grow & shrink // "at the tail". To that end, I always check the freechain first -- // reuse an item before using a new one from the current buffer. // More optimization would require sorting the freechain & stuff. // template class CPoolAllocator { // CHAINBUFHDR ---------------------------------------- // Header struct for chaining together pool buffers. // struct CHAINBUFHDR { CHAINBUFHDR * phbNext; int cItems; int cItemsUsed; // Remainder of buffer is set of items of type T. T rgtPool[0]; // Just to quiet our noisy compiler. CHAINBUFHDR() {}; ~CHAINBUFHDR() {}; }; // CHAINITEM ------------------------------------------ // Struct "applied over" free items to chain them together. // The actual items MUST be large enough to accomodate this. // struct CHAINITEM { CHAINITEM * piNext; }; // Chain of buffers. CHAINBUFHDR * m_phbCurrent; // Chain of free'd items to reuse. CHAINITEM * m_piFreeChain; // Size of chunk to alloc. int m_ciChunkSize; // Constant (enum) for our default starting chunk size (in items). // enum { CHUNKSIZE_START = 20 }; public: // Constructor takes count of items for initial chunk size. // CPoolAllocator (int ciChunkSize = CHUNKSIZE_START) : m_phbCurrent(NULL), m_piFreeChain(NULL), m_ciChunkSize(ciChunkSize) { // A CHAINITEM struct will be "applied over" free items to chain // them together. // The actual items MUST be large enough to accomodate this. // Assert (sizeof(T) >= sizeof(CHAINITEM)); }; ~CPoolAllocator() { // Walk the list of blocks we allocated and free them. // while (m_phbCurrent) { CHAINBUFHDR * phbTemp = m_phbCurrent->phbNext; ExFree (m_phbCurrent); m_phbCurrent = phbTemp; } } // ------------------------------------------------------------------------ // GetItem // Return an item from the pool to our caller. // We get the item from the free chain, or from the next block of items. // T * GetItem() { T * ptToReturn; if (m_piFreeChain) { // The free chain is non-empty. Return the first item here. // ptToReturn = reinterpret_cast(m_piFreeChain); m_piFreeChain = m_piFreeChain->piNext; } else { // The free chain is empty. We must grab a never-used item from // the current block. // if (!m_phbCurrent || (m_phbCurrent->cItemsUsed == m_phbCurrent->cItems)) { // There are no more items in the current block. // Allocate a whole new block of items. // CHAINBUFHDR * phbNew = static_cast( ExAlloc (sizeof(CHAINBUFHDR) + (m_ciChunkSize * sizeof(T)))); // The allocators CAN FAIL. Handle this case! if (!phbNew) return NULL; phbNew->cItems = m_ciChunkSize; phbNew->cItemsUsed = 0; phbNew->phbNext = m_phbCurrent; m_phbCurrent = phbNew; } // Now we should have a block with an unused item for us to return. // Assert (m_phbCurrent && (m_phbCurrent->cItemsUsed < m_phbCurrent->cItems)); ptToReturn = & m_phbCurrent->rgtPool[ m_phbCurrent->cItemsUsed++ ]; } Assert (ptToReturn); return ptToReturn; } // ------------------------------------------------------------------------ // FreeItem // The caller is done with this item. Add it to our free chain. // void FreeItem (T * pi) { // Add the item to the free chain. // To do this without allocating more memory, we use the item's // storage to hold our next-pointer. // The actual items MUST be large enough to accomodate this. // reinterpret_cast(pi)->piNext = m_piFreeChain; m_piFreeChain = reinterpret_cast(pi); } }; // ======================================================================== // // TEMPLATE CLASS CCache // // A generic hash cache template. Items in the cache uniquely map keys of // type _K to values of type _Ty. Keys and values are copied when // they are added to the cache; there is no "ownership". // // The key (type _K) must provide methods hash and isequal. These methods // will be used to hash and compare the keys. // // // Add() // Adds an item (key/value pair) to the cache. Returns a reference // to the added item's value. // // Set() // Sets an item's value, adding the item if it doesn't already exist. // Returns a reference to the added item's value. // // Lookup() // Looks for an item with the specified key. If the item exists, // returns a pointer to its value, otherwise returns NULL. // // FFetch() // Boolean version of Lookup(). // // Remove() // Removes the item associated with a particular key. // Does nothing if there is no item with that key. // // Clear() // Removes all items from the cache. // // ForEach() // Applies an operation, specified by an operator object passed in // as a parameter, to all of the items in the cache. // // ForEachMatch() // Applies an operation, specified by an operator object passed in // as a parameter, to each item in the cache that matches the provided key. // // Additional functions proposed // Rehash - currently ITP only // Resize the table & re-add all items. // DumpCacheUsage() - NYI // Dump the bookkeeping data about the cache. // template class CCache { // --------------------------------------------------------------------- // Cache Entry structures // struct Entry { struct Entry * peNext; _K key; _Ty data; #ifdef DBG BOOL fValid; // Is this entry valid? #endif // DBG // CONSTRUCTORS Entry (const _K& k, const _Ty& d) : key(k), data(d) { }; // // The following is to get around the fact that the store has // defined a "new" macro which makes using the new operator to // do in place initialization very difficult // void EntryInit (const _K& k, const _Ty& d) { key = k; data = d; }; }; struct TableEntry //HashLine { BOOL fLineValid; // Is this cache line valid? Entry * peChain; #ifdef DBG int cEntries; // Number of entries in this line in the cache. mutable BYTE cAccesses; // Bookkeeping #endif // DBG }; // --------------------------------------------------------------------- // The hash table data // int m_cSize; // Size of the hash table. auto_heap_ptr m_argTable; // The hash table. int m_cItems; // Current number of items in the cache. // --------------------------------------------------------------------- // Pool allocator to alloc nodes // CPoolAllocator m_poolalloc; // --------------------------------------------------------------------- // Constant (enum) for our default initial count of lines in the cache. // NOTE: Callers should really pick their own best size. // This size is prime to try to force fewer collisions. // enum { CACHESIZE_START = 37 }; #ifdef DBG // --------------------------------------------------------------------- // Bookkeeping bits // int m_cCollisions; // Adds that hit the same chain mutable int m_cHits; // Lookup/Set hits mutable int m_cMisses; // Lookup/Set misses #endif // DBG // --------------------------------------------------------------------- // Helper function to build the table // BOOL FBuildTable() { Assert (!m_argTable.get()); // Allocate space for the number of cache lines we need (m_cSize). // m_argTable = reinterpret_cast(ExAlloc ( m_cSize * sizeof(TableEntry))); // The allocators CAN FAIL. Handle this case! if (!m_argTable.get()) return FALSE; ZeroMemory (m_argTable.get(), m_cSize * sizeof(TableEntry)); return TRUE; } // --------------------------------------------------------------------- // CreateEntry // Create a new entry to add to the cache. // Entry * CreateEntry(const _K& k, const _Ty& d) { Entry * peNew = m_poolalloc.GetItem(); // The allocators CAN FAIL. Handle this case! if (!peNew) return NULL; ZeroMemory (peNew, sizeof(Entry)); // peNew = new (peNew) Entry(k,d); peNew->EntryInit (k,d); #ifdef DBG peNew->fValid = TRUE; #endif // DBG return peNew; } void DeleteEntry(Entry * pe) { pe->~Entry(); #ifdef DBG pe->fValid = FALSE; #endif // DBG m_poolalloc.FreeItem (pe); } // NOT IMPLEMENTED // CCache (const CCache&); CCache& operator= (const CCache&); public: // ===================================================================== // // TEMPLATE CLASS IOp // // Operator base class interface used in ForEach() operations // on the cache. // The operator can return FALSE to cancel iteration, or TRUE to // continue walking the cache. // class IOp { // NOT IMPLEMENTED // IOp& operator= (const IOp&); public: virtual BOOL operator() (const _K& key, const _Ty& value) = 0; }; // ===================================================================== // // CREATORS // CCache (int cSize = CACHESIZE_START) : m_cSize(cSize), m_cItems(0) { Assert (m_cSize); // table size of zero is invalid! // (and would cause div-by-zero errors later!) #ifdef DBG m_cCollisions = 0; m_cHits = 0; m_cMisses = 0; #endif // DBG }; ~CCache() { // If we have a table (FInit was successfully called), clear it. // if (m_argTable.get()) Clear(); // Auto-pointer will clean up the table. }; BOOL FInit() { // Set up the cache with the provided initial size. // When running under the store (exdav.dll) THIS CAN FAIL! // return FBuildTable(); } // ===================================================================== // // ACCESSORS // // -------------------------------------------------------------------- // CItems // Returns the number of items in the cache. // int CItems() const { return m_cItems; } // -------------------------------------------------------------------- // Lookup // Find the first item in the cache that matches this key. // key.hash is used to find the correct line of the cache. // key.isequal is called on each item in the collision chain until a // match is found. // _Ty * Lookup (const _K& key) const { // Find the index of the correct cache line for this key. // int iHash = key.hash(m_cSize); Assert (iHash < m_cSize); Assert (m_argTable.get()); #ifdef DBG TableEntry * pte = &m_argTable[iHash]; pte->cAccesses++; #endif // DBG // Do we have any entries in this cache line? // If this cache line is not valid, we have no entries -- NOT found. // if (m_argTable[iHash].fLineValid) { Entry * pe = m_argTable[iHash].peChain; while (pe) { Assert (pe->fValid); if (key.isequal (pe->key)) { #ifdef DBG m_cHits++; #endif // DBG return &pe->data; } pe = pe->peNext; } } #ifdef DBG m_cMisses++; #endif // DBG return NULL; } // -------------------------------------------------------------------- // FFetch // Boolean-returning wrapper for Lookup. // BOOL FFetch (const _K& key, _Ty * pValueRet) const { _Ty * pValueFound = Lookup (key); if (pValueFound) { *pValueRet = *pValueFound; return TRUE; } return FALSE; } // -------------------------------------------------------------------- // ForEach // Seek through the cache, calling the provided operator on each item. // The operator can return FALSE to cancel iteration, or TRUE to continue // walking the cache. // // NOTE: This function is built to allow deletion of the item being // visited (see the comment inside the while loop -- fetch a pointer // to the next item BEFORE calling the visitor op), BUT other // deletes and adds are not "supported" and will have undefined results. // Two specific disaster scenarios: delete of some other item could // actually delete the item we pre-fetched, and we will crash on the // next time around the loop. add of any item during the op callback // could end up adding the item either before or after our current loop // position, and thus might get visited, or might not. // void ForEach (IOp& op) const { // If we don't have any items, quit now! // if (!m_cItems) return; Assert (m_argTable.get()); // Loop through all items in the cache, calling the // provided operator on each item. // for (int iHash = 0; iHash < m_cSize; iHash++) { // Look for valid cache entries. // if (m_argTable[iHash].fLineValid) { Entry * pe = m_argTable[iHash].peChain; while (pe) { // To support deleting inside the op, // fetch the next item BEFORE calling the op. // Entry * peNext = pe->peNext; Assert (pe->fValid); // Found a valid entry. Call the operator. // If the operator returns TRUE, keep looping. // If he returns FALSE, quit the loop. // if (!op (pe->key, pe->data)) return; pe = peNext; } } } } // -------------------------------------------------------------------- // ForEachMatch // Seek through the cache, calling the provided operator on each item // that has a matching key. This is meant to be used with a cache // that may have duplicate items. // The operator can return FALSE to cancel iteration, or TRUE to continue // walking the cache. // void ForEachMatch (const _K& key, IOp& op) const { // If we don't have any items, quit now! // if (!m_cItems) return; // Find the index of the correct cache line for this key. // int iHash = key.hash(m_cSize); Assert (iHash < m_cSize); Assert (m_argTable.get()); #ifdef DBG TableEntry * pte = &m_argTable[iHash]; pte->cAccesses++; #endif // DBG // Only process if this row of the cache is valid. // if (m_argTable[iHash].fLineValid) { // Loop through all items in this row of the cache, calling the // provided operator on each item. // Entry * pe = m_argTable[iHash].peChain; while (pe) { // To support deleting inside the op, // fetch the next item BEFORE calling the op. // Entry * peNext = pe->peNext; Assert (pe->fValid); if (key.isequal (pe->key)) { // Found a matching entry. Call the operator. // If the operator returns TRUE, keep looping. // If he returns FALSE, quit the loop. // if (!op (pe->key, pe->data)) return; } pe = peNext; } } } // ===================================================================== // // MANIPULATORS // // -------------------------------------------------------------------- // FSet // Reset the value of an item in the cache, adding it if the item // does not yet exist. // BOOL FSet (const _K& key, const _Ty& value) { // Find the index of the correct cache line for this key. // int iHash = key.hash (m_cSize); Assert (iHash < m_cSize); Assert (m_argTable.get()); #ifdef DBG TableEntry * pte = &m_argTable[iHash]; pte->cAccesses++; #endif // DBG // Look for valid cache entries. // if (m_argTable[iHash].fLineValid) { Entry * pe = m_argTable[iHash].peChain; while (pe) { Assert (pe->fValid); if (key.isequal (pe->key)) { #ifdef DBG m_cHits++; #endif // DBG pe->data = value; return TRUE; } pe = pe->peNext; } } #ifdef DBG m_cMisses++; #endif // DBG // The items does NOT exist in the cache. Add it now. // return FAdd (key, value); } // -------------------------------------------------------------------- // FAdd // Add an item to the cache. // WARNING: Duplicate keys will be blindly added here! Use FSet() // if you want to change the value for an existing item. Use Lookup() // to check if a matching item already exists. //$LATER: On DBG, scan the list for duplicate keys. // BOOL FAdd (const _K& key, const _Ty& value) { // Create a new element to add to the chain. // NOTE: This calls the copy constructors for the key & value! // Entry * peNew = CreateEntry (key, value); // The allocators CAN FAIL. Handle this case! if (!peNew) return FALSE; // Find the index of the correct cache line for this key. // int iHash = key.hash (m_cSize); Assert (iHash < m_cSize); Assert (m_argTable.get()); #ifdef DBG TableEntry * pte = &m_argTable[iHash]; pte->cEntries++; if (m_argTable[iHash].peChain) m_cCollisions++; else pte->cAccesses = 0; #endif // DBG // Link this new element into the chain. // peNew->peNext = m_argTable[iHash].peChain; m_argTable[iHash].peChain = peNew; m_argTable[iHash].fLineValid = TRUE; m_cItems++; return TRUE; } // -------------------------------------------------------------------- // Remove // Remove an item from the cache. //$REVIEW: Does this need to return a "found" boolean?? // void Remove (const _K& key) { // Find the index of the correct cache line for this key. // int iHash = key.hash (m_cSize); Assert (iHash < m_cSize); Assert (m_argTable.get()); #ifdef DBG TableEntry * pte = &m_argTable[iHash]; pte->cAccesses++; #endif // DBG // If this cache line is not valid, we have no entries -- // nothing to remove. // if (m_argTable[iHash].fLineValid) { Entry * pe = m_argTable[iHash].peChain; Entry * peNext = pe->peNext; Assert (pe->fValid); // Delete first item in chain. // if (key.isequal (pe->key)) { // Snip the item to delete (pe) out of the chain. m_argTable[iHash].peChain = peNext; if (!peNext) { // We deleted the last item. This line is empty. // m_argTable[iHash].fLineValid = FALSE; } // Delete entry to destroy the copied data (value) object. DeleteEntry (pe); m_cItems--; #ifdef DBG pte->cEntries--; #endif // DBG } else { // Lookahead compare & delete. // while (peNext) { Assert (peNext->fValid); if (key.isequal (peNext->key)) { // Snip peNext out of the chain. pe->peNext = peNext->peNext; // Delete entry to destroy the copied data (value) object. DeleteEntry (peNext); m_cItems--; #ifdef DBG pte->cEntries--; #endif // DBG break; } // Advance pe = peNext; peNext = pe->peNext; } } } } // -------------------------------------------------------------------- // Clear // Clear all items from the cache. // NOTE: This does not destroy the table -- the cache is still usable // after this call. // void Clear() { if (m_argTable.get()) { // Walk the cache, checking for valid lines. // for (int iHash = 0; iHash < m_cSize; iHash++) { // If the line if valid, look for items to clear out. // if (m_argTable[iHash].fLineValid) { Entry * pe = m_argTable[iHash].peChain; // The cache line was marked as valid. There should be // at least one item here. Assert (pe); // Walk the chain of items in this cache line. // while (pe) { Entry * peTemp = pe->peNext; Assert (pe->fValid); // Delete entry to destroy the copied data (value) object. DeleteEntry (pe); pe = peTemp; } } // Clear out our cache line. // m_argTable[iHash].peChain = NULL; m_argTable[iHash].fLineValid = FALSE; #ifdef DBG // Clear out the bookkeeping bits in the cache line. // m_argTable[iHash].cEntries = 0; m_argTable[iHash].cAccesses = 0; #endif // DBG } // We have no more items. // m_cItems = 0; } } #ifdef ITP_USE_ONLY // --------------------------------------------------------------------- // Rehash // Re-allocates the cache's hash table and re-hashes all items. // NOTE: If this call fails (due to memory failure), the old hash table // is restored so that we don't lose any the items. // **RA** This call has NOT been tested in production (shipping) code! // **RA** It is provided here for ITP use only!!! // BOOL FRehash (int cNewSize) { Assert (m_argTable.get()); // Swap out the old table and build a new one. // auto_heap_ptr pOldTable ( m_argTable.relinquish() ); int cOldSize = m_cSize; Assert (pOldTable.get()); m_cSize = cNewSize; if (!FBuildTable()) { Assert (pOldTable.get()); Assert (!m_argTable.get()); // Restore the old table. // m_cSize = cOldSize; m_argTable = pOldTable.relinquish(); return FALSE; } // If no items in the cache, we're done! // if (!m_cItems) { return TRUE; } // Loop through all items in the cache (old table), placing them // into the new table. // for ( int iHash = 0; iHash < cOldSize; iHash++ ) { // Look for valid cache entries. // if (pOldTable[iHash].fLineValid) { Entry * pe = pOldTable[iHash].peChain; while (pe) { // Keep track of next item. Entry * peNext = pe->peNext; Assert (pe->fValid); // Found a valid entry. Place it in the new hash table. // int iHashNew = pe->key.hash (m_cSize); pe->peNext = m_argTable[iHashNew].peChain; m_argTable[iHashNew].peChain = pe; m_argTable[iHashNew].fLineValid = TRUE; #ifdef DBG m_argTable[iHashNew].cEntries++; #endif // DBG pe = peNext; } } } // We're done re-filling the cache. // return TRUE; } #endif // ITP_USE_ONLY }; // ======================================================================== // COMMON KEY CLASSES // ======================================================================== // ======================================================================== // class DwordKey // Key class for any dword data that can be compared with ==. // class DwordKey { private: DWORD m_dw; public: DwordKey (DWORD dw) : m_dw(dw) {} DWORD Dw() const { return m_dw; } int DwordKey::hash (const int rhs) const { return (m_dw % rhs); } bool DwordKey::isequal (const DwordKey& rhs) const { return (rhs.m_dw == m_dw); } }; // ======================================================================== // class PvoidKey // Key class for any pointer data that can be compared with ==. // class PvoidKey { private: PVOID m_pv; public: PvoidKey (PVOID pv) : m_pv(pv) {} // operators for use with the hash cache // int PvoidKey::hash (const int rhs) const { // Since we are talking about ptrs, we want // to shift the pointer such that the hash // values don't tend to overlap due to alignment // NOTE: This shouldn't matter if you choose your hash table size // (rhs) well.... but it also doesn't hurt. // return (int)((reinterpret_cast(m_pv) >> ALIGN_NATURAL) % rhs); } bool PvoidKey::isequal (const PvoidKey& rhs) const { // Just check if the values are equal. // return (rhs.m_pv == m_pv); } }; // ======================================================================== // // CLASS Int64Key // // __int64-based key class for use with the CCache (hashcache). // class Int64Key { private: __int64 m_i64; // NOT IMPLEMENTED // bool operator< (const Int64Key& rhs) const; public: Int64Key (__int64 i64) : m_i64(i64) { }; // operators for use with the hash cache // int hash (const int rhs) const { // Don't even bother with the high part of the int64. // The mod operation would lose that part anyway.... // return ( static_cast(m_i64) % rhs ); } BOOL isequal (const Int64Key& rhs) const { // Just check if the ids are equal. // return ( m_i64 == rhs.m_i64 ); } }; // ======================================================================== // CLASS GuidKey // Key class for per-MDB cache of prop-mapping tables. // class GuidKey { private: const GUID * m_pguid; public: GuidKey (const GUID * pguid) : m_pguid(pguid) { } // operators for use with the hash cache // int hash (const int rhs) const { return (m_pguid->Data1 % rhs); } bool isequal (const GuidKey& rhs) const { return (!!IsEqualGUID (*m_pguid, *rhs.m_pguid)); } }; // ======================================================================== // CLASS StoredGuidKey // Key class for per-MDB cache of prop-mapping tables. // class StoredGuidKey { private: GUID m_guid; public: StoredGuidKey (const GUID guid) { CopyMemory(&m_guid, &guid, sizeof(GUID)); } // operators for use with the hash cache // int hash (const int rhs) const { return (m_guid.Data1 % rhs); } bool isequal (const StoredGuidKey& rhs) const { return (!!IsEqualGUID (m_guid, rhs.m_guid)); } }; #endif // !_EX_GENCACHE_H_