#define newthing using System; using System.Text; using System.IO; using System.Diagnostics; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Linq; #pragma warning disable 0420, 0414 public partial class LockFreeDictionary<K, V> : IDictionary<K, V> where K : IEquatable<K> where V : IEquatable<V> { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// /// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [StructLayout(LayoutKind.Auto, Pack = 64)] public partial class Config { int EntriesPerBlockPower; int EntriesPerBlock; int GlobalIndexMask; int ResizeTrigger; public Config(LockFreeDictionary<K, V> dict, int entries_per_block_power) { this.d = dict; EntriesPerBlockPower = entries_per_block_power; EntriesPerBlock = 1 << EntriesPerBlockPower; GlobalIndexMask = EntriesPerBlock - 1; ResizeTrigger = GlobalIndexMask & unchecked((int)0xFFFFFFF0); entry_blocks = new Entry[0x10][]; entry_blocks[0] = new Entry[EntriesPerBlock]; #if newthing free_lists = new Freelist[800]; free_lists_x = new Freelist[800]; for (int i = 0; i < free_lists.Length; i+=100) { free_lists_x[i] = free_lists[i] = new Freelist(this); } #else free_lists = new Freelist[BucketList.HashFriendly((uint)dict.c_processors * 2)]; for (int i = 0; i < free_lists.Length; i++) free_lists[i] = new Freelist(this); #endif m_buckets = new BucketListPrimary(this, DefaultCapacity); } internal LockFreeDictionary<K, V> d; /// each EntryBlock stores a fixed number of entries Entry[][] entry_blocks; /// entries have a global index across all EntryBlocks int m_gx_next; /// a pair of bucket arrays that can be atomically swapped internal volatile BucketList m_buckets; /// the number of entries in the dictionary is maintained with interlocked operations internal int m_count = 0; /// a small number of free lists internal Freelist[] free_lists; internal Freelist[] free_lists_x; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// internal bool IsCurrent { get { return this == d.m_config; } } internal BucketList ChangeBuckets(BucketList bl_old, BucketList bl_new) { BucketList bl_cur = Interlocked.CompareExchange(ref m_buckets, bl_new, bl_old); return bl_cur == bl_old ? bl_new : bl_cur; } internal void CheckAssist(ref int mtid) { Config.BucketListResize blr = m_buckets as Config.BucketListResize; if (blr != null) blr._check_assist(ref mtid); } internal void RequestBucketResize(ref int mtid) { Config.BucketListPrimary blp = m_buckets as Config.BucketListPrimary; if (blp != null) blp.InitiateBucketResize(ref mtid); } internal void IncrementCountAndCheck(ref int mtid) { int c = Interlocked.Increment(ref m_count); if (c > m_buckets.BucketCount) RequestBucketResize(ref mtid); } internal IEnumerator<KeyValuePair<K, V>> EnumerateKeyValuePairs() { int mtid = -1; for (int offs, i = 0; i < m_gx_next; i++) { Entry e = GetBlock(i, out offs)[offs]; /// todo: this is subject to torn reads Thread.MemoryBarrier(); if (e.hash != Freelist.FreelistHash) { bool f; BucketList.BucketHead bh; do m_buckets.GetBucketHead(ref mtid, e.hash, out bh); /// ignore config change while (!bh.TryContainsKey(e.key, out f)); if (f) yield return new KeyValuePair<K, V>(e.key, e.value); } } } #if newthing int[] fl_affinity = new int[256]; Freelist GetFreelist(ref int mtid) { if (mtid != -1) return free_lists_x[mtid]; int mid = (Thread.CurrentThread.ManagedThreadId & 0xFF) * 8; int ix = fl_affinity[mid]; Freelist fl_use; while ((fl_use = free_lists[ix]) == null || Interlocked.CompareExchange(ref free_lists[ix], null, fl_use) != fl_use) { if ((ix += 100) == free_lists.Length) ix = 0; } mtid = fl_affinity[mid] = ix; return fl_use; } #endif /// <summary> /// Get an unused entry index, either from one of the freelists, or by allocating a new one /// </summary> internal int GetUnusedIndex(ref int mtid) { Freelist fl_use; #if newthing fl_use = GetFreelist(ref mtid); #else if (mtid == -1) mtid = Thread.CurrentThread.ManagedThreadId % free_lists.Length; fl_use = free_lists[mtid]; #endif int gx; if (!fl_use.TryGetFreeIndex(out gx)) { if ((uint)m_gx_next >= 0xFFFFFFF0) throw new OverflowException("The dictionary already contains the amximum number of elements."); gx = Interlocked.Increment(ref m_gx_next) - 1; } return gx; } /// <summary> /// Add an entry index to one of the the freelists /// </summary> internal void ReleaseIndex(ref int mtid, int gx) { Freelist fl_use; #if newthing fl_use = GetFreelist(ref mtid); #else if (mtid == -1) mtid = Thread.CurrentThread.ManagedThreadId % free_lists.Length; fl_use = free_lists[mtid]; #endif fl_use.AddIndexToFreeList(gx); } internal Entry[] GetBlock(int gx, out int offs) { int i_block = (gx >> EntriesPerBlockPower); offs = gx & GlobalIndexMask; Entry[] eb = entry_blocks[i_block]; if (eb == null) { Entry[][] rgeb_loc = entry_blocks; Thread.MemoryBarrier(); if (i_block == rgeb_loc.Length - 1) { Entry[][] rgeb_new = new Entry[rgeb_loc.Length + 0x10][]; rgeb_loc.CopyTo(rgeb_new, 0); var t1 = Interlocked.CompareExchange(ref entry_blocks, rgeb_new, rgeb_loc); rgeb_loc = (t1 == rgeb_loc) ? rgeb_new : t1; } eb = rgeb_loc[i_block]; if (eb == null) { Entry[] eb_new = new Entry[EntriesPerBlock]; eb = Interlocked.CompareExchange(ref entry_blocks[i_block], eb_new, null) ?? eb_new; } } #if false else if (offs >= ResizeTrigger) { int mtid = Thread.CurrentThread.ManagedThreadId % free_lists.Length; } #endif return eb; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public partial struct Entry { /// to merit 'beforefieldinit' semantics, we cannot have a static constructor and must do this all inline static Type t_key = typeof(K); static Type t_value = typeof(V); static internal bool f_value_type_key = t_key.IsValueType; static internal bool f_value_type_value = t_value.IsValueType; static internal bool f_key_null_check = !f_value_type_key || Nullable.GetUnderlyingType(t_key) != null; static internal int StructureSize { get { return cb_ent; } } static int cb_ent = sizeof(int) + sizeof(uint) + (f_value_type_key ? Marshal.SizeOf(default(K)) : IntPtr.Size) + (f_value_type_value ? Marshal.SizeOf(default(V)) : IntPtr.Size); internal int gx_next; internal uint hash; internal K key; internal V value; static internal bool CompareValues(V v1, V v2) { return (!f_value_type_value && v1 == null) ? v2 == null : v1.Equals(v2); } }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Lock-free LIFO stack /// </summary> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public partial class Freelist { public Freelist(Config cfg) { this.cfg = cfg; this.m_head = unchecked((UInt32)FreeListTerminator); this.p2 = new FalseSharingPadding(); } internal const Int32 FreelistHash = 0; internal const Int32 FreeListTerminator = unchecked((Int32)0xFFFFFFFE); const Int64 SequenceMask = unchecked((Int64)0xFFFFFFFF00000000); const Int64 SequenceIncrement = 0x0000000100000000; Config cfg; FalseSharingPadding p2; Int64 m_head; /// <summary> /// Add an item to the free list /// </summary> public void AddIndexToFreeList(int gx_add) { Int64 new_head, head = Interlocked.CompareExchange(ref m_head, 0, 0); int offs; Entry[] rg = cfg.GetBlock(gx_add, out offs); rg[offs] = default(Entry); // permit GC release of reference types, set Entry.FreelistHash while (true) { rg[offs].gx_next = (Int32)head; new_head = ((head + SequenceIncrement) & SequenceMask) | (UInt32)gx_add; if ((new_head = Interlocked.CompareExchange(ref m_head, new_head, head)) == head) return; head = new_head; } } /// <summary> /// Pick the first item from the free list. /// </summary> /// <returns><value>true</value> if an free index is returned in <paramref name="gx_out"/>, /// <value>false</value> otherwise.</returns> public bool TryGetFreeIndex(out int gx_out) { Int64 same_head, new_head, head = Interlocked.CompareExchange(ref m_head, 0, 0); while (true) { int offs, gx_next; while (true) { gx_out = (Int32)head; if (gx_out == FreeListTerminator) { gx_out = -1; return false; } gx_next = cfg.GetBlock(gx_out, out offs)[offs].gx_next; /// acquire frence: read of gx_next must occur prior to the read of m_head in this.Head. Thread.MemoryBarrier(); if (head == (same_head = Interlocked.CompareExchange(ref m_head, 0, 0))) break; head = same_head; } new_head = ((head + SequenceIncrement) & SequenceMask) | (UInt32)gx_next; if ((new_head = Interlocked.CompareExchange(ref m_head, new_head, head)) == head) return true; head = new_head; } } }; }; }; [StructLayout(LayoutKind.Explicit, Size = 2000)] internal struct FalseSharingPadding { }