|
|
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// 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 <ex\exmem.h>
#include <align.h>
// ========================================================================
//
// 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 T> 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<T *>(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<CHAINBUFHDR *>( 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<CHAINITEM *>(pi)->piNext = m_piFreeChain; m_piFreeChain = reinterpret_cast<CHAINITEM *>(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 _K, class _Ty> 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<TableEntry> m_argTable; // The hash table.
int m_cItems; // Current number of items in the cache.
// ---------------------------------------------------------------------
// Pool allocator to alloc nodes
//
CPoolAllocator<Entry> 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<TableEntry *>(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<TableEntry> 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<UINT_PTR>(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<UINT>(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_
|