Shadowrun: Awakened 29 September 2011 - Build 871
SphynxCollexion.hpp
Go to the documentation of this file.
00001 /*
00002     Copyright (c) 2009-2010 Christopher A. Taylor.  All rights reserved.
00003 
00004     Redistribution and use in source and binary forms, with or without
00005     modification, are permitted provided that the following conditions are met:
00006 
00007     * Redistributions of source code must retain the above copyright notice,
00008       this list of conditions and the following disclaimer.
00009     * Redistributions in binary form must reproduce the above copyright notice,
00010       this list of conditions and the following disclaimer in the documentation
00011       and/or other materials provided with the distribution.
00012     * Neither the name of LibCat nor the names of its contributors may be used
00013       to endorse or promote products derived from this software without
00014       specific prior written permission.
00015 
00016     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00017     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00018     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00019     ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00020     LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00021     CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00022     SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00023     INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00024     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00025     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00026     POSSIBILITY OF SUCH DAMAGE.
00027 */
00028 
00029 #ifndef CAT_SPHYNX_COLLEXION_HPP
00030 #define CAT_SPHYNX_COLLEXION_HPP
00031 
00032 #include <cat/threads/Mutex.hpp>
00033 #include <cat/threads/RegionAllocator.hpp>
00034 #include <cat/net/SphynxTransport.hpp>
00035 
00036 namespace cat {
00037 
00038 
00039 namespace sphynx {
00040 
00041 
00042 /*
00043     The purpose of sphynx::Collexion and sphynx::CollexionIterator is to
00044     store lists of Connexion object references and iterate through them.
00045 
00046     Since the number of clients may be in the thousands, I feel it is
00047     important to scale effectively.  So in the Collexion data structure,
00048     insertion and removal are O(1) operations.  Also locks should be held
00049     for the smallest amount of time possible, so I have taken care to make
00050     locks short and reduce the amount of locking.  For example, the iterator
00051     caches large blocks of data instead of locking for each iteration.
00052 
00053     The design is optimized for cache usage, re-using common code to benefit
00054     from code cache and allocating and accessing table entries on cache line
00055     boundaries to double memory performance over a naive approach.
00056 */
00057 
00058 
00060 
00061 template<class T>
00062 class CollexionIterator;
00063 
00064 template<class T>
00065 struct CollexionElement
00066 {
00067     // Number of active enumerators using this element
00068     // If references are held it cannot be deleted
00069     // so the KILL flag is set on the 'next' member and
00070     // the final enumerator to reduce the reference count
00071     // to zero is responsible for removal.
00072     u32 refcnt;
00073 
00074     // Bitfield:
00075     //  1 bit: COLLISION FLAG
00076     //  1 bit: KILL FLAG
00077     //  30 bits: Table index to next element in list + 1
00078     u32 next;
00079 
00080     // Data at this table element
00081     T *conn;
00082 };
00083 
00084 struct CollexionElement2
00085 {
00086     // Previous table element in list + 1
00087     u32 last;
00088 
00089     // Hash of data pointer from main entry (so it doesn't need to be recalculated during growth)
00090     u32 hash;
00091 };
00092 
00093 template<class T>
00094 class Collexion
00095 {
00096     static const u32 COLLIDE_MASK = 0x80000000;
00097     static const u32 KILL_MASK = 0x40000000;
00098     static const u32 NEXT_MASK = 0x3fffffff;
00099     static const u32 MIN_ALLOCATED = 32;
00100 
00101 private:
00102     // Number of used table elements
00103     u32 _used;
00104 
00105     // Number of allocated table elements
00106     u32 _allocated;
00107 
00108     // First table index in list of active elements
00109     u32 _first;
00110 
00111     // Primary table
00112     CollexionElement<T> *_table;
00113 
00114     // Secondary table, split off so that primary table elements will
00115     // fit on a cache line.  Contains data that is only accessed rarely.
00116     CollexionElement2 *_table2;
00117 
00118     // Table lock
00119     Mutex _lock;
00120 
00121 protected:
00122     // Attempt to double size of hash table (does not hold lock)
00123     bool DoubleTable();
00124 
00125     // Hash a pointer to a 32-bit table key
00126     static CAT_INLINE u32 HashPtr(T *ptr)
00127     {
00128         u64 key = 0xBADdecafDEADbeef;
00129 
00130 #if defined(CAT_WORD_64)
00131         key ^= *(u64*)&ptr;
00132 #else
00133         key ^= *(u32*)&ptr;
00134 #endif
00135 
00136         key = (~key) + (key << 18);
00137         key = key ^ (key >> 31);
00138         key = key * 21;
00139         key = key ^ (key >> 11);
00140         key = key + (key << 6);
00141         key = key ^ (key >> 22);
00142         return (u32)key;
00143     }
00144 
00145     // Common functions shared by interface for good code cache usage:
00146 
00147     // Unlink a table key
00148     void Unlink(u32 key);
00149 
00150     // Fill an iterator with the next set of data
00151     // Returns false if no data remains to fill
00152     void Fill(CollexionIterator<T> &iter, u32 first);
00153 
00154     // Find a hash table key based on data
00155     u32 Find(T *conn);
00156 
00157 public:
00158     // Ctor zeros everything
00159     Collexion()
00160     {
00161         _first = 0;
00162         _used = 0;
00163         _allocated = 0;
00164         _table = 0;
00165         _table2 = 0;
00166     }
00167 
00168     // Dtor releases dangling memory
00169     ~Collexion();
00170 
00171     // Returns true if table is empty
00172     CAT_INLINE bool IsEmpty() { return _used == 0; }
00173 
00174     // Insert Connexion object, return false if already present or out of memory
00175     bool Insert(T *conn);
00176 
00177     // Remove Connexion object from list if it exists
00178     bool Remove(T *conn);
00179 
00180     // Begin iterating through list
00181     void Begin(CollexionIterator<T> &iter);
00182 
00183     // Iterate
00184     void Next(CollexionIterator<T> &iter, bool refill = true);
00185 };
00186 
00187 
00189 
00190 template<class T>
00191 class CollexionIterator
00192 {
00193     friend class Collexion<T>;
00194 
00195     static const u32 MAX_CACHE = 256;
00196 
00197     // Parent Collexion object
00198     Collexion<T> *_parent;
00199 
00200     // First and last hash table indices in parent
00201     u32 _first, _last;
00202 
00203     // Stores the size of the parent when snapshot was taken,
00204     // will invalidate _first and _last if table changed size
00205     u32 _prev_allocated;
00206 
00207     // Connexion object cache, to avoid locking and unlocking a lot
00208     T *_cache[MAX_CACHE];
00209 
00210     // Offset into cache and total elements in cache
00211     // Will grab another cache once offset reaches total
00212     u32 _offset, _total;
00213 
00214 public:
00215     // Smart pointer -style accessors
00216     CAT_INLINE T *Get() throw() { return _cache[_offset]; }
00217     CAT_INLINE T *operator->() throw() { return _cache[_offset]; }
00218     CAT_INLINE T &operator*() throw() { return *_cache[_offset]; }
00219     CAT_INLINE operator T*() { return _cache[_offset]; }
00220 
00221 public:
00222     // Ctor: Grabs first cache of Connexions
00223     CollexionIterator(Collexion<T> &begin);
00224 
00225     // Dtor: Calls Release()
00226     ~CollexionIterator();
00227 
00228     // Iterate to next Connexion object in list
00229     CollexionIterator &operator++();
00230 
00231     // Releases reference to any outstanding Connexions
00232     void Release();
00233 };
00234 
00235 
00237 
00238 template<class T>
00239 bool Collexion<T>::DoubleTable()
00240 {
00241     u32 new_allocated = _allocated << 1;
00242     if (new_allocated < MIN_ALLOCATED) new_allocated = MIN_ALLOCATED;
00243 
00244     // Allocate secondary table
00245     u32 new_bytes2 = sizeof(CollexionElement2) * new_allocated;
00246     CollexionElement2 *new_table2 = reinterpret_cast<CollexionElement2*>(
00247         RegionAllocator::ii->Acquire(new_bytes2) );
00248 
00249     if (!new_table2) return false;
00250 
00251     // Allocate primary table
00252     u32 new_bytes = sizeof(CollexionElement<T>) * new_allocated;
00253     CollexionElement<T> *new_table = reinterpret_cast<CollexionElement<T> *>(
00254         RegionAllocator::ii->Acquire(new_bytes) );
00255 
00256     if (!new_table)
00257     {
00258         RegionAllocator::ii->Release(new_table2);
00259         return false;
00260     }
00261 
00262     CAT_CLR(new_table, new_bytes);
00263 
00264     u32 new_first = 0;
00265 
00266     if (_table && _table2)
00267     {
00268         // For each entry in the old table,
00269         u32 ii = _first, mask = _allocated - 1;
00270 
00271         while (ii)
00272         {
00273             --ii;
00274             CollexionElement<T> *oe = &_table[ii];
00275             u32 hash = _table2[ii].hash;
00276             u32 key = hash & mask;
00277 
00278             // While collisions occur,
00279             while (new_table[key].conn)
00280             {
00281                 // Mark collision
00282                 new_table[key].next |= COLLIDE_MASK;
00283 
00284                 // Walk collision list
00285                 key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
00286             }
00287 
00288             // Fill new table element
00289             new_table[key].conn = oe->conn;
00290             new_table2[key].hash = hash;
00291             // new_table[key].refcnt is already zero
00292 
00293             // Link new element to new list
00294             if (new_first)
00295             {
00296                 new_table[key].next |= new_first;
00297                 new_table2[new_first - 1].last = key;
00298             }
00299             // new_table[key].next is already zero so no need to zero it here
00300             new_first = key + 1;
00301 
00302             // Get next old table entry
00303             ii = oe->next & NEXT_MASK;
00304         }
00305 
00306         // Zero head->last
00307         new_table2[new_first - 1].last = 0;
00308     }
00309 
00310     // Resulting linked list starting with _first-1 will extend until e->next == 0
00311 
00312     if (_table2) RegionAllocator::ii->Release(_table2);
00313     if (_table) RegionAllocator::ii->Release(_table);
00314 
00315     _table = new_table;
00316     _table2 = new_table2;
00317     _allocated = new_allocated;
00318     _first = new_first;
00319     return true;
00320 }
00321 
00322 template<class T>
00323 Collexion<T>::~Collexion()
00324 {
00325     if (_table2)
00326     {
00327         RegionAllocator::ii->Release(_table2);
00328     }
00329 
00330     // If table doesn't exist, return
00331     if (!_table) return;
00332 
00333     // For each allocated table entry,
00334     for (u32 ii = 0; ii < _allocated; ++ii)
00335     {
00336         // Get Connexion object
00337         T *conn = _table[ii].conn;
00338 
00339         // If object is valid, release it
00340         if (conn) conn->ReleaseRef();
00341     }
00342 
00343     // Release table memory
00344     RegionAllocator::ii->Release(_table);
00345 }
00346 
00347 template<class T>
00348 bool Collexion<T>::Insert(T *conn)
00349 {
00350     u32 hash = HashPtr(conn);
00351     conn->AddRef();
00352 
00353     AutoMutex lock(_lock);
00354 
00355     // If more than half of the table will be used,
00356     if (_used >= (_allocated >> 1))
00357     {
00358         // Double the size of the table (O(1) allocation pattern)
00359         // Growing pains are softened by careful design
00360         if (!DoubleTable())
00361         {
00362             // On growth failure, return false
00363             lock.Release();
00364 
00365             conn->ReleaseRef();
00366 
00367             return false;
00368         }
00369     }
00370 
00371     // Mask off high bits to make table key from hash
00372     u32 mask = _allocated - 1;
00373     u32 key = hash & mask;
00374 
00375     // While empty table entry not found,
00376     while (_table[key].conn)
00377     {
00378         // If Connexion object is already in the table,
00379         if (_table[key].conn == conn)
00380         {
00381             // Return false on duplicate
00382             lock.Release();
00383 
00384             conn->ReleaseRef();
00385 
00386             return false;
00387         }
00388 
00389         // Mark as a collision
00390         _table[key].next |= COLLIDE_MASK;
00391 
00392         // Walk collision list
00393         key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
00394     }
00395 
00396     // Fill new element
00397     _table[key].conn = conn;
00398     _table[key].refcnt = 0;
00399     _table2[key].hash = hash;
00400     _table2[key].last = 0;
00401 
00402     // Link new element to front of list
00403     if (_first) _table2[_first - 1].last = key + 1;
00404     _table[key].next = (_table[key].next & COLLIDE_MASK) | _first;
00405     _first = key + 1;
00406 
00407     ++_used;
00408     return true;
00409 }
00410 
00411 template<class T>
00412 void Collexion<T>::Unlink(u32 key)
00413 {
00414     // Clear reference
00415     _table[key].conn = 0;
00416 
00417     // Unlink from active list
00418     u32 next = _table[key].next & NEXT_MASK;
00419     u32 last = _table2[key].last;
00420 
00421     if (last) _table[last-1].next = (_table[last-1].next & ~NEXT_MASK) | next;
00422     else _first = next;
00423     if (next) _table2[next-1].last = last;
00424 
00425     // If this key was a leaf on a collision wind,
00426     if (!(_table[key].next & COLLIDE_MASK))
00427     {
00428         u32 mask = _allocated - 1;
00429 
00430         do
00431         {
00432             // Go backwards through the collision list one step
00433             key = ((key + COLLISION_INCRINVERSE) * COLLISION_MULTINVERSE) & mask;
00434 
00435             // Stop where collision list stops
00436             if (!(_table[key].next & COLLIDE_MASK))
00437                 break;
00438 
00439             // Turn off collision key for previous entry
00440             _table[key].next &= ~COLLIDE_MASK;
00441 
00442         } while (!_table[key].conn);
00443     }
00444 
00445     // Update number of used elements
00446     --_used;
00447 }
00448 
00449 template<class T>
00450 bool Collexion<T>::Remove(T *conn)
00451 {
00452     u32 hash = HashPtr(conn);
00453 
00454     AutoMutex lock(_lock);
00455 
00456     // If table doesn't exist,
00457     if (!_allocated) return false;
00458 
00459     // Mask off high bits to make table key from hash
00460     u32 mask = _allocated - 1;
00461     u32 key = hash & mask;
00462 
00463     // While target table entry not found,
00464     for (;;)
00465     {
00466         // If target was found,
00467         if (_table[key].conn == conn)
00468         {
00469             if (_table[key].refcnt)
00470             {
00471                 // Mark it killed so iterator can clean it up when it's finished
00472                 _table[key].next |= KILL_MASK;
00473             }
00474             else
00475             {
00476                 Unlink(key);
00477 
00478                 lock.Release();
00479 
00480                 conn->ReleaseRef();
00481             }
00482 
00483             // Return success
00484             return true;
00485         }
00486 
00487         if (!(_table[key].next & COLLIDE_MASK))
00488         {
00489             break; // End of collision list
00490         }
00491 
00492         // Walk collision list
00493         key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
00494     }
00495 
00496     // Return failure: not found
00497     return false;
00498 }
00499 
00500 template<class T>
00501 void Collexion<T>::Fill(CollexionIterator<T> &iter, u32 first)
00502 {
00503     u32 key = first;
00504 
00505     // Find first list element that does not want to die
00506     while (key && (_table[key-1].next & KILL_MASK))
00507     {
00508         // Go to next
00509         key = _table[key-1].next & NEXT_MASK;
00510     }
00511 
00512     iter._offset = 0;
00513 
00514     // If no elements in table,
00515     if (!key)
00516     {
00517         // Return empty set
00518         iter._cache[0] = 0;
00519         iter._total = 0;
00520         iter._first = 0;
00521         iter._last = 0;
00522         return;
00523     }
00524 
00525     // Remember size of hash table in case it grows before iterator is done
00526     iter._prev_allocated = _allocated;
00527 
00528     // Remember first key for next iteration
00529     iter._first = key;
00530 
00531     // For each of the first MAX_CACHE elements in the table, copy the data pointer to cache
00532     u32 ii = 0, final = 0;
00533 
00534     do
00535     {
00536         // If element does not want to die,
00537         if (!(_table[key-1].next & KILL_MASK))
00538         {
00539             // Copy data pointer
00540             iter._cache[ii] = _table[key-1].conn;
00541 
00542             // Increment reference count for element
00543             _table[key-1].refcnt++;
00544 
00545             // Remember key as the next iteration starting point
00546             final = key;
00547 
00548             // Check if copy is complete
00549             if (++ii >= CollexionIterator<T>::MAX_CACHE) break;
00550         }
00551 
00552         // Go to next key
00553         key = _table[key-1].next & NEXT_MASK;
00554 
00555     } while (key);
00556 
00557     // Record number of elements written
00558     iter._total = ii;
00559 
00560     // Remember next key for next iteration
00561     iter._last = final;
00562 }
00563 
00564 template<class T>
00565 void Collexion<T>::Begin(CollexionIterator<T> &iter)
00566 {
00567     iter._parent = this;
00568 
00569     AutoMutex lock(_lock);
00570 
00571     Fill(iter, _first);
00572 }
00573 
00574 // Find a hash table key based on data
00575 template<class T>
00576 u32 Collexion<T>::Find(T *conn)
00577 {
00578     u32 mask = _allocated - 1;
00579     u32 key = HashPtr(conn) & mask;
00580 
00581     // Find the object in the collision list starting at the expected location
00582     while (_table[key].conn != conn)
00583     {
00584         // If at the end of the collision list,
00585         if (!(_table[key].next & COLLIDE_MASK))
00586             break; // Should never happen
00587 
00588         // Walk collision list
00589         key = (key * COLLISION_MULTIPLIER + COLLISION_INCREMENTER) & mask;
00590     }
00591 
00592     return key;
00593 }
00594 
00595 template<class T>
00596 void Collexion<T>::Next(CollexionIterator<T> &iter, bool refill)
00597 {
00598     T *release_list[CollexionIterator<T>::MAX_CACHE];
00599     u32 release_ii = 0;
00600 
00601     u32 key = iter._first;
00602     u32 last = iter._last;
00603 
00604     // If iteration is done,
00605     if (!key) return;
00606 
00607     AutoMutex lock(_lock);
00608 
00609     // If hash table changed size (rare),
00610     if (iter._prev_allocated != _allocated)
00611     {
00612         // iter._first and iter._last are invalid
00613         // ...but we can find them again based on the cached pointers!
00614         key = Find(iter._cache[0]) + 1;
00615         last = Find(iter._cache[iter._total - 1]) + 1;
00616     }
00617 
00618     last = _table[last-1].next & NEXT_MASK;
00619 
00620     // Release any table elements that want to die now
00621     do 
00622     {
00623         u32 flags = _table[key-1].next;
00624 
00625         // If reference count is now zero for this element,
00626         if (0 == --_table[key-1].refcnt)
00627         {
00628             // If element wants to die,
00629             if (flags & KILL_MASK)
00630             {
00631                 // Prepare to release data after lock is released
00632                 release_list[release_ii++] = _table[key-1].conn;
00633 
00634                 Unlink(key-1);
00635             }
00636         }
00637 
00638         key = flags & NEXT_MASK;
00639 
00640     } while (key != last);
00641 
00642     // Fill iterator starting with next key
00643     if (refill) Fill(iter, key);
00644 
00645     lock.Release();
00646 
00647     if (!refill)
00648     {
00649         // Return empty set
00650         iter._cache[0] = 0;
00651         iter._total = 0;
00652         iter._offset = 0;
00653         iter._first = 0;
00654         iter._last = 0;
00655     }
00656 
00657     // Release data awaiting destruction
00658     for (u32 ii = 0; ii < release_ii; ++ii)
00659     {
00660         release_list[ii]->ReleaseRef();
00661     }
00662 }
00663 
00664 
00666 
00667 template<class T>
00668 CollexionIterator<T>::CollexionIterator(Collexion<T> &begin)
00669 {
00670     begin.Begin(*this);
00671 }
00672 
00673 template<class T>
00674 CollexionIterator<T>::~CollexionIterator()
00675 {
00676     Release();
00677 }
00678 
00679 template<class T>
00680 CollexionIterator<T> &CollexionIterator<T>::operator++()
00681 {
00682     if (++_offset >= _total)
00683     {
00684         _parent->Next(*this, true);
00685     }
00686 
00687     return *this;
00688 }
00689 
00690 template<class T>
00691 void CollexionIterator<T>::Release()
00692 {
00693     if (_parent)
00694     {
00695         _parent->Next(*this, false);
00696         _parent = 0;
00697     }
00698 }
00699 
00700 
00701 } // namespace sphynx
00702 
00703 
00704 } // namespace cat
00705 
00706 #endif // CAT_SPHYNX_COLLEXION_HPP

Copyright © 2007-2010 by The Shadowrun: Awakened Team. This work is licensed under the GNU Lesser General Public License 3.

GNU Lesser General Public License 3 Sourceforge.net