/* Copyright 2008 The 'A Concurrent Hashtable' development team (http://www.codeplex.com/CH/People/ProjectPeople.aspx) This library is licensed under the GNU Library General Public License (LGPL). You should have received a copy of the license along with the source code. If not, an online copy of the license can be found at http://www.codeplex.com/CH/license. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using Orvid.Concurrent.Threading; namespace Orvid.Concurrent.Collections { /// /// A 'single writer - multi reader' threaded segment in a hashtable. /// /// /// /// /// Though each segment can be accessed by 1 writer thread simultaneously, the hashtable becomes concurrent /// for writing by containing many segments so that collisions are rare. The table will be fully concurrent /// for read operations as far as they are not colliding with write operations. /// Each segment is itself a small hashtable that can grow and shrink individualy. This prevents blocking of /// the entire hashtable when growing or shrinking is needed. Because each segment is relatively small (depending on /// the quality of the hash) resizing of the individual segments should not take much time. /// internal class Segment { #region Construction protected Segment( ) {} public static Segment Create(Int32 initialSize) { var instance = new Segment(); instance.Initialize(initialSize); return instance; } /// /// Initialize the segment. /// /// protected virtual void Initialize(Int32 initialSize) { _List = new TStored[Math.Max(4, initialSize)]; } /// /// When segment gets introduced into hashtable then its allocated space should be added to the /// total allocated space. /// Single threaded access or locking is needed /// /// public void Welcome(ConcurrentHashtable traits) { traits.EffectTotalAllocatedSpace(_List.Length); } /// /// When segment gets removed from hashtable then its allocated space should be subtracted to the /// total allocated space. /// Single threaded access or locking is needed /// /// public void Bye(ConcurrentHashtable traits) { traits.EffectTotalAllocatedSpace(-_List.Length); _List = null; } #endregion #region Locking TinyReaderWriterLock _Lock; internal void LockForWriting() { _Lock.LockForWriting(); } internal void LockForReading() { _Lock.LockForReading(); } internal bool Lock(bool forReading) { return forReading ? _Lock.LockForReading(false) : _Lock.LockForWriting(false); } internal void ReleaseForWriting() { _Lock.ReleaseForWriting(); } internal void ReleaseForReading() { _Lock.ReleaseForReading(); } internal void Release(bool forReading) { if (forReading) _Lock.ReleaseForReading(); else _Lock.ReleaseForWriting(); } #endregion /// /// Array with 'slots'. Each slot can be filled or empty. /// internal TStored[] _List; /// /// Boolean value indicating if this segment has not been trashed yet. /// internal bool IsAlive { get { return _List != null; } } #region Item Manipulation methods /// /// Inserts an item into a *not empty* spot given by position i. It moves items forward until an empty spot is found. /// /// /// /// /// private void InsertItemAtIndex(UInt32 mask, UInt32 i, TStored itemCopy, ConcurrentHashtable traits) { do { //swap TStored temp = _List[i]; _List[i] = itemCopy; itemCopy = temp; i = (i + 1) & mask; } while(!traits.IsEmpty(ref _List[i])); _List[i] = itemCopy; } /// /// Find item in segment. /// /// Reference to the search key to use. /// Out reference to store the found item in. /// Object that tells this segment how to treat items and keys. /// True if an item could be found, otherwise false. public bool FindItem(ref TSearch key, out TStored item, ConcurrentHashtable traits) { var searchHash = traits.GetKeyHashCode(ref key); var mask = (UInt32)(_List.Length - 1); var i = searchHash & mask; if (!traits.IsEmpty(ref _List[i])) { var firstHash = traits.GetItemHashCode(ref _List[i]); var storedItemHash = firstHash; var searchHashDiff = (searchHash - firstHash) & mask; do { if (storedItemHash == searchHash && traits.ItemEqualsKey(ref _List[i], ref key)) { item = _List[i]; return true; } i = (i + 1) & mask; if(traits.IsEmpty(ref _List[i])) break; storedItemHash = traits.GetItemHashCode(ref _List[i]); } while (((storedItemHash - firstHash) & mask) <= searchHashDiff); } item = default(TStored); return false; } /// /// Find an existing item or, if it can't be found, insert a new item. /// /// Reference to the item that will be inserted if an existing item can't be found. It will also be used to search with. /// Out reference to store the found item or, if it can not be found, the new inserted item. /// Object that tells this segment how to treat items and keys. /// True if an existing item could be found, otherwise false. public bool GetOldestItem(ref TStored key, out TStored item, ConcurrentHashtable traits) { var searchHash = traits.GetItemHashCode(ref key); var mask = (UInt32)(_List.Length - 1); var i = searchHash & mask; if (!traits.IsEmpty(ref _List[i])) { var firstHash = traits.GetItemHashCode(ref _List[i]); var storedItemHash = firstHash; var searchHashDiff = (searchHash - firstHash) & mask; while (true) { if (storedItemHash == searchHash && traits.ItemEqualsItem(ref _List[i], ref key)) { item = _List[i]; return true; } i = (i + 1) & mask; if (traits.IsEmpty(ref _List[i])) break; storedItemHash = traits.GetItemHashCode(ref _List[i]); if (((storedItemHash - firstHash) & mask) > searchHashDiff) { //insert InsertItemAtIndex(mask, i, key, traits); IncrementCount(traits); item = key; return false; } } } item = _List[i] = key; IncrementCount(traits); return false; } /// /// Inserts an item in the segment, possibly replacing an equal existing item. /// /// A reference to the item to insert. /// An out reference where any replaced item will be written to, if no item was replaced the new item will be written to this reference. /// Object that tells this segment how to treat items and keys. /// True if an existing item could be found and is replaced, otherwise false. public bool InsertItem(ref TStored key, out TStored item, ConcurrentHashtable traits) { var searchHash = traits.GetItemHashCode(ref key); var mask = (UInt32)(_List.Length - 1); var i = searchHash & mask; if (!traits.IsEmpty(ref _List[i])) { var firstHash = traits.GetItemHashCode(ref _List[i]); var storedItemHash = firstHash; var searchHashDiff = (searchHash - firstHash) & mask; while (true) { if (storedItemHash == searchHash && traits.ItemEqualsItem(ref _List[i], ref key)) { item = _List[i]; _List[i] = key; return true; } i = (i + 1) & mask; if (traits.IsEmpty(ref _List[i])) break; storedItemHash = traits.GetItemHashCode(ref _List[i]); if (((storedItemHash - firstHash) & mask) > searchHashDiff) { //insert InsertItemAtIndex(mask, i, key, traits); IncrementCount(traits); item = key; return false; } } } item = _List[i] = key; IncrementCount(traits); return false; } /// /// Removes an item from the segment. /// /// A reference to the key to search with. /// An out reference where the removed item will be stored or default() if no item to remove can be found. /// Object that tells this segment how to treat items and keys. /// True if an item could be found and is removed, false otherwise. public bool RemoveItem(ref TSearch key, out TStored item, ConcurrentHashtable traits) { var searchHash = traits.GetKeyHashCode(ref key); var mask = (UInt32)(_List.Length - 1); var i = searchHash & mask; if (!traits.IsEmpty(ref _List[i])) { var firstHash = traits.GetItemHashCode(ref _List[i]); var storedItemHash = firstHash; var searchHashDiff = (searchHash - firstHash) & mask; do { if (storedItemHash == searchHash && traits.ItemEqualsKey(ref _List[i], ref key)) { item = _List[i]; RemoveAtIndex(i, traits); DecrementCount(traits); return true; } i = (i + 1) & mask; if (traits.IsEmpty(ref _List[i])) break; storedItemHash = traits.GetItemHashCode(ref _List[i]); } while (((storedItemHash - firstHash) & mask) <= searchHashDiff); } item = default(TStored); return false; } protected void RemoveAtIndex(UInt32 index, ConcurrentHashtable traits) { var mask = (UInt32)(_List.Length - 1); var i = index; var j = (index + 1) & mask; while(!traits.IsEmpty(ref _List[j]) && (traits.GetItemHashCode(ref _List[j]) & mask) != j) { _List[i] = _List[j]; i = j; j = (j + 1) & mask; } _List[i] = default(TStored); } public void Clear(ConcurrentHashtable traits) { var oldList = _List; _List = new TStored[4]; var effect = _List.Length - oldList.Length; if (effect != 0) traits.EffectTotalAllocatedSpace(effect); _Count = 0; } /// /// Iterate over items in the segment. /// /// Position beyond which the next filled slot will be found and the item in that slot returned. (Starting with -1) /// Out reference where the next item will be stored or default if the end of the segment is reached. /// Object that tells this segment how to treat items and keys. /// The index position the next item has been found or -1 otherwise. public int GetNextItem(int beyond, out TStored item, ConcurrentHashtable traits) { for (int end = _List.Length; ++beyond < end; ) { if (!traits.IsEmpty(ref _List[beyond])) { item = _List[beyond]; return beyond; } } item = default(TStored); return -1; } #endregion #region Resizing protected virtual void ResizeList(ConcurrentHashtable traits) { var oldList = _List; var oldListLength = oldList.Length; var newListLength = 2; while (newListLength < _Count) newListLength <<= 1; newListLength <<= 1; if (newListLength != oldListLength) { _List = new TStored[newListLength]; var mask = (UInt32)(newListLength - 1); for (int i = 0; i != oldListLength; ++i) if (!traits.IsEmpty(ref oldList[i])) { var searchHash = traits.GetItemHashCode(ref oldList[i]); //j is prefered insertion pos in new list. var j = searchHash & mask; if (traits.IsEmpty(ref _List[j])) _List[j] = oldList[i]; else { var firstHash = traits.GetItemHashCode(ref _List[j]); var storedItemHash = firstHash; var searchHashDiff = (searchHash - firstHash) & mask; while (true) { j = (j + 1) & mask; if (traits.IsEmpty(ref _List[j])) { _List[j] = oldList[i]; break; } storedItemHash = traits.GetItemHashCode(ref _List[j]); if (((storedItemHash - firstHash) & mask) > searchHashDiff) { InsertItemAtIndex(mask, j, oldList[i], traits); break; } } } } traits.EffectTotalAllocatedSpace(newListLength - oldListLength); } } /// /// Total numer of filled slots in _List. /// internal Int32 _Count; protected void DecrementCount(ConcurrentHashtable traits, int amount) { var oldListLength = _List.Length; _Count -= amount; if (oldListLength > 4 && _Count < (oldListLength >> 2)) //Shrink ResizeList(traits); } protected void DecrementCount(ConcurrentHashtable traits) { DecrementCount(traits, 1); } private void IncrementCount(ConcurrentHashtable traits) { var oldListLength = _List.Length; if (++_Count >= (oldListLength - (oldListLength >> 2))) //Grow ResizeList(traits); } /// /// Remove any excess allocated space /// /// internal void Trim(ConcurrentHashtable traits) { DecrementCount(traits, 0); } #endregion } }