| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- /*
- Copyright (c) 2009-2010 Christopher A. Taylor. 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 LibCat 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 HOLDER 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.
- */
- #ifndef CAT_SPHYNX_COLLEXION_HPP
- #define CAT_SPHYNX_COLLEXION_HPP
- #include <cat/threads/Mutex.hpp>
- #include <cat/threads/RegionAllocator.hpp>
- #include <cat/net/SphynxTransport.hpp>
- namespace cat {
- namespace sphynx {
- /*
- The purpose of sphynx::Collexion and sphynx::CollexionIterator is to
- store lists of Connexion object references and iterate through them.
- Since the number of clients may be in the thousands, I feel it is
- important to scale effectively. So in the Collexion data structure,
- insertion and removal are O(1) operations. Also locks should be held
- for the smallest amount of time possible, so I have taken care to make
- locks short and reduce the amount of locking. For example, the iterator
- caches large blocks of data instead of locking for each iteration.
- The design is optimized for cache usage, re-using common code to benefit
- from code cache and allocating and accessing table entries on cache line
- boundaries to double memory performance over a naive approach.
- */
- //// sphynx::Collexion
- template<class T>
- class CollexionIterator;
- template<class T>
- struct CollexionElement
- {
- // Number of active enumerators using this element
- // If references are held it cannot be deleted
- // so the KILL flag is set on the 'next' member and
- // the final enumerator to reduce the reference count
- // to zero is responsible for removal.
- u32 refcnt;
- // Bitfield:
- // 1 bit: COLLISION FLAG
- // 1 bit: KILL FLAG
- // 30 bits: Table index to next element in list + 1
- u32 next;
- // Data at this table element
- T *conn;
- };
- struct CollexionElement2
- {
- // Previous table element in list + 1
- u32 last;
- // Hash of data pointer from main entry (so it doesn't need to be recalculated during growth)
- u32 hash;
- };
- template<class T>
- class Collexion
- {
- static const u32 COLLIDE_MASK = 0x80000000;
- static const u32 KILL_MASK = 0x40000000;
- static const u32 NEXT_MASK = 0x3fffffff;
- static const u32 MIN_ALLOCATED = 32;
- private:
- // Number of used table elements
- u32 _used;
- // Number of allocated table elements
- u32 _allocated;
- // First table index in list of active elements
- u32 _first;
- // Primary table
- CollexionElement<T> *_table;
- // Secondary table, split off so that primary table elements will
- // fit on a cache line. Contains data that is only accessed rarely.
- CollexionElement2 *_table2;
- // Table lock
- Mutex _lock;
- protected:
- // Attempt to double size of hash table (does not hold lock)
- bool DoubleTable();
- // Hash a pointer to a 32-bit table key
- static CAT_INLINE u32 HashPtr(T *ptr)
- {
- u64 key = 0xBADdecafDEADbeef;
- #if defined(CAT_WORD_64)
- key ^= *(u64*)&ptr;
- #else
- key ^= *(u32*)&ptr;
- #endif
- key = (~key) + (key << 18);
- key = key ^ (key >> 31);
- key = key * 21;
- key = key ^ (key >> 11);
- key = key + (key << 6);
- key = key ^ (key >> 22);
- return (u32)key;
- }
- // Common functions shared by interface for good code cache usage:
- // Unlink a table key
- void Unlink(u32 key);
- // Fill an iterator with the next set of data
- // Returns false if no data remains to fill
- void Fill(CollexionIterator<T> &iter, u32 first);
- // Find a hash table key based on data
- u32 Find(T *conn);
- public:
- // Ctor zeros everything
- Collexion()
- {
- _first = 0;
- _used = 0;
- _allocated = 0;
- _table = 0;
- _table2 = 0;
- }
- // Dtor releases dangling memory
- ~Collexion();
- // Returns true if table is empty
- CAT_INLINE bool IsEmpty() { return _used == 0; }
- // Insert Connexion object, return false if already present or out of memory
- bool Insert(T *conn);
- // Remove Connexion object from list if it exists
- bool Remove(T *conn);
- // Begin iterating through list
- void Begin(CollexionIterator<T> &iter);
- // Iterate
- void Next(CollexionIterator<T> &iter, bool refill = true);
- };
- //// sphynx::CollexionIterator
- template<class T>
- class CollexionIterator
- {
- friend class Collexion<T>;
- static const u32 MAX_CACHE = 256;
- // Parent Collexion object
- Collexion<T> *_parent;
- // First and last hash table indices in parent
- u32 _first, _last;
- // Stores the size of the parent when snapshot was taken,
- // will invalidate _first and _last if table changed size
- u32 _prev_allocated;
- // Connexion object cache, to avoid locking and unlocking a lot
- T *_cache[MAX_CACHE];
- // Offset into cache and total elements in cache
- // Will grab another cache once offset reaches total
- u32 _offset, _total;
- public:
- // Smart pointer -style accessors
- CAT_INLINE T *Get() throw() { return _cache[_offset]; }
- CAT_INLINE T *operator->() throw() { return _cache[_offset]; }
- CAT_INLINE T &operator*() throw() { return *_cache[_offset]; }
- CAT_INLINE operator T*() { return _cache[_offset]; }
- public:
- // Ctor: Grabs first cache of Connexions
- CollexionIterator(Collexion<T> &begin);
- // Dtor: Calls Release()
- ~CollexionIterator();
- // Iterate to next Connexion object in list
- CollexionIterator &operator++();
- // Releases reference to any outstanding Connexions
- void Release();
- };
- //// sphynx::Collexion
- template<class T>
- bool Collexion<T>::DoubleTable()
- {
- u32 new_allocated = _allocated << 1;
- if (new_allocated < MIN_ALLOCATED) new_allocated = MIN_ALLOCATED;
- // Allocate secondary table
- u32 new_bytes2 = sizeof(CollexionElement2) * new_allocated;
- CollexionElement2 *new_table2 = reinterpret_cast<CollexionElement2*>(
- RegionAllocator::ii->Acquire(new_bytes2) );
- if (!new_table2) return false;
- // Allocate primary table
- u32 new_bytes = sizeof(CollexionElement<T>) * new_allocated;
- CollexionElement<T> *new_table = reinterpret_cast<CollexionElement<T> *>(
- RegionAllocator::ii->Acquire(new_bytes) );
- if (!new_table)
- {
- RegionAllocator::ii->Release(new_table2);
- return false;
- }
- CAT_CLR(new_table, new_bytes);
- u32 new_first = 0;
- if (_table && _table2)
- {
- // For each entry in the old table,
- u32 ii = _first, mask = _allocated - 1;
- while (ii)
- {
- --ii;
- CollexionElement<T> *oe = &_table[ii];
- u32 hash = _table2[ii].hash;
- u32 key = hash & mask;
- // While collisions occur,
- while (new_table[key].conn)
- {
- // Mark collision
- new_table[key].next |= COLLIDE_MASK;
- // Walk collision list
- key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
- }
- // Fill new table element
- new_table[key].conn = oe->conn;
- new_table2[key].hash = hash;
- // new_table[key].refcnt is already zero
- // Link new element to new list
- if (new_first)
- {
- new_table[key].next |= new_first;
- new_table2[new_first - 1].last = key;
- }
- // new_table[key].next is already zero so no need to zero it here
- new_first = key + 1;
- // Get next old table entry
- ii = oe->next & NEXT_MASK;
- }
- // Zero head->last
- new_table2[new_first - 1].last = 0;
- }
- // Resulting linked list starting with _first-1 will extend until e->next == 0
- if (_table2) RegionAllocator::ii->Release(_table2);
- if (_table) RegionAllocator::ii->Release(_table);
- _table = new_table;
- _table2 = new_table2;
- _allocated = new_allocated;
- _first = new_first;
- return true;
- }
- template<class T>
- Collexion<T>::~Collexion()
- {
- if (_table2)
- {
- RegionAllocator::ii->Release(_table2);
- }
- // If table doesn't exist, return
- if (!_table) return;
- // For each allocated table entry,
- for (u32 ii = 0; ii < _allocated; ++ii)
- {
- // Get Connexion object
- T *conn = _table[ii].conn;
- // If object is valid, release it
- if (conn) conn->ReleaseRef();
- }
- // Release table memory
- RegionAllocator::ii->Release(_table);
- }
- template<class T>
- bool Collexion<T>::Insert(T *conn)
- {
- u32 hash = HashPtr(conn);
- conn->AddRef();
- AutoMutex lock(_lock);
- // If more than half of the table will be used,
- if (_used >= (_allocated >> 1))
- {
- // Double the size of the table (O(1) allocation pattern)
- // Growing pains are softened by careful design
- if (!DoubleTable())
- {
- // On growth failure, return false
- lock.Release();
- conn->ReleaseRef();
- return false;
- }
- }
- // Mask off high bits to make table key from hash
- u32 mask = _allocated - 1;
- u32 key = hash & mask;
- // While empty table entry not found,
- while (_table[key].conn)
- {
- // If Connexion object is already in the table,
- if (_table[key].conn == conn)
- {
- // Return false on duplicate
- lock.Release();
- conn->ReleaseRef();
- return false;
- }
- // Mark as a collision
- _table[key].next |= COLLIDE_MASK;
- // Walk collision list
- key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
- }
- // Fill new element
- _table[key].conn = conn;
- _table[key].refcnt = 0;
- _table2[key].hash = hash;
- _table2[key].last = 0;
- // Link new element to front of list
- if (_first) _table2[_first - 1].last = key + 1;
- _table[key].next = (_table[key].next & COLLIDE_MASK) | _first;
- _first = key + 1;
- ++_used;
- return true;
- }
- template<class T>
- void Collexion<T>::Unlink(u32 key)
- {
- // Clear reference
- _table[key].conn = 0;
- // Unlink from active list
- u32 next = _table[key].next & NEXT_MASK;
- u32 last = _table2[key].last;
- if (last) _table[last-1].next = (_table[last-1].next & ~NEXT_MASK) | next;
- else _first = next;
- if (next) _table2[next-1].last = last;
- // If this key was a leaf on a collision wind,
- if (!(_table[key].next & COLLIDE_MASK))
- {
- u32 mask = _allocated - 1;
- do
- {
- // Go backwards through the collision list one step
- key = ((key + COLLISION_INCRINVERSE) * COLLISION_MULTINVERSE) & mask;
- // Stop where collision list stops
- if (!(_table[key].next & COLLIDE_MASK))
- break;
- // Turn off collision key for previous entry
- _table[key].next &= ~COLLIDE_MASK;
- } while (!_table[key].conn);
- }
- // Update number of used elements
- --_used;
- }
- template<class T>
- bool Collexion<T>::Remove(T *conn)
- {
- u32 hash = HashPtr(conn);
- AutoMutex lock(_lock);
- // If table doesn't exist,
- if (!_allocated) return false;
- // Mask off high bits to make table key from hash
- u32 mask = _allocated - 1;
- u32 key = hash & mask;
- // While target table entry not found,
- for (;;)
- {
- // If target was found,
- if (_table[key].conn == conn)
- {
- if (_table[key].refcnt)
- {
- // Mark it killed so iterator can clean it up when it's finished
- _table[key].next |= KILL_MASK;
- }
- else
- {
- Unlink(key);
- lock.Release();
- conn->ReleaseRef();
- }
- // Return success
- return true;
- }
- if (!(_table[key].next & COLLIDE_MASK))
- {
- break; // End of collision list
- }
- // Walk collision list
- key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
- }
- // Return failure: not found
- return false;
- }
- template<class T>
- void Collexion<T>::Fill(CollexionIterator<T> &iter, u32 first)
- {
- u32 key = first;
- // Find first list element that does not want to die
- while (key && (_table[key-1].next & KILL_MASK))
- {
- // Go to next
- key = _table[key-1].next & NEXT_MASK;
- }
- iter._offset = 0;
- // If no elements in table,
- if (!key)
- {
- // Return empty set
- iter._cache[0] = 0;
- iter._total = 0;
- iter._first = 0;
- iter._last = 0;
- return;
- }
- // Remember size of hash table in case it grows before iterator is done
- iter._prev_allocated = _allocated;
- // Remember first key for next iteration
- iter._first = key;
- // For each of the first MAX_CACHE elements in the table, copy the data pointer to cache
- u32 ii = 0, final = 0;
- do
- {
- // If element does not want to die,
- if (!(_table[key-1].next & KILL_MASK))
- {
- // Copy data pointer
- iter._cache[ii] = _table[key-1].conn;
- // Increment reference count for element
- _table[key-1].refcnt++;
- // Remember key as the next iteration starting point
- final = key;
- // Check if copy is complete
- if (++ii >= CollexionIterator<T>::MAX_CACHE) break;
- }
- // Go to next key
- key = _table[key-1].next & NEXT_MASK;
- } while (key);
- // Record number of elements written
- iter._total = ii;
- // Remember next key for next iteration
- iter._last = final;
- }
- template<class T>
- void Collexion<T>::Begin(CollexionIterator<T> &iter)
- {
- iter._parent = this;
- AutoMutex lock(_lock);
- Fill(iter, _first);
- }
- // Find a hash table key based on data
- template<class T>
- u32 Collexion<T>::Find(T *conn)
- {
- u32 mask = _allocated - 1;
- u32 key = HashPtr(conn) & mask;
- // Find the object in the collision list starting at the expected location
- while (_table[key].conn != conn)
- {
- // If at the end of the collision list,
- if (!(_table[key].next & COLLIDE_MASK))
- break; // Should never happen
- // Walk collision list
- key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
- }
- return key;
- }
- template<class T>
- void Collexion<T>::Next(CollexionIterator<T> &iter, bool refill)
- {
- T *release_list[CollexionIterator<T>::MAX_CACHE];
- u32 release_ii = 0;
- u32 key = iter._first;
- u32 last = iter._last;
- // If iteration is done,
- if (!key) return;
- AutoMutex lock(_lock);
- // If hash table changed size (rare),
- if (iter._prev_allocated != _allocated)
- {
- // iter._first and iter._last are invalid
- // ...but we can find them again based on the cached pointers!
- key = Find(iter._cache[0]) + 1;
- last = Find(iter._cache[iter._total - 1]) + 1;
- }
- last = _table[last-1].next & NEXT_MASK;
- // Release any table elements that want to die now
- do
- {
- u32 flags = _table[key-1].next;
- // If reference count is now zero for this element,
- if (0 == --_table[key-1].refcnt)
- {
- // If element wants to die,
- if (flags & KILL_MASK)
- {
- // Prepare to release data after lock is released
- release_list[release_ii++] = _table[key-1].conn;
- Unlink(key-1);
- }
- }
- key = flags & NEXT_MASK;
- } while (key != last);
- // Fill iterator starting with next key
- if (refill) Fill(iter, key);
- lock.Release();
- if (!refill)
- {
- // Return empty set
- iter._cache[0] = 0;
- iter._total = 0;
- iter._offset = 0;
- iter._first = 0;
- iter._last = 0;
- }
- // Release data awaiting destruction
- for (u32 ii = 0; ii < release_ii; ++ii)
- {
- release_list[ii]->ReleaseRef();
- }
- }
- //// sphynx::CollexionIterator
- template<class T>
- CollexionIterator<T>::CollexionIterator(Collexion<T> &begin)
- {
- begin.Begin(*this);
- }
- template<class T>
- CollexionIterator<T>::~CollexionIterator()
- {
- Release();
- }
- template<class T>
- CollexionIterator<T> &CollexionIterator<T>::operator++()
- {
- if (++_offset >= _total)
- {
- _parent->Next(*this, true);
- }
- return *this;
- }
- template<class T>
- void CollexionIterator<T>::Release()
- {
- if (_parent)
- {
- _parent->Next(*this, false);
- _parent = 0;
- }
- }
- } // namespace sphynx
- } // namespace cat
- #endif // CAT_SPHYNX_COLLEXION_HPP
|