├── NativeCollection ├── .idea │ └── .idea.NativeCollection │ │ └── .idea │ │ ├── vcs.xml │ │ ├── indexLayout.xml │ │ └── .gitignore ├── NativeCollection │ ├── INativeCollectionClass.cs │ ├── NativeCollection.csproj │ ├── UnsafeType │ │ ├── Map │ │ │ ├── MapPair.cs │ │ │ └── Map.cs │ │ ├── NativeStackPool.cs │ │ ├── MultiMap │ │ │ ├── MultiMapPair.cs │ │ │ └── MultiMap.cs │ │ ├── HashHelpers.cs │ │ ├── Stack.cs │ │ ├── Queue.cs │ │ ├── List.cs │ │ ├── SortedSet │ │ │ └── Node.cs │ │ ├── HashSet.cs │ │ └── UnOrderMap │ │ │ └── UnOrderMap.cs │ ├── NativePool.cs │ ├── Stack.cs │ ├── Queue.cs │ ├── HashSet.cs │ ├── Map.cs │ ├── SortedSet.cs │ ├── MultiMap.cs │ ├── ThrowHelper.cs │ ├── NativeMemoryHelper.cs │ ├── UnOrderMap.cs │ ├── List.cs │ └── FixedSizeMemoryPool │ │ ├── FixedSizeMemoryPool.cs │ │ └── Slab.cs ├── Benchmark │ ├── Program.cs │ ├── Benchmark.csproj │ └── Benchmarks │ │ ├── BenchmarkMemoryPool.cs │ │ ├── BenchmarkHashSet.cs │ │ ├── BenchmarkSortedSet.cs │ │ ├── BenchmarkMap.cs │ │ ├── BenchmarkUnOrderMap.cs │ │ └── BenchmarkMultiMap.cs ├── NativeCollection.Test │ ├── NativeCollection.Test.csproj │ ├── MultiMapTest.cs │ ├── UnOrderMapTest.cs │ ├── MapTest.cs │ ├── QueueTest.cs │ ├── HashSetTest.cs │ ├── StackTest.cs │ ├── SortedSetTest.cs │ └── ListTest.cs ├── MemoryProfile │ ├── MemoryProfile.csproj │ └── MemoryLeakTest.cs └── NativeCollection.sln ├── LICENSE ├── .gitignore └── README.md /NativeCollection/.idea/.idea.NativeCollection/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /NativeCollection/.idea/.idea.NativeCollection/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/INativeCollectionClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NativeCollection 4 | { 5 | public interface INativeCollectionClass : IDisposable 6 | { 7 | void ReInit(); 8 | 9 | bool IsDisposed { get; } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /NativeCollection/.idea/.idea.NativeCollection/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /contentModel.xml 7 | /.idea.NativeCollection.iml 8 | /projectSettingsUpdater.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/NativeCollection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 5 | true 6 | net7.0 7 | enable 8 | 9 | 10 | 11 | 12 | TRACE; 13 | 14 | 15 | -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using System.Diagnostics; 4 | using Benchmark.Benchmarks; 5 | using BenchmarkDotNet.Running; 6 | using NativeCollection; 7 | using NativeCollection.UnsafeType; 8 | 9 | public static class Program 10 | { 11 | public static void Main() 12 | { 13 | //var summary = BenchmarkRunner.Run(typeof(Program).Assembly); 14 | var summary = BenchmarkRunner.Run(); 15 | 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/NativeCollection.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 susices 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/Map/MapPair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection.UnsafeType 5 | { 6 | public unsafe struct MapPair : IEquatable>, IComparable> 7 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 8 | { 9 | public T Key { get; private set; } 10 | public K Value => _value; 11 | 12 | internal K _value; 13 | 14 | public MapPair(T key,K value = default) 15 | { 16 | Key = key; 17 | _value = value; 18 | } 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public bool Equals(MapPair other) 21 | { 22 | return Key.Equals(other.Key); 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public int CompareTo(MapPair other) 27 | { 28 | return Key.CompareTo(other.Key); 29 | } 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public override int GetHashCode() 33 | { 34 | return Key.GetHashCode(); 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /NativeCollection/MemoryProfile/MemoryProfile.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | TRACE;MEMORY_PROFILE 13 | 14 | 15 | 16 | TRACE;MEMORY_PROFILE 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | NativeCollection/%(RecursiveDir)%(FileName)%(Extension) 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkMemoryPool.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using BenchmarkDotNet.Attributes; 3 | using BenchmarkDotNet.Configs; 4 | using NativeCollection; 5 | using NativeCollection.UnsafeType; 6 | 7 | namespace Benchmark.Benchmarks; 8 | [ShortRunJob] 9 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 10 | [CategoriesColumn] 11 | public class BenchmarkMemoryPool 12 | { 13 | [BenchmarkCategory("Alloc")] 14 | [Benchmark] 15 | public void MemoryPoolAlloc() 16 | { 17 | unsafe 18 | { 19 | FixedSizeMemoryPool* memoryPool =FixedSizeMemoryPool.Create(32, 32); 20 | for (int i = 0; i < 10000; i++) 21 | { 22 | var ptr = memoryPool->Alloc(); 23 | int* value = (int*)ptr; 24 | *value = i; 25 | } 26 | 27 | memoryPool->Dispose(); 28 | } 29 | } 30 | 31 | [BenchmarkCategory("Alloc")] 32 | [Benchmark(Baseline = true)] 33 | public void NativeMemoryAlloc() 34 | { 35 | unsafe 36 | { 37 | for (int i = 0; i < 10000; i++) 38 | { 39 | var ptr = NativeMemory.Alloc(32); 40 | int* value = (int*)ptr; 41 | *value = i; 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/NativePool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using NativeCollection.UnsafeType; 4 | 5 | namespace NativeCollection 6 | { 7 | public unsafe class NativePool : INativeCollectionClass where T: unmanaged,IEquatable,IPool 8 | { 9 | private UnsafeType.NativeStackPool* _nativePool; 10 | private const int _defaultPoolSize = 200; 11 | private int _poolSize; 12 | public NativePool(int maxPoolSize = _defaultPoolSize) 13 | { 14 | _poolSize = maxPoolSize; 15 | _nativePool = UnsafeType.NativeStackPool.Create(_poolSize); 16 | IsDisposed = false; 17 | } 18 | 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public T* Alloc() 21 | { 22 | return _nativePool->Alloc(); 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public void Return(T* ptr) 27 | { 28 | _nativePool->Return(ptr); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | if (IsDisposed) 34 | { 35 | return; 36 | } 37 | if (_nativePool != null) 38 | { 39 | _nativePool->Dispose(); 40 | NativeMemoryHelper.Free(_nativePool); 41 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 42 | IsDisposed = true; 43 | } 44 | } 45 | 46 | public void ReInit() 47 | { 48 | if (IsDisposed) 49 | { 50 | _nativePool = UnsafeType.NativeStackPool.Create(_poolSize); 51 | IsDisposed = false; 52 | } 53 | } 54 | 55 | public bool IsDisposed { get; private set; } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | [Bb]in/ 15 | [Oo]bj/ 16 | 17 | # MSTest test Results 18 | [Tt]est[Rr]esult*/ 19 | [Bb]uild[Ll]og.* 20 | 21 | *_i.c 22 | *_p.c 23 | *_i.h 24 | *.ilk 25 | *.meta 26 | *.obj 27 | *.pch 28 | *.pdb 29 | *.pgc 30 | *.pgd 31 | *.rsp 32 | *.sbr 33 | *.tlb 34 | *.tli 35 | *.tlh 36 | *.tmp 37 | *.tmp_proj 38 | *.log 39 | *.vspscc 40 | *.vssscc 41 | .builds 42 | *.pidb 43 | *.log 44 | *.svclog 45 | *.scc 46 | 47 | # Visual C++ cache files 48 | ipch/ 49 | *.aps 50 | *.ncb 51 | *.opensdf 52 | *.sdf 53 | *.cachefile 54 | 55 | # Visual Studio profiler 56 | *.psess 57 | *.vsp 58 | *.vspx 59 | 60 | # Guidance Automation Toolkit 61 | *.gpState 62 | 63 | # ReSharper is a .NET coding add-in 64 | _ReSharper*/ 65 | *.[Rr]e[Ss]harper 66 | *.DotSettings.user 67 | 68 | # Click-Once directory 69 | publish/ 70 | 71 | # Publish Web Output 72 | *.Publish.xml 73 | *.pubxml 74 | *.azurePubxml 75 | 76 | # NuGet Packages Directory 77 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 78 | packages/ 79 | ## TODO: If the tool you use requires repositories.config, also uncomment the next line 80 | !packages/repositories.config 81 | 82 | # Windows Azure Build Output 83 | csx/ 84 | *.build.csdef 85 | 86 | # Windows Store app package directory 87 | AppPackages/ 88 | 89 | # Others 90 | sql/ 91 | *.Cache 92 | ClientBin/ 93 | [Ss]tyle[Cc]op.* 94 | ![Ss]tyle[Cc]op.targets 95 | ~$* 96 | *~ 97 | *.dbmdl 98 | *.[Pp]ublish.xml 99 | 100 | *.publishsettings 101 | 102 | # RIA/Silverlight projects 103 | Generated_Code/ 104 | 105 | # Backup & report files from converting an old project file to a newer 106 | # Visual Studio version. Backup files are not needed, because we have git ;-) 107 | _UpgradeReport_Files/ 108 | Backup*/ 109 | UpgradeLog*.XML 110 | UpgradeLog*.htm 111 | 112 | # SQL Server files 113 | App_Data/*.mdf 114 | App_Data/*.ldf 115 | 116 | # ========================= 117 | # Windows detritus 118 | # ========================= 119 | 120 | # Windows image file caches 121 | Thumbs.db 122 | ehthumbs.db 123 | 124 | # Folder config file 125 | Desktop.ini 126 | 127 | # Recycle Bin used on file shares 128 | $RECYCLE.BIN/ 129 | 130 | # Mac desktop service store files 131 | .DS_Store 132 | 133 | _NCrunch* 134 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/MultiMapTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class MultiMapTest 7 | { 8 | [Fact] 9 | public void AddRemove() 10 | { 11 | MultiMap multiMap = new MultiMap(); 12 | multiMap.Add(1,11); 13 | multiMap.Count.Should().Be(1); 14 | multiMap.Add(1,12); 15 | multiMap.Count.Should().Be(1); 16 | multiMap.Add(3,44); 17 | multiMap.Count.Should().Be(2); 18 | multiMap.Remove(3, 44).Should().Be(true); 19 | multiMap.Count.Should().Be(1); 20 | multiMap.Clear(); 21 | multiMap.Count.Should().Be(0); 22 | multiMap.Remove(1).Should().Be(false); 23 | multiMap.Remove(1, 4).Should().Be(false); 24 | 25 | multiMap.Clear(); 26 | for (int i = 0; i < 1000; i++) 27 | { 28 | multiMap.Add(1,Random.Shared.Next()); 29 | } 30 | multiMap.Count.Should().Be(1); 31 | } 32 | 33 | [Fact] 34 | public void Enumerator() 35 | { 36 | SortedDictionary sortedDictionary = new SortedDictionary(); 37 | MultiMap multiMap = new MultiMap(); 38 | 39 | for (int i = 0; i < 1000; i++) 40 | { 41 | int value = Random.Shared.Next(); 42 | sortedDictionary.Add(value,1); 43 | multiMap.Add(value,1); 44 | } 45 | 46 | var MultiMapEnumerator = multiMap.GetEnumerator(); 47 | var sortedDictionaryEnumerator = sortedDictionary.GetEnumerator(); 48 | while (sortedDictionaryEnumerator.MoveNext()) 49 | { 50 | MultiMapEnumerator.MoveNext(); 51 | int key = sortedDictionaryEnumerator.Current.Key; 52 | MultiMapEnumerator.Current.Key.Should().Be(key); 53 | } 54 | } 55 | 56 | 57 | [Fact] 58 | public void NativeCollectionClass() 59 | { 60 | MultiMap multiMap = new MultiMap(); 61 | multiMap.IsDisposed.Should().Be(false); 62 | for (int i = 0; i < 100; i++) 63 | { 64 | multiMap.Add(Random.Shared.Next(),1); 65 | } 66 | multiMap.Dispose(); 67 | multiMap.IsDisposed.Should().Be(true); 68 | multiMap.ReInit(); 69 | multiMap.IsDisposed.Should().Be(false); 70 | multiMap.Count.Should().Be(0); 71 | for (int i = 0; i < 100; i++) 72 | { 73 | multiMap.Add(Random.Shared.Next(),1); 74 | } 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/NativeStackPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection.UnsafeType 5 | { 6 | public interface IPool : IDisposable 7 | { 8 | public void OnReturnToPool(); 9 | public void OnGetFromPool(); 10 | } 11 | 12 | public unsafe struct NativeStackPool : IDisposable where T: unmanaged,IPool 13 | { 14 | public int MaxSize { get; private set; } 15 | private Stack* _stack; 16 | public static NativeStackPool* Create(int maxPoolSize) 17 | { 18 | NativeStackPool* pool = (NativeStackPool*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 19 | pool->_stack = Stack.Create(); 20 | pool->MaxSize = maxPoolSize; 21 | return pool; 22 | } 23 | 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public T* Alloc() 26 | { 27 | if (_stack->TryPop(out var itemPtr)) 28 | { 29 | var item = (T*)itemPtr; 30 | item->OnGetFromPool(); 31 | return item; 32 | } 33 | return null; 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public void Return(T* ptr) 38 | { 39 | if (_stack->Count>=MaxSize) 40 | { 41 | ptr->Dispose(); 42 | NativeMemoryHelper.Free(ptr); 43 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()); 44 | return; 45 | } 46 | ptr->OnReturnToPool(); 47 | _stack->Push(new IntPtr(ptr)); 48 | } 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public bool IsPoolMax() 52 | { 53 | return _stack->Count >= MaxSize; 54 | } 55 | 56 | public void Clear() 57 | { 58 | while (_stack->TryPop(out var ptr)) 59 | { 60 | T* item = (T*)ptr; 61 | item->Dispose(); 62 | NativeMemoryHelper.Free(item); 63 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()); 64 | } 65 | } 66 | 67 | public void Dispose() 68 | { 69 | Clear(); 70 | _stack->Dispose(); 71 | NativeMemoryHelper.Free(_stack); 72 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 73 | } 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/Stack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection 5 | { 6 | public unsafe class Stack : INativeCollectionClass where T : unmanaged 7 | { 8 | private const int _defaultCapacity = 10; 9 | private UnsafeType.Stack* _stack; 10 | private int _capacity; 11 | public Stack(int initialCapacity = _defaultCapacity) 12 | { 13 | _capacity = initialCapacity; 14 | _stack = UnsafeType.Stack.Create(_capacity); 15 | IsDisposed = false; 16 | } 17 | 18 | public int Count => _stack->Count; 19 | 20 | public void Dispose() 21 | { 22 | if (IsDisposed) 23 | { 24 | return; 25 | } 26 | if (_stack != null) 27 | { 28 | _stack->Dispose(); 29 | NativeMemoryHelper.Free(_stack); 30 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 31 | IsDisposed = true; 32 | } 33 | } 34 | 35 | public void Clear() 36 | { 37 | _stack->Clear(); 38 | } 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public bool Contains(in T obj) 42 | { 43 | return _stack->Contains(obj); 44 | } 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public T Peak() 48 | { 49 | return _stack->Peak(); 50 | } 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 53 | public T Pop() 54 | { 55 | return _stack->Pop(); 56 | } 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public bool TryPop(out T result) 60 | { 61 | var returnValue = _stack->TryPop(out result); 62 | return returnValue; 63 | } 64 | 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public void Push(in T obj) 67 | { 68 | _stack->Push(obj); 69 | } 70 | 71 | ~Stack() 72 | { 73 | Dispose(); 74 | } 75 | 76 | public void ReInit() 77 | { 78 | if (IsDisposed) 79 | { 80 | _stack = UnsafeType.Stack.Create(_capacity); 81 | IsDisposed = false; 82 | } 83 | } 84 | 85 | public bool IsDisposed { get; private set; } 86 | } 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/MultiMap/MultiMapPair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection.UnsafeType 5 | { 6 | public unsafe struct MultiMapPair : IEquatable>, IComparable> 7 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 8 | { 9 | private UnsafeType.List* _value; 10 | 11 | public T Key { get; private set; } 12 | 13 | public ref UnsafeType.List Value 14 | { 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | get { return ref Unsafe.AsRef>(_value); } 17 | } 18 | 19 | public MultiMapPair(T key) 20 | { 21 | Key = key; 22 | _value = null; 23 | } 24 | 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static MultiMapPair Create(in T key, FixedSizeMemoryPool* pool, NativeStackPool>* stackPool) 27 | { 28 | var pair = new MultiMapPair(key); 29 | List* list = stackPool->Alloc(); 30 | if (list==null) 31 | { 32 | list = List.AllocFromMemoryPool(pool); 33 | } 34 | pair._value = list; 35 | return pair; 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public bool Equals(MultiMapPair other) 40 | { 41 | return Key.Equals(other.Key); 42 | } 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public int CompareTo(MultiMapPair other) 46 | { 47 | return Key.CompareTo(other.Key); 48 | } 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public override int GetHashCode() 52 | { 53 | return Key.GetHashCode(); 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public void Dispose(FixedSizeMemoryPool* pool,NativeStackPool>* stackPool) 58 | { 59 | if (_value!=null) 60 | { 61 | if (!stackPool->IsPoolMax()) 62 | { 63 | stackPool->Return(_value); 64 | _value = null; 65 | } 66 | else 67 | { 68 | _value->Dispose(); 69 | pool->Free(_value); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/UnOrderMapTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class UnOrderMapTest 7 | { 8 | [Fact] 9 | public void AddRemove() 10 | { 11 | UnOrderMap unOrderMap = new UnOrderMap(); 12 | unOrderMap.Add(1,11); 13 | unOrderMap.Count.Should().Be(1); 14 | unOrderMap[1].Should().Be(11); 15 | unOrderMap.Count.Should().Be(1); 16 | unOrderMap[1].Should().Be(11); 17 | unOrderMap.Add(3,44); 18 | unOrderMap.Count.Should().Be(2); 19 | unOrderMap.Remove(3).Should().Be(true); 20 | unOrderMap.Count.Should().Be(1); 21 | unOrderMap.Clear(); 22 | unOrderMap.Count.Should().Be(0); 23 | unOrderMap.Remove(1).Should().Be(false); 24 | unOrderMap.Clear(); 25 | 26 | unOrderMap.Clear(); 27 | unOrderMap.Add(1,100); 28 | unOrderMap[1].Should().Be(100); 29 | unOrderMap[1] = 101; 30 | unOrderMap[1].Should().Be(101); 31 | unOrderMap[2] = 102; 32 | unOrderMap[2].Should().Be(102); 33 | } 34 | 35 | [Fact] 36 | public void Enumerator() 37 | { 38 | Dictionary dictionary = new Dictionary(); 39 | UnOrderMap unOrderMap = new UnOrderMap(); 40 | 41 | for (int i = 0; i < 1000; i++) 42 | { 43 | int value = Random.Shared.Next(); 44 | dictionary.Add(value,1); 45 | unOrderMap.Add(value,1); 46 | } 47 | 48 | using var mapEnumerator = unOrderMap.GetEnumerator(); 49 | using var sortedDictionaryEnumerator = dictionary.GetEnumerator(); 50 | while (sortedDictionaryEnumerator.MoveNext()) 51 | { 52 | mapEnumerator.MoveNext(); 53 | int key = sortedDictionaryEnumerator.Current.Key; 54 | mapEnumerator.Current.Key.Should().Be(key); 55 | } 56 | } 57 | 58 | [Fact] 59 | public void NativeCollectionClass() 60 | { 61 | UnOrderMap unOrderMap = new UnOrderMap(); 62 | unOrderMap.IsDisposed.Should().Be(false); 63 | for (int i = 0; i < 100; i++) 64 | { 65 | unOrderMap.Add(Random.Shared.Next(),1); 66 | } 67 | unOrderMap.Dispose(); 68 | unOrderMap.IsDisposed.Should().Be(true); 69 | unOrderMap.ReInit(); 70 | unOrderMap.IsDisposed.Should().Be(false); 71 | unOrderMap.Count.Should().Be(0); 72 | for (int i = 0; i < 100; i++) 73 | { 74 | unOrderMap.Add(Random.Shared.Next(),1); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/Queue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection 5 | { 6 | public unsafe class Queue : INativeCollectionClass where T : unmanaged 7 | { 8 | private UnsafeType.Queue* _queue; 9 | private const int _defaultCapacity = 10; 10 | private int _capacity; 11 | public Queue(int capacity = 10) 12 | { 13 | _capacity = capacity; 14 | _queue = UnsafeType.Queue.Create(_capacity); 15 | IsDisposed = false; 16 | } 17 | 18 | public int Count => _queue->Count; 19 | 20 | public void Dispose() 21 | { 22 | if (IsDisposed) 23 | { 24 | return; 25 | } 26 | if (_queue != null) 27 | { 28 | _queue->Dispose(); 29 | NativeMemoryHelper.Free(_queue); 30 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 31 | IsDisposed = true; 32 | } 33 | } 34 | 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public void Clear() 37 | { 38 | _queue->Clear(); 39 | } 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | public void Enqueue(in T item) 43 | { 44 | _queue->Enqueue(item); 45 | } 46 | 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public T Dequeue() 49 | { 50 | return _queue->Dequeue(); 51 | } 52 | 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | public bool TryDequeue(out T result) 55 | { 56 | var value = _queue->TryDequeue(out result); 57 | return value; 58 | } 59 | 60 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 61 | public T Peek() 62 | { 63 | return _queue->Peek(); 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public bool TryPeek(out T result) 68 | { 69 | var value = _queue->TryPeek(out result); 70 | return value; 71 | } 72 | 73 | ~Queue() 74 | { 75 | Dispose(); 76 | } 77 | 78 | public void ReInit() 79 | { 80 | if (IsDisposed) 81 | { 82 | _queue = UnsafeType.Queue.Create(_capacity); 83 | IsDisposed = false; 84 | } 85 | } 86 | 87 | public bool IsDisposed { get; private set; } 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/HashSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace NativeCollection 7 | { 8 | public unsafe class HashSet: ICollection, INativeCollectionClass where T : unmanaged, IEquatable 9 | { 10 | 11 | private UnsafeType.HashSet* _hashSet; 12 | private const int _defaultCapacity = 10; 13 | public HashSet(int capacity = _defaultCapacity) 14 | { 15 | _hashSet = UnsafeType.HashSet.Create(capacity); 16 | IsDisposed = false; 17 | } 18 | IEnumerator IEnumerable.GetEnumerator() 19 | { 20 | return GetEnumerator(); 21 | } 22 | 23 | IEnumerator IEnumerable.GetEnumerator() 24 | { 25 | return GetEnumerator(); 26 | } 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public UnsafeType.HashSet.Enumerator GetEnumerator() => new UnsafeType.HashSet.Enumerator(_hashSet); 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public void Add(T item) 33 | { 34 | _hashSet->Add(item); 35 | } 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public void Clear() 39 | { 40 | _hashSet->Clear(); 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public bool Contains(T item) 45 | { 46 | return _hashSet->Contains(item); 47 | } 48 | 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public void CopyTo(T[] array, int arrayIndex) 51 | { 52 | _hashSet->CopyTo(array, arrayIndex); 53 | } 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public bool Remove(T item) 57 | { 58 | return _hashSet->Remove(item); 59 | } 60 | 61 | public int Count => _hashSet->Count; 62 | public bool IsReadOnly => false; 63 | 64 | public void Dispose() 65 | { 66 | if (IsDisposed) 67 | { 68 | return; 69 | } 70 | if (_hashSet!=null) 71 | { 72 | _hashSet->Dispose(); 73 | NativeMemoryHelper.Free(_hashSet); 74 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 75 | IsDisposed = true; 76 | } 77 | } 78 | 79 | public void ReInit() 80 | { 81 | if (IsDisposed) 82 | { 83 | _hashSet = UnsafeType.HashSet.Create(_defaultCapacity); 84 | IsDisposed = false; 85 | } 86 | } 87 | 88 | public bool IsDisposed { get; private set; } 89 | 90 | ~HashSet() 91 | { 92 | Dispose(); 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/MapTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class MapTest 7 | { 8 | [Fact] 9 | public void AddRemove() 10 | { 11 | Map map = new Map(); 12 | map.Add(1,11); 13 | map.Count.Should().Be(1); 14 | map[1].Should().Be(11); 15 | map.Add(1,12); 16 | map.Count.Should().Be(1); 17 | map[1].Should().Be(11); 18 | map.Add(3,44); 19 | map.Count.Should().Be(2); 20 | map.Remove(3).Should().Be(true); 21 | map.Count.Should().Be(1); 22 | map.Clear(); 23 | map.Count.Should().Be(0); 24 | map.Remove(1).Should().Be(false); 25 | map.Clear(); 26 | for (int i = 0; i < 1000; i++) 27 | { 28 | map.Add(1,Random.Shared.Next()); 29 | } 30 | map.Count.Should().Be(1); 31 | 32 | map.Clear(); 33 | map.Add(1,100); 34 | map[1].Should().Be(100); 35 | map[1] = 101; 36 | map[1].Should().Be(101); 37 | map[2] = 102; 38 | map[2].Should().Be(102); 39 | SortedDictionary sortedDictionary = new SortedDictionary(); 40 | sortedDictionary.Add(1,100); 41 | sortedDictionary[1] = 101; 42 | sortedDictionary[1].Should().Be(101); 43 | } 44 | 45 | [Fact] 46 | public void Enumerator() 47 | { 48 | SortedDictionary sortedDictionary = new SortedDictionary(); 49 | Map map = new Map(); 50 | 51 | for (int i = 0; i < 1000; i++) 52 | { 53 | int value = Random.Shared.Next(); 54 | sortedDictionary.Add(value,1); 55 | map.Add(value,1); 56 | } 57 | 58 | using var mapEnumerator = map.GetEnumerator(); 59 | using var sortedDictionaryEnumerator = sortedDictionary.GetEnumerator(); 60 | while (sortedDictionaryEnumerator.MoveNext()) 61 | { 62 | mapEnumerator.MoveNext(); 63 | int key = sortedDictionaryEnumerator.Current.Key; 64 | mapEnumerator.Current.Key.Should().Be(key); 65 | } 66 | } 67 | 68 | [Fact] 69 | public void NativeCollectionClass() 70 | { 71 | Map map = new Map(); 72 | map.IsDisposed.Should().Be(false); 73 | for (int i = 0; i < 100; i++) 74 | { 75 | map.Add(Random.Shared.Next(),1); 76 | } 77 | map.Dispose(); 78 | map.IsDisposed.Should().Be(true); 79 | map.ReInit(); 80 | map.IsDisposed.Should().Be(false); 81 | map.Count.Should().Be(0); 82 | for (int i = 0; i < 100; i++) 83 | { 84 | map.Add(Random.Shared.Next(),1); 85 | } 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/Map.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using NativeCollection.UnsafeType; 6 | 7 | namespace NativeCollection 8 | { 9 | public unsafe class Map : IEnumerable>, INativeCollectionClass 10 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 11 | { 12 | private const int _defaultPoolBlockSize = 64; 13 | private int _poolBlockSize; 14 | private UnsafeType.Map* _map; 15 | 16 | public Map(int poolBlockSize = _defaultPoolBlockSize) 17 | { 18 | _poolBlockSize = poolBlockSize; 19 | _map = UnsafeType.Map.Create(_poolBlockSize); 20 | IsDisposed = false; 21 | } 22 | 23 | public K this[T key] 24 | { 25 | get => (*_map)[key]; 26 | set => (*_map)[key] = value; 27 | } 28 | 29 | public int Count => _map->Count; 30 | 31 | IEnumerator> IEnumerable>.GetEnumerator() 32 | { 33 | return GetEnumerator(); 34 | } 35 | 36 | IEnumerator IEnumerable.GetEnumerator() 37 | { 38 | return GetEnumerator(); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | if (IsDisposed) return; 44 | if (_map != null) 45 | { 46 | _map->Dispose(); 47 | NativeMemoryHelper.Free(_map); 48 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 49 | IsDisposed = true; 50 | } 51 | } 52 | 53 | public void ReInit() 54 | { 55 | if (IsDisposed) 56 | { 57 | _map = UnsafeType.Map.Create(_poolBlockSize); 58 | IsDisposed = false; 59 | } 60 | } 61 | 62 | public bool IsDisposed { get; private set; } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public void Add(T key, K value) 66 | { 67 | _map->Add(key, value); 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public bool Remove(T key) 72 | { 73 | return _map->Remove(key); 74 | } 75 | 76 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 | public void Clear() 78 | { 79 | _map->Clear(); 80 | } 81 | 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | public UnsafeType.SortedSet>.Enumerator GetEnumerator() 84 | { 85 | return _map->GetEnumerator(); 86 | } 87 | 88 | ~Map() 89 | { 90 | Dispose(); 91 | } 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/SortedSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace NativeCollection 7 | { 8 | public unsafe class SortedSet : ICollection, INativeCollectionClass where T : unmanaged, IEquatable,IComparable 9 | { 10 | private UnsafeType.SortedSet* _sortedSet; 11 | private const int _defaultNodePoolBlockSize = 64; 12 | private int _poolBlockSize; 13 | public SortedSet(int nodePoolSize = _defaultNodePoolBlockSize) 14 | { 15 | _poolBlockSize = nodePoolSize; 16 | _sortedSet = UnsafeType.SortedSet.Create(_poolBlockSize); 17 | IsDisposed = false; 18 | } 19 | IEnumerator IEnumerable.GetEnumerator() 20 | { 21 | return GetEnumerator(); 22 | } 23 | 24 | IEnumerator IEnumerable.GetEnumerator() 25 | { 26 | return GetEnumerator(); 27 | } 28 | 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public UnsafeType.SortedSet.Enumerator GetEnumerator() 31 | { 32 | return new UnsafeType.SortedSet.Enumerator(_sortedSet); 33 | } 34 | 35 | void ICollection.Add(T item) 36 | { 37 | _sortedSet->Add(item); 38 | } 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public bool Add(T item) 42 | { 43 | return _sortedSet->Add(item); 44 | } 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public void Clear() 48 | { 49 | _sortedSet->Clear(); 50 | } 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 53 | public bool Contains(T item) 54 | { 55 | return _sortedSet->Contains(item); 56 | } 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public void CopyTo(T[] array, int arrayIndex) 60 | { 61 | _sortedSet->CopyTo(array,arrayIndex); 62 | } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public bool Remove(T item) 66 | { 67 | return _sortedSet->Remove(item); 68 | } 69 | 70 | public T? Min => _sortedSet->Min; 71 | 72 | public T? Max => _sortedSet->Max; 73 | 74 | public int Count => _sortedSet->Count; 75 | public bool IsReadOnly => false; 76 | public void Dispose() 77 | { 78 | if (IsDisposed) 79 | { 80 | return; 81 | } 82 | if (_sortedSet!=null) 83 | { 84 | _sortedSet->Dispose(); 85 | NativeMemoryHelper.Free(_sortedSet); 86 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 87 | IsDisposed = true; 88 | } 89 | } 90 | 91 | public void ReInit() 92 | { 93 | if (IsDisposed) 94 | { 95 | _sortedSet = UnsafeType.SortedSet.Create(_poolBlockSize); 96 | IsDisposed = false; 97 | } 98 | } 99 | 100 | public bool IsDisposed { get; private set; } 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeCollection", "NativeCollection\NativeCollection.csproj", "{905BB42C-1200-4233-9236-B83E966E29B7}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "Benchmark\Benchmark.csproj", "{3A5D66D6-2B33-4E99-86C2-92248472758C}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5AE8E136-E26F-4D78-A185-3A7C17A06531}" 8 | EndProject 9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{51879ABF-A076-4EBB-9B55-814F5EF1F571}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeCollection.Test", "NativeCollection.Test\NativeCollection.Test.csproj", "{9AAD09E7-B5FB-4715-A23D-519F0F7D8B61}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{0408D84C-B09D-4325-9031-2634446A0BFB}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemoryProfile", "MemoryProfile\MemoryProfile.csproj", "{4291EA34-4635-4EA5-8982-369707D3D8F4}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {905BB42C-1200-4233-9236-B83E966E29B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {905BB42C-1200-4233-9236-B83E966E29B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {905BB42C-1200-4233-9236-B83E966E29B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {905BB42C-1200-4233-9236-B83E966E29B7}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {3A5D66D6-2B33-4E99-86C2-92248472758C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {3A5D66D6-2B33-4E99-86C2-92248472758C}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {3A5D66D6-2B33-4E99-86C2-92248472758C}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {3A5D66D6-2B33-4E99-86C2-92248472758C}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {9AAD09E7-B5FB-4715-A23D-519F0F7D8B61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {9AAD09E7-B5FB-4715-A23D-519F0F7D8B61}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {9AAD09E7-B5FB-4715-A23D-519F0F7D8B61}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {9AAD09E7-B5FB-4715-A23D-519F0F7D8B61}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {4291EA34-4635-4EA5-8982-369707D3D8F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {4291EA34-4635-4EA5-8982-369707D3D8F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {4291EA34-4635-4EA5-8982-369707D3D8F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {4291EA34-4635-4EA5-8982-369707D3D8F4}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {905BB42C-1200-4233-9236-B83E966E29B7} = {5AE8E136-E26F-4D78-A185-3A7C17A06531} 42 | {9AAD09E7-B5FB-4715-A23D-519F0F7D8B61} = {51879ABF-A076-4EBB-9B55-814F5EF1F571} 43 | {3A5D66D6-2B33-4E99-86C2-92248472758C} = {0408D84C-B09D-4325-9031-2634446A0BFB} 44 | {4291EA34-4635-4EA5-8982-369707D3D8F4} = {51879ABF-A076-4EBB-9B55-814F5EF1F571} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/QueueTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class QueueTest 7 | { 8 | [Fact] 9 | public void EnqueueDequeue() 10 | { 11 | System.Collections.Generic.Queue managedQueue = new System.Collections.Generic.Queue(); 12 | Queue nativeQueue = new Queue(); 13 | 14 | for (int i = 0; i < 1000; i++) 15 | { 16 | int value = Random.Shared.Next(); 17 | managedQueue.Enqueue(value); 18 | nativeQueue.Enqueue(value); 19 | nativeQueue.Count.Should().Be(i + 1); 20 | } 21 | 22 | for (int i = 0; i < 1000; i++) 23 | { 24 | managedQueue.Dequeue().Should().Be(nativeQueue.Dequeue()); 25 | nativeQueue.Count.Should().Be(1000 - i - 1); 26 | } 27 | 28 | bool hasException = false; 29 | 30 | try 31 | { 32 | nativeQueue.Dequeue(); 33 | } 34 | catch (Exception e) 35 | { 36 | hasException = true; 37 | } 38 | 39 | hasException.Should().Be(true); 40 | } 41 | 42 | [Fact] 43 | public void Peak() 44 | { 45 | Queue nativeQueue = new Queue(); 46 | System.Collections.Generic.Queue managedQueue = new System.Collections.Generic.Queue(); 47 | 48 | for (int i = 0; i < 1000; i++) 49 | { 50 | int value = Random.Shared.Next(); 51 | managedQueue.Enqueue(value); 52 | nativeQueue.Enqueue(value); 53 | nativeQueue.Peek().Should().Be(managedQueue.Peek()); 54 | nativeQueue.TryPeek(out var peakValue); 55 | managedQueue.TryPeek(out var peakValue2); 56 | peakValue.Should().Be(peakValue2); 57 | } 58 | 59 | { 60 | nativeQueue.Clear(); 61 | nativeQueue.TryPeek(out var value).Should().Be(false); 62 | } 63 | 64 | 65 | } 66 | 67 | [Fact] 68 | public void Clear() 69 | { 70 | Queue nativeQueue = new Queue(); 71 | for (int i = 0; i < 100; i++) 72 | { 73 | for (int j = 0; j < Random.Shared.Next(1,100); j++) 74 | { 75 | int value = Random.Shared.Next(); 76 | nativeQueue.Enqueue(value); 77 | } 78 | nativeQueue.Clear(); 79 | nativeQueue.Count.Should().Be(0); 80 | } 81 | } 82 | 83 | [Fact] 84 | public void NativeCollectionClass() 85 | { 86 | Queue queue = new Queue(); 87 | queue.IsDisposed.Should().Be(false); 88 | for (int i = 0; i < 100; i++) 89 | { 90 | queue.Enqueue(Random.Shared.Next()); 91 | } 92 | queue.Dispose(); 93 | queue.IsDisposed.Should().Be(true); 94 | queue.ReInit(); 95 | queue.IsDisposed.Should().Be(false); 96 | queue.Count.Should().Be(0); 97 | for (int i = 0; i < 100; i++) 98 | { 99 | queue.Enqueue(Random.Shared.Next()); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/MultiMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using NativeCollection.UnsafeType; 6 | 7 | namespace NativeCollection 8 | { 9 | public unsafe class MultiMap : IEnumerable>, INativeCollectionClass 10 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 11 | { 12 | private UnsafeType.MultiMap* _multiMap; 13 | 14 | private const int _defaultPoolBlockSize = 64; 15 | 16 | private const int _defaultListPoolSize = 200; 17 | 18 | private int _poolBlockSize; 19 | 20 | private int _listPoolSize; 21 | 22 | public MultiMap(int listPoolSize = _defaultListPoolSize,int nodePoolBlockSize = _defaultPoolBlockSize) 23 | { 24 | _poolBlockSize = nodePoolBlockSize; 25 | _listPoolSize = listPoolSize; 26 | _multiMap = UnsafeType.MultiMap.Create(_poolBlockSize,_listPoolSize); 27 | IsDisposed = false; 28 | } 29 | 30 | public Span this[T key] => (*_multiMap)[key]; 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public void Add(T key, K value) 34 | { 35 | _multiMap->Add(key,value); 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public bool Remove(T key, K value) 40 | { 41 | return _multiMap->Remove(key, value); 42 | } 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public bool Remove(T key) 46 | { 47 | return _multiMap->Remove(key); 48 | } 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public void Clear() 52 | { 53 | _multiMap->Clear(); 54 | } 55 | 56 | public int Count => _multiMap->Count; 57 | 58 | IEnumerator> IEnumerable>. GetEnumerator() 59 | { 60 | return GetEnumerator(); 61 | } 62 | 63 | IEnumerator IEnumerable.GetEnumerator() 64 | { 65 | return GetEnumerator(); 66 | } 67 | 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public UnsafeType.SortedSet>.Enumerator GetEnumerator() 70 | { 71 | return _multiMap->GetEnumerator(); 72 | } 73 | 74 | public void Dispose() 75 | { 76 | if (IsDisposed) 77 | { 78 | return; 79 | } 80 | 81 | if (_multiMap!=null) 82 | { 83 | 84 | _multiMap->Dispose(); 85 | NativeMemoryHelper.Free(_multiMap); 86 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 87 | IsDisposed = true; 88 | } 89 | } 90 | 91 | public void ReInit() 92 | { 93 | if (IsDisposed) 94 | { 95 | _multiMap = UnsafeType.MultiMap.Create(_poolBlockSize,_listPoolSize); 96 | IsDisposed = false; 97 | } 98 | } 99 | 100 | public bool IsDisposed { get; private set; } 101 | 102 | ~MultiMap() 103 | { 104 | Dispose(); 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/ThrowHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace NativeCollection 5 | { 6 | internal static class ThrowHelper 7 | { 8 | [DoesNotReturn] 9 | public static void StackInitialCapacityException() 10 | { 11 | throw new ArgumentOutOfRangeException(); 12 | } 13 | 14 | [DoesNotReturn] 15 | public static void StackEmptyException() 16 | { 17 | throw new InvalidOperationException("Stack Empty"); 18 | } 19 | 20 | [DoesNotReturn] 21 | public static void QueueEmptyException() 22 | { 23 | throw new InvalidOperationException("EmptyQueue"); 24 | } 25 | 26 | [DoesNotReturn] 27 | public static void ListInitialCapacityException() 28 | { 29 | throw new ArgumentOutOfRangeException(); 30 | } 31 | 32 | [DoesNotReturn] 33 | public static void IndexMustBeLessException() 34 | { 35 | throw new ArgumentOutOfRangeException("IndexMustBeLess"); 36 | } 37 | 38 | 39 | [DoesNotReturn] 40 | public static void ListSmallCapacity() 41 | { 42 | throw new ArgumentOutOfRangeException("SmallCapacity"); 43 | } 44 | 45 | 46 | [DoesNotReturn] 47 | public static void ListIndexOutOfRange() 48 | { 49 | throw new ArgumentOutOfRangeException("ListIndexOutOfRange"); 50 | } 51 | 52 | [DoesNotReturn] 53 | public static void ArgumentNullException(string argName) 54 | { 55 | throw new ArgumentNullException(argName); 56 | } 57 | [DoesNotReturn] 58 | public static void ArgumentOutOfRangeException(string argName) 59 | { 60 | throw new ArgumentOutOfRangeException(argName); 61 | } 62 | 63 | [DoesNotReturn] 64 | public static void ConcurrentOperationsNotSupported() 65 | { 66 | throw new InvalidOperationException("ConcurrentOperationsNotSupported"); 67 | } 68 | 69 | [DoesNotReturn] 70 | public static void HashSetCapacityOutOfRange() 71 | { 72 | throw new ArgumentOutOfRangeException("HashSetCapacityOutOfRange"); 73 | } 74 | 75 | [DoesNotReturn] 76 | public static void HashSetEnumFailedVersion() 77 | { 78 | throw new InvalidOperationException("EnumFailedVersion"); 79 | } 80 | 81 | [DoesNotReturn] 82 | public static void HashSetEnumOpCantHappen() 83 | { 84 | throw new InvalidOperationException("EnumOpCantHappen"); 85 | } 86 | 87 | [DoesNotReturn] 88 | public static void SortedSetVersionChanged() 89 | { 90 | throw new InvalidOperationException("_version != _tree.version"); 91 | } 92 | 93 | [DoesNotReturn] 94 | public static void ThrowAddingDuplicateWithKeyArgumentException() 95 | { 96 | throw new ArgumentException("AddingDuplicateWithKey"); 97 | } 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/HashHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NativeCollection.UnsafeType 4 | { 5 | public static class HashHelpers 6 | { 7 | public const uint HashCollisionThreshold = 100; 8 | 9 | // This is the maximum prime smaller than Array.MaxLength. 10 | public const int MaxPrimeArrayLength = 0x7FFFFFC3; 11 | 12 | public const int HashPrime = 101; 13 | 14 | private static readonly int[] s_primes = new int[72] 15 | { 16 | 3, 17 | 7, 18 | 11, 19 | 17, 20 | 23, 21 | 29, 22 | 37, 23 | 47, 24 | 59, 25 | 71, 26 | 89, 27 | 107, 28 | 131, 29 | 163, 30 | 197, 31 | 239, 32 | 293, 33 | 353, 34 | 431, 35 | 521, 36 | 631, 37 | 761, 38 | 919, 39 | 1103, 40 | 1327, 41 | 1597, 42 | 1931, 43 | 2333, 44 | 2801, 45 | 3371, 46 | 4049, 47 | 4861, 48 | 5839, 49 | 7013, 50 | 8419, 51 | 10103, 52 | 12143, 53 | 14591, 54 | 17519, 55 | 21023, 56 | 25229, 57 | 30293, 58 | 36353, 59 | 43627, 60 | 52361, 61 | 62851, 62 | 75431, 63 | 90523, 64 | 108631, 65 | 130363, 66 | 156437, 67 | 187751, 68 | 225307, 69 | 270371, 70 | 324449, 71 | 389357, 72 | 467237, 73 | 560689, 74 | 672827, 75 | 807403, 76 | 968897, 77 | 1162687, 78 | 1395263, 79 | 1674319, 80 | 2009191, 81 | 2411033, 82 | 2893249, 83 | 3471899, 84 | 4166287, 85 | 4999559, 86 | 5999471, 87 | 7199369 88 | }; 89 | 90 | public static bool IsPrime(int candidate) 91 | { 92 | if ((candidate & 1) == 0) 93 | return candidate == 2; 94 | int num = (int) Math.Sqrt((double) candidate); 95 | for (int index = 3; index <= num; index += 2) 96 | { 97 | if (candidate % index == 0) 98 | return false; 99 | } 100 | return true; 101 | } 102 | 103 | public static int GetPrime(int min) 104 | { 105 | if (min < 0) 106 | throw new ArgumentException("SR.Arg_HTCapacityOverflow"); 107 | foreach (int prime in HashHelpers.s_primes) 108 | { 109 | if (prime >= min) 110 | return prime; 111 | } 112 | for (int candidate = min | 1; candidate < int.MaxValue; candidate += 2) 113 | { 114 | if (HashHelpers.IsPrime(candidate) && (candidate - 1) % 101 != 0) 115 | return candidate; 116 | } 117 | return min; 118 | } 119 | 120 | public static int ExpandPrime(int oldSize) 121 | { 122 | int min = 2 * oldSize; 123 | return (uint) min > 2147483587U && 2147483587 > oldSize ? 2147483587 : HashHelpers.GetPrime(min); 124 | } 125 | 126 | /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). 127 | /// This should only be used on 64-bit. 128 | public static ulong GetFastModMultiplier(uint divisor) => 129 | ulong.MaxValue / divisor + 1; 130 | 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkHashSet.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | 4 | namespace Benchmark.Benchmarks; 5 | [ShortRunJob] 6 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 7 | [CategoriesColumn] 8 | public class BenchmarkHashSet 9 | { 10 | [Params(10000,100000,1000000)] 11 | public int Count { get; set; } 12 | private List input; 13 | private NativeCollection.HashSet nativesHashSet; 14 | private HashSet managedHashSet; 15 | 16 | 17 | [GlobalSetup(Targets = new []{nameof(NativeAddRemove),nameof(ManagedAddRemove)})] 18 | public void InitAddRemove() 19 | { 20 | nativesHashSet = new NativeCollection.HashSet(1000); 21 | managedHashSet = new HashSet(); 22 | input = new List(); 23 | for (int i = 0; i < Count; i++) 24 | { 25 | input.Add(Random.Shared.Next(Count)); 26 | } 27 | foreach (var value in input) 28 | { 29 | nativesHashSet.Add(value); 30 | } 31 | foreach (var value in input) 32 | { 33 | managedHashSet.Add(value); 34 | } 35 | } 36 | 37 | [BenchmarkCategory("AddRemove")] 38 | [Benchmark] 39 | public void NativeAddRemove() 40 | { 41 | for (int i = 0; i < 100; i++) 42 | { 43 | for (int j = Count; j < Count+1000; j++) 44 | { 45 | nativesHashSet.Add(j); 46 | } 47 | 48 | for (int j = Count; j < Count+1000; j++) 49 | { 50 | nativesHashSet.Remove(j); 51 | } 52 | } 53 | } 54 | 55 | [BenchmarkCategory("AddRemove")] 56 | [Benchmark(Baseline = true)] 57 | public void ManagedAddRemove() 58 | { 59 | for (int i = 0; i < 100; i++) 60 | { 61 | for (int j = Count; j < Count+1000; j++) 62 | { 63 | managedHashSet.Add(j); 64 | } 65 | 66 | for (int j = Count; j < Count+1000; j++) 67 | { 68 | managedHashSet.Remove(j); 69 | } 70 | } 71 | } 72 | 73 | [GlobalSetup(Targets = new []{nameof(NativeEnumerate),nameof(ManagedEnumerate)})] 74 | public void InitEnumerate() 75 | { 76 | nativesHashSet = new NativeCollection.HashSet(); 77 | managedHashSet = new HashSet(); 78 | input = new List(); 79 | for (int i = 0; i < Count; i++) 80 | { 81 | input.Add(Random.Shared.Next()); 82 | } 83 | foreach (var value in input) 84 | { 85 | nativesHashSet.Add(value); 86 | } 87 | foreach (var value in input) 88 | { 89 | managedHashSet.Add(value); 90 | } 91 | } 92 | 93 | [BenchmarkCategory("Enumerate")] 94 | [Benchmark] 95 | public void NativeEnumerate() 96 | { 97 | int result = 0; 98 | foreach (var value in nativesHashSet) 99 | { 100 | result += value; 101 | } 102 | } 103 | 104 | [BenchmarkCategory("Enumerate")] 105 | [Benchmark(Baseline = true)] 106 | public void ManagedEnumerate() 107 | { 108 | int result = 0; 109 | foreach (var value in managedHashSet) 110 | { 111 | result += value; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/NativeMemoryHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace NativeCollection 7 | { 8 | public static unsafe class NativeMemoryHelper 9 | { 10 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 11 | public static void* Alloc(UIntPtr byteCount) 12 | { 13 | AddNativeMemoryByte((long)byteCount); 14 | #if NET6_0_OR_GREATER 15 | return NativeMemory.Alloc(byteCount); 16 | #else 17 | return Marshal.AllocHGlobal((int)byteCount).ToPointer(); 18 | #endif 19 | } 20 | 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | public static void* Alloc(UIntPtr elementCount, UIntPtr elementSize) 23 | { 24 | AddNativeMemoryByte((long)((long)elementCount * (long)elementSize)); 25 | #if NET6_0_OR_GREATER 26 | return NativeMemory.Alloc(elementCount, elementSize); 27 | #else 28 | return Marshal.AllocHGlobal((int)((int)elementCount*(int)elementSize)).ToPointer(); 29 | #endif 30 | } 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public static void* AllocZeroed(UIntPtr byteCount) 34 | { 35 | AddNativeMemoryByte((long)byteCount); 36 | #if NET6_0_OR_GREATER 37 | return NativeMemory.AllocZeroed(byteCount); 38 | #else 39 | var ptr = Marshal.AllocHGlobal((int)byteCount).ToPointer(); 40 | Unsafe.InitBlockUnaligned(ptr,0,(uint)byteCount); 41 | return ptr; 42 | #endif 43 | } 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public static void* AllocZeroed(UIntPtr elementCount, UIntPtr elementSize) 47 | { 48 | AddNativeMemoryByte((long)((long)elementCount * (long)elementSize)); 49 | #if NET6_0_OR_GREATER 50 | return NativeMemory.AllocZeroed(elementCount, elementSize); 51 | #else 52 | var ptr = Marshal.AllocHGlobal((int)((int)elementCount*(int)elementSize)).ToPointer(); 53 | Unsafe.InitBlockUnaligned(ptr,0,(uint)((uint)elementCount*(uint)elementSize)); 54 | return ptr; 55 | #endif 56 | } 57 | 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public static void Free(T* ptr) where T : unmanaged 61 | { 62 | #if NET6_0_OR_GREATER 63 | NativeMemory.Free(ptr); 64 | #else 65 | Marshal.FreeHGlobal(new IntPtr(ptr)); 66 | #endif 67 | } 68 | 69 | #if MEMORY_PROFILE 70 | public static long NativeMemoryBytes; 71 | #endif 72 | 73 | public static void AddNativeMemoryByte(long size) 74 | { 75 | GC.AddMemoryPressure((long)size); 76 | //Console.WriteLine($"AddNativeMemoryByte {size}"); 77 | #if MEMORY_PROFILE 78 | NativeMemoryBytes += size; 79 | #endif 80 | } 81 | 82 | public static void RemoveNativeMemoryByte(long size) 83 | { 84 | GC.RemoveMemoryPressure(size); 85 | //Console.WriteLine($"RemoveMemoryPressure {size}"); 86 | #if MEMORY_PROFILE 87 | NativeMemoryBytes -= size; 88 | #endif 89 | } 90 | 91 | public static long GetNativeMemoryBytes() 92 | { 93 | #if MEMORY_PROFILE 94 | return NativeMemoryBytes; 95 | #else 96 | return 0; 97 | #endif 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/HashSetTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | using Xunit.Sdk; 4 | 5 | namespace NativeCollection.Test; 6 | 7 | public class HashSetTest 8 | { 9 | [Fact] 10 | public void AddRemove() 11 | { 12 | HashSet hashSet = new HashSet(); 13 | hashSet.Add(1); 14 | hashSet.Add(2); 15 | hashSet.Add(1); 16 | hashSet.Count.Should().Be(2); 17 | hashSet.Remove(2); 18 | hashSet.Count.Should().Be(1); 19 | hashSet.Remove(1); 20 | hashSet.Count.Should().Be(0); 21 | hashSet.Add(23); 22 | hashSet.Add(123); 23 | hashSet.Clear(); 24 | hashSet.Count.Should().Be(0); 25 | hashSet.Remove(1).Should().Be(false); 26 | hashSet.Add(1); 27 | hashSet.Remove(1).Should().Be(true); 28 | } 29 | 30 | [Fact] 31 | public void Contains() 32 | { 33 | HashSet hashSet = new HashSet(); 34 | hashSet.Contains(1).Should().Be(false); 35 | hashSet.Add(1); 36 | hashSet.Contains(1).Should().Be(true); 37 | hashSet.Contains(2).Should().Be(false); 38 | hashSet.Clear(); 39 | hashSet.Contains(1).Should().Be(false); 40 | } 41 | 42 | [Fact] 43 | public void AddRemoveInEnumerator() 44 | { 45 | HashSet hashSet = new HashSet(); 46 | 47 | for (int i = 0; i < 100; i++) 48 | { 49 | int value = i; 50 | hashSet.Add(value); 51 | } 52 | 53 | foreach (var value in hashSet) 54 | { 55 | if (value%2==0) 56 | { 57 | hashSet.Remove(value); 58 | } 59 | } 60 | 61 | hashSet.Count.Should().Be(50); 62 | hashSet.Clear(); 63 | 64 | for (int i = 0; i < 100; i++) 65 | { 66 | int value = Random.Shared.Next(); 67 | hashSet.Add(value); 68 | } 69 | 70 | bool hasException = false; 71 | 72 | try 73 | { 74 | foreach (var value in hashSet) 75 | { 76 | hashSet.Add(Random.Shared.Next()); 77 | } 78 | } 79 | catch (Exception e) 80 | { 81 | hasException = true; 82 | } 83 | 84 | hasException.Should().Be(true); 85 | hasException = false; 86 | 87 | 88 | } 89 | 90 | [Fact] 91 | public void CopyTo() 92 | { 93 | int[] rawList = new int[100]; 94 | HashSet hashSet = new HashSet(); 95 | for (int i = 0; i < 100; i++) 96 | { 97 | hashSet.Add(i); 98 | } 99 | 100 | hashSet.CopyTo(rawList,0); 101 | 102 | for (int i = 0; i < 100; i++) 103 | { 104 | rawList[i].Should().Be(i); 105 | } 106 | 107 | } 108 | 109 | [Fact] 110 | public void NativeCollectionClass() 111 | { 112 | HashSet hashSet = new HashSet(); 113 | hashSet.IsDisposed.Should().Be(false); 114 | for (int i = 0; i < 100; i++) 115 | { 116 | hashSet.Add(Random.Shared.Next()); 117 | } 118 | hashSet.Dispose(); 119 | hashSet.IsDisposed.Should().Be(true); 120 | hashSet.ReInit(); 121 | hashSet.IsDisposed.Should().Be(false); 122 | hashSet.Count.Should().Be(0); 123 | for (int i = 0; i < 100; i++) 124 | { 125 | hashSet.Add(Random.Shared.Next()); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/Map/Map.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace NativeCollection.UnsafeType 8 | { 9 | public unsafe struct Map :IEnumerable>, IDisposable where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 10 | { 11 | private UnsafeType.SortedSet>* _sortedSet; 12 | 13 | public static Map* Create(int poolBlockSize) 14 | { 15 | Map* map = (Map*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 16 | map->_sortedSet = UnsafeType.SortedSet>.Create(poolBlockSize); 17 | return map; 18 | } 19 | 20 | public K this[T key] { 21 | get 22 | { 23 | var pair = new MapPair(key); 24 | var node = _sortedSet->FindNode(pair); 25 | if (node!=null) 26 | { 27 | return node->Item.Value; 28 | } 29 | return default; 30 | } 31 | set 32 | { 33 | var pair = new MapPair(key,value); 34 | var node = _sortedSet->FindNode(pair); 35 | if (node!=null) 36 | { 37 | node->Item._value = value; 38 | } 39 | else 40 | { 41 | _sortedSet->Add(pair); 42 | } 43 | } 44 | } 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public void Add(T key, K value) 48 | { 49 | var mapPair = new MapPair(key,value); 50 | _sortedSet->Add(mapPair); 51 | } 52 | 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | public void Clear() 55 | { 56 | _sortedSet->Clear(); 57 | } 58 | 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public bool ContainsKey(T key) 61 | { 62 | return _sortedSet->Contains(new MapPair(key)); 63 | } 64 | 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public bool Remove(T key) 67 | { 68 | return _sortedSet->Remove(new MapPair(key)); 69 | } 70 | 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | public bool TryGetValue(T key, out K value) 73 | { 74 | var node = _sortedSet->FindNode(new MapPair(key)); 75 | if (node==null) 76 | { 77 | value = default; 78 | return false; 79 | } 80 | value = node->Item.Value; 81 | return true; 82 | } 83 | 84 | public int Count => _sortedSet->Count; 85 | 86 | IEnumerator> IEnumerable>.GetEnumerator() 87 | { 88 | return GetEnumerator(); 89 | } 90 | 91 | IEnumerator IEnumerable.GetEnumerator() 92 | { 93 | return GetEnumerator(); 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public UnsafeType.SortedSet>.Enumerator GetEnumerator() 98 | { 99 | return new UnsafeType.SortedSet>.Enumerator(_sortedSet); 100 | } 101 | 102 | public void Dispose() 103 | { 104 | if (_sortedSet != null) 105 | { 106 | _sortedSet->Dispose(); 107 | NativeMemoryHelper.Free(_sortedSet); 108 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>>()); 109 | } 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnOrderMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | using NativeCollection.UnsafeType; 6 | 7 | namespace NativeCollection 8 | { 9 | public unsafe class UnOrderMap : IEnumerable>, INativeCollectionClass 10 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 11 | { 12 | private int _capacity; 13 | private UnsafeType.UnOrderMap* _unOrderMap; 14 | 15 | public UnOrderMap(int initCapacity = 0) 16 | { 17 | _capacity = initCapacity; 18 | _unOrderMap = UnsafeType.UnOrderMap.Create(_capacity); 19 | IsDisposed = false; 20 | } 21 | 22 | public K this[T key] 23 | { 24 | get => (*_unOrderMap)[key]; 25 | set => (*_unOrderMap)[key] = value; 26 | } 27 | 28 | public int Count => _unOrderMap->Count; 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public void Add(in T key, K value) 32 | { 33 | _unOrderMap->Add(key, value); 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public bool Remove(in T key) 38 | { 39 | return _unOrderMap->Remove(key); 40 | } 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public void Clear() 44 | { 45 | _unOrderMap->Clear(); 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public bool ContainsKey(in T key) 50 | { 51 | return _unOrderMap->ContainsKey(key); 52 | } 53 | 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public bool TryGetValue(in T key, out K value) 56 | { 57 | bool contains = _unOrderMap->TryGetValue(key, out var actualValue); 58 | if (contains) 59 | { 60 | value = actualValue; 61 | return true; 62 | } 63 | value = default; 64 | return false; 65 | } 66 | 67 | 68 | IEnumerator> IEnumerable>.GetEnumerator() 69 | { 70 | return GetEnumerator(); 71 | } 72 | 73 | IEnumerator IEnumerable.GetEnumerator() 74 | { 75 | return GetEnumerator(); 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public UnsafeType.UnOrderMap.Enumerator GetEnumerator() 80 | { 81 | return _unOrderMap->GetEnumerator(); 82 | } 83 | 84 | public void Dispose() 85 | { 86 | if (IsDisposed) return; 87 | if (_unOrderMap != null) 88 | { 89 | _unOrderMap->Dispose(); 90 | NativeMemoryHelper.Free(_unOrderMap); 91 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 92 | IsDisposed = true; 93 | } 94 | } 95 | 96 | public void ReInit() 97 | { 98 | if (IsDisposed) 99 | { 100 | _unOrderMap = UnsafeType.UnOrderMap.Create(_capacity); 101 | IsDisposed = false; 102 | } 103 | } 104 | 105 | public bool IsDisposed { get; private set; } 106 | 107 | ~UnOrderMap() 108 | { 109 | Dispose(); 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkSortedSet.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | using BenchmarkDotNet.Diagnostics.Windows.Configs; 4 | 5 | namespace Benchmark.Benchmarks; 6 | [ShortRunJob] 7 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 8 | [CategoriesColumn] 9 | public class BenchmarkSortedSet 10 | { 11 | [Params(1000000)] 12 | public int Count { get; set; } 13 | private List input; 14 | private NativeCollection.SortedSet nativesSortedSet; 15 | private SortedSet managedSortedSet; 16 | 17 | 18 | [GlobalSetup(Targets = new []{nameof(NativeAddRemove),nameof(ManagedAddRemove)})] 19 | public void InitAddRemove() 20 | { 21 | nativesSortedSet = new NativeCollection.SortedSet(1000); 22 | managedSortedSet = new SortedSet(); 23 | input = new List(); 24 | for (int i = 0; i < Count; i++) 25 | { 26 | input.Add(Random.Shared.Next(Count)); 27 | } 28 | foreach (var value in input) 29 | { 30 | nativesSortedSet.Add(value); 31 | } 32 | foreach (var value in input) 33 | { 34 | managedSortedSet.Add(value); 35 | } 36 | } 37 | 38 | [BenchmarkCategory("AddRemove")] 39 | [Benchmark] 40 | public void NativeAddRemove() 41 | { 42 | for (int i = 0; i < 100; i++) 43 | { 44 | for (int j = Count; j < Count+1000; j++) 45 | { 46 | nativesSortedSet.Add(j); 47 | } 48 | 49 | for (int j = Count; j < Count+1000; j++) 50 | { 51 | nativesSortedSet.Remove(j); 52 | } 53 | } 54 | } 55 | 56 | [BenchmarkCategory("AddRemove")] 57 | [Benchmark(Baseline = true)] 58 | public void ManagedAddRemove() 59 | { 60 | for (int i = 0; i < 100; i++) 61 | { 62 | for (int j = Count; j < Count+1000; j++) 63 | { 64 | managedSortedSet.Add(j); 65 | } 66 | 67 | for (int j = Count; j < Count+1000; j++) 68 | { 69 | managedSortedSet.Remove(j); 70 | } 71 | } 72 | } 73 | 74 | [GlobalSetup(Targets = new []{nameof(NativeEnumerate),nameof(ManagedEnumerate)})] 75 | public void InitEnumerate() 76 | { 77 | nativesSortedSet = new NativeCollection.SortedSet(); 78 | managedSortedSet = new SortedSet(); 79 | input = new List(); 80 | for (int i = 0; i < Count; i++) 81 | { 82 | input.Add(Random.Shared.Next()); 83 | } 84 | foreach (var value in input) 85 | { 86 | nativesSortedSet.Add(value); 87 | } 88 | foreach (var value in input) 89 | { 90 | managedSortedSet.Add(value); 91 | } 92 | } 93 | 94 | [BenchmarkCategory("Enumerate")] 95 | [Benchmark] 96 | public void NativeEnumerate() 97 | { 98 | int result = 0; 99 | foreach (var value in nativesSortedSet) 100 | { 101 | result += value; 102 | } 103 | } 104 | 105 | [BenchmarkCategory("Enumerate")] 106 | [Benchmark(Baseline = true)] 107 | public void ManagedEnumerate() 108 | { 109 | int result = 0; 110 | foreach (var value in managedSortedSet) 111 | { 112 | result += value; 113 | } 114 | } 115 | 116 | [GlobalCleanup] 117 | public void Dispose() 118 | { 119 | nativesSortedSet.Dispose(); 120 | } 121 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/Stack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection.UnsafeType 5 | { 6 | public unsafe struct Stack : IDisposable, IPool where T : unmanaged 7 | { 8 | private T* _array; 9 | private int _version; 10 | private const int _defaultCapacity = 10; 11 | internal int ArrayLength { get; private set; } 12 | 13 | public static Stack* Create(int initialCapacity = _defaultCapacity) 14 | { 15 | if (initialCapacity < 0) ThrowHelper.StackInitialCapacityException(); 16 | 17 | var stack = (Stack*)NativeMemoryHelper.Alloc((UIntPtr)System.Runtime.CompilerServices.Unsafe.SizeOf>()); 18 | 19 | if (initialCapacity < _defaultCapacity) 20 | initialCapacity = _defaultCapacity; // Simplify doubling logic in Push. 21 | 22 | stack->_array = (T*)NativeMemoryHelper.Alloc((UIntPtr)initialCapacity, (UIntPtr)System.Runtime.CompilerServices.Unsafe.SizeOf()); 23 | stack->ArrayLength = initialCapacity; 24 | stack->Count = 0; 25 | stack->_version = 0; 26 | return stack; 27 | } 28 | 29 | public int Count { get; private set; } 30 | 31 | 32 | public void Clear() 33 | { 34 | Count = 0; 35 | _version++; 36 | } 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public bool Contains(in T obj) 40 | { 41 | var count = Count; 42 | while (count-- > 0) 43 | if (obj.Equals(_array[count])) 44 | return true; 45 | return false; 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public T Peak() 50 | { 51 | if (Count == 0) ThrowHelper.StackEmptyException(); 52 | return _array[Count - 1]; 53 | } 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public T Pop() 57 | { 58 | if (Count == 0) 59 | ThrowHelper.StackEmptyException(); 60 | 61 | _version++; 62 | var obj = _array[--Count]; 63 | _array[Count] = default; 64 | return obj; 65 | } 66 | 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | public bool TryPop(out T result) 69 | { 70 | var index = Count - 1; 71 | var array = _array; 72 | if ((uint)index >= (uint)ArrayLength) 73 | { 74 | result = default; 75 | return false; 76 | } 77 | 78 | ++_version; 79 | Count = index; 80 | result = array[index]; 81 | return true; 82 | } 83 | 84 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 | public void Push(in T obj) 86 | { 87 | if (Count == ArrayLength) 88 | { 89 | var newArray = (T*)NativeMemoryHelper.Alloc((UIntPtr)(ArrayLength * 2), (UIntPtr)System.Runtime.CompilerServices.Unsafe.SizeOf()); 90 | System.Runtime.CompilerServices.Unsafe.CopyBlockUnaligned(newArray, _array, (uint)(Count * System.Runtime.CompilerServices.Unsafe.SizeOf())); 91 | NativeMemoryHelper.Free(_array); 92 | NativeMemoryHelper.RemoveNativeMemoryByte(ArrayLength * System.Runtime.CompilerServices.Unsafe.SizeOf()); 93 | _array = newArray; 94 | ArrayLength = ArrayLength * 2; 95 | } 96 | 97 | _array[Count++] = obj; 98 | _version++; 99 | } 100 | 101 | public void Dispose() 102 | { 103 | NativeMemoryHelper.Free(_array); 104 | NativeMemoryHelper.RemoveNativeMemoryByte(ArrayLength * System.Runtime.CompilerServices.Unsafe.SizeOf()); 105 | } 106 | 107 | public void OnReturnToPool() 108 | { 109 | Clear(); 110 | } 111 | 112 | public void OnGetFromPool() 113 | { 114 | 115 | } 116 | } 117 | } 118 | 119 | 120 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/StackTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class StackTest 7 | { 8 | [Fact] 9 | public void PushPopClear() 10 | { 11 | Stack nativeStack = new Stack(); 12 | System.Collections.Generic.Stack managedStack = new System.Collections.Generic.Stack(); 13 | 14 | for (int i = 0; i < 10; i++) 15 | { 16 | int count = Random.Shared.Next(1, 1000); 17 | for (int j = 0; j < count; j++) 18 | { 19 | int value = Random.Shared.Next(); 20 | nativeStack.Push(value); 21 | managedStack.Push(value); 22 | } 23 | 24 | for (int j = 0; j < count; j++) 25 | { 26 | nativeStack.Pop().Should().Be(managedStack.Pop()); 27 | } 28 | managedStack.Clear(); 29 | nativeStack.Clear(); 30 | nativeStack.Count.Should().Be(0); 31 | } 32 | nativeStack.Clear(); 33 | nativeStack.Count.Should().Be(0); 34 | nativeStack.Clear(); 35 | nativeStack.Count.Should().Be(0); 36 | } 37 | 38 | 39 | [Fact] 40 | public void Contains() 41 | { 42 | Stack nativeStack = new Stack(); 43 | System.Collections.Generic.Stack managedStack = new System.Collections.Generic.Stack(); 44 | 45 | int count = 1000; 46 | for (int j = 0; j < count; j++) 47 | { 48 | int value = Random.Shared.Next(1,99999); 49 | nativeStack.Push(value); 50 | managedStack.Push(value); 51 | nativeStack.Contains(value).Should().Be(true); 52 | } 53 | 54 | while (managedStack.TryPop(out var popValue)) 55 | { 56 | nativeStack.Contains(popValue).Should().Be(true); 57 | } 58 | 59 | for (int i = 0; i < 1000; i++) 60 | { 61 | nativeStack.Contains(Random.Shared.Next(-100000, 0)).Should().Be(false); 62 | } 63 | } 64 | 65 | [Fact] 66 | public void Peak() 67 | { 68 | Stack nativeStack = new Stack(); 69 | System.Collections.Generic.Stack managedStack = new System.Collections.Generic.Stack(); 70 | 71 | for (int i = 0; i < 10; i++) 72 | { 73 | int count = Random.Shared.Next(1, 1000); 74 | for (int j = 0; j < count; j++) 75 | { 76 | int value = Random.Shared.Next(); 77 | nativeStack.Push(value); 78 | managedStack.Push(value); 79 | nativeStack.Peak().Should().Be(managedStack.Peek()); 80 | } 81 | 82 | for (int j = 0; j < count; j++) 83 | { 84 | nativeStack.Peak().Should().Be(managedStack.Peek()); 85 | nativeStack.Pop(); 86 | managedStack.Pop(); 87 | } 88 | 89 | nativeStack.TryPop(out _).Should().Be(false); 90 | bool hasException = false; 91 | try 92 | { 93 | nativeStack.Pop(); 94 | } 95 | catch (Exception e) 96 | { 97 | hasException = true; 98 | } 99 | 100 | hasException.Should().Be(true); 101 | 102 | 103 | managedStack.Clear(); 104 | nativeStack.Clear(); 105 | } 106 | 107 | } 108 | 109 | [Fact] 110 | public void NativeCollectionClass() 111 | { 112 | Stack stack = new Stack(); 113 | stack.IsDisposed.Should().Be(false); 114 | for (int i = 0; i < 100; i++) 115 | { 116 | stack.Push(Random.Shared.Next()); 117 | } 118 | stack.Dispose(); 119 | stack.IsDisposed.Should().Be(true); 120 | stack.ReInit(); 121 | stack.IsDisposed.Should().Be(false); 122 | stack.Count.Should().Be(0); 123 | for (int i = 0; i < 100; i++) 124 | { 125 | stack.Push(Random.Shared.Next()); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/List.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace NativeCollection 7 | { 8 | public unsafe class List: ICollection, INativeCollectionClass where T:unmanaged, IEquatable 9 | { 10 | private UnsafeType.List* _list; 11 | private const int _defaultCapacity = 10; 12 | private int _capacity; 13 | public List(int capacity = _defaultCapacity) 14 | { 15 | _capacity = capacity; 16 | _list = UnsafeType.List.Create(_capacity); 17 | IsDisposed = false; 18 | } 19 | 20 | IEnumerator IEnumerable.GetEnumerator() 21 | { 22 | return GetEnumerator(); 23 | } 24 | 25 | IEnumerator IEnumerable.GetEnumerator() 26 | { 27 | return GetEnumerator(); 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public UnsafeType.List.Enumerator GetEnumerator() 32 | { 33 | return new UnsafeType.List.Enumerator(_list); 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public void Add(T item) 38 | { 39 | _list->Add(item); 40 | } 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public void Clear() 44 | { 45 | _list->Clear(); 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public bool Contains(T item) 50 | { 51 | return _list->Contains(item); 52 | } 53 | 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public void CopyTo(T[] array, int arrayIndex) 56 | { 57 | _list->CopyTo(array,arrayIndex); 58 | } 59 | 60 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 61 | public bool Remove(T item) 62 | { 63 | return _list->Remove(item); 64 | } 65 | 66 | public int Capacity 67 | { 68 | get => _list->Capacity; 69 | set => _list->Capacity = value; 70 | } 71 | 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | public int IndexOf(in T item) 74 | { 75 | return _list->IndexOf(item); 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public void RemoveAt(int index) 80 | { 81 | _list->RemoveAt(index); 82 | } 83 | 84 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 | public void Insert(int index, T item) 86 | { 87 | _list->Insert(index, item); 88 | } 89 | 90 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 91 | public void InsertRange(int index, Span collection) 92 | { 93 | _list->InsertRange(index,collection); 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public void RemoveRange(int index, int count) 98 | { 99 | _list->RemoveRange(index,count); 100 | } 101 | 102 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 103 | public Span WrittenSpan() 104 | { 105 | return _list->WrittenSpan(); 106 | } 107 | 108 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 109 | public Span TotalSpan() 110 | { 111 | return _list->TotalSpan(); 112 | } 113 | 114 | public ref T this[int index] => ref (*_list)[index]; 115 | 116 | public int Count => _list->Count; 117 | public bool IsReadOnly => false; 118 | 119 | public void Dispose() 120 | { 121 | if (IsDisposed) 122 | { 123 | return; 124 | } 125 | if (_list!=null) 126 | { 127 | _list->Dispose(); 128 | NativeMemoryHelper.Free(_list); 129 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 130 | IsDisposed = true; 131 | } 132 | } 133 | 134 | public void ReInit() 135 | { 136 | if (IsDisposed) 137 | { 138 | _list = UnsafeType.List.Create(_capacity); 139 | IsDisposed = false; 140 | } 141 | } 142 | 143 | public bool IsDisposed { get; private set; } 144 | 145 | ~List() 146 | { 147 | Dispose(); 148 | } 149 | } 150 | } 151 | 152 | 153 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/FixedSizeMemoryPool/FixedSizeMemoryPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace NativeCollection 6 | { 7 | public unsafe partial struct FixedSizeMemoryPool: IDisposable 8 | { 9 | // 最大维护的空slab 多出的空slab直接释放 10 | public int MaxUnUsedSlabs; 11 | 12 | public int ItemSize; 13 | 14 | public int BlockSize; 15 | 16 | public SlabLinkedList InUsedSlabs; 17 | 18 | public SlabLinkedList UnUsedSlabs; 19 | public FixedSizeMemoryPool* Self 20 | { 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | get { return (FixedSizeMemoryPool*)Unsafe.AsPointer(ref this); } 23 | } 24 | 25 | public static FixedSizeMemoryPool* Create(int blockSize, int itemSize , int maxUnUsedSlabs = 3) 26 | { 27 | FixedSizeMemoryPool* memoryPool = (FixedSizeMemoryPool*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf()); 28 | memoryPool->ItemSize = itemSize; 29 | memoryPool->BlockSize = blockSize; 30 | memoryPool->MaxUnUsedSlabs = maxUnUsedSlabs; 31 | Slab* initSlab = Slab.Create(blockSize, itemSize,null,null); 32 | memoryPool->InUsedSlabs = new SlabLinkedList(initSlab); 33 | memoryPool->UnUsedSlabs = new SlabLinkedList(null); 34 | return memoryPool; 35 | } 36 | 37 | public void* Alloc() 38 | { 39 | Debug.Assert(InUsedSlabs.Top!=null && InUsedSlabs.Top->FreeSize>0); 40 | byte* allocPtr = InUsedSlabs.Top->Alloc(); 41 | 42 | if (InUsedSlabs.Top->IsAllAlloc()) 43 | { 44 | InUsedSlabs.MoveTopToBottom(); 45 | 46 | if (InUsedSlabs.Top->IsAllAlloc()) 47 | { 48 | Slab* newSlab = Slab.Create(BlockSize, ItemSize,null,null); 49 | InUsedSlabs.AddToTop(newSlab); 50 | } 51 | } 52 | return allocPtr; 53 | } 54 | 55 | public void Free(void* ptr) 56 | { 57 | Debug.Assert(ptr!=null); 58 | ListNode* listNode = (ListNode*)((byte*)ptr - Unsafe.SizeOf()); 59 | Debug.Assert(listNode!=null); 60 | 61 | Slab* slab = listNode->ParentSlab; 62 | slab->Free(listNode); 63 | 64 | if (slab==InUsedSlabs.Top) 65 | { 66 | return; 67 | } 68 | 69 | // 当前链表头为空时 移至空闲链表 70 | if (InUsedSlabs.Top->IsAllFree()) 71 | { 72 | Slab* oldTopSlab = InUsedSlabs.Top; 73 | InUsedSlabs.SplitOut(oldTopSlab); 74 | UnUsedSlabs.AddToTop(oldTopSlab); 75 | 76 | // 释放多于的空slab 77 | if (UnUsedSlabs.SlabCount>MaxUnUsedSlabs) 78 | { 79 | var bottomSlab = UnUsedSlabs.Bottom; 80 | UnUsedSlabs.SplitOut(bottomSlab); 81 | bottomSlab->Dispose(); 82 | } 83 | } 84 | 85 | // 对应slab移至链表头部 86 | if (slab!=InUsedSlabs.Top) 87 | { 88 | InUsedSlabs.SplitOut(slab); 89 | InUsedSlabs.AddToTop(slab); 90 | } 91 | } 92 | 93 | public void ReleaseUnUsedSlabs() 94 | { 95 | Slab* unUsedSlab = UnUsedSlabs.Top; 96 | while (unUsedSlab!=null) 97 | { 98 | Slab* currentSlab = unUsedSlab; 99 | unUsedSlab = unUsedSlab->Next; 100 | currentSlab->Dispose(); 101 | } 102 | UnUsedSlabs = new SlabLinkedList(null); 103 | } 104 | 105 | public void Dispose() 106 | { 107 | Slab* inUsedSlab = InUsedSlabs.Top; 108 | while (inUsedSlab!=null) 109 | { 110 | Slab* currentSlab = inUsedSlab; 111 | inUsedSlab = inUsedSlab->Next; 112 | currentSlab->Dispose(); 113 | } 114 | 115 | ReleaseUnUsedSlabs(); 116 | 117 | if (Self!=null) 118 | { 119 | NativeMemoryHelper.Free(Self); 120 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()); 121 | } 122 | } 123 | } 124 | } 125 | 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NativeCollection 2 | Native Collection library in c# 3 | 4 | 目标是实现一个常用集合的0gc高性能非托管库 5 | 6 | 已实现Stack Queue HashSet SortedSet list map multimap. 7 | 8 | 注: 本项目容器代码为基于.Net标准库容器的移植 ,api进行了一定的简化. 9 | 10 | 下表是Multimap对比托管版本实现的性能测试 各项操作性能均快于托管版本一倍以上 11 | 12 | ``` 13 | 14 | BenchmarkDotNet v0.13.7, Windows 10 (10.0.19043.928/21H1/May2021Update) 15 | Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores 16 | .NET SDK 7.0.306 17 | [Host] : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2 18 | DefaultJob : .NET 7.0.9 (7.0.923.32018), X64 RyuJIT AVX2 19 | 20 | ``` 21 | | Method | Categories | KeyCount | Mean | Error | StdDev | Ratio | RatioSD | 22 | |--------------------------------- |----------------- |--------- |-----------------:|---------------:|---------------:|------:|--------:| 23 | | **NativeAddAndRemove** | **AddAndRemove** | **10000** | **237,981.43 ns** | **410.339 ns** | **383.831 ns** | **0.44** | **0.00** | 24 | | ManagedAddAndRemove | AddAndRemove | 10000 | 546,403.44 ns | 1,182.838 ns | 1,048.555 ns | 1.00 | 0.00 | 25 | | | | | | | | | | 26 | | **NativeAddAndRemove** | **AddAndRemove** | **100000** | **295,918.36 ns** | **317.246 ns** | **264.914 ns** | **0.43** | **0.00** | 27 | | ManagedAddAndRemove | AddAndRemove | 100000 | 687,445.54 ns | 953.501 ns | 845.254 ns | 1.00 | 0.00 | 28 | | | | | | | | | | 29 | | **NativeAddAndRemove** | **AddAndRemove** | **1000000** | **373,694.08 ns** | **931.678 ns** | **825.909 ns** | **0.40** | **0.00** | 30 | | ManagedAddAndRemove | AddAndRemove | 1000000 | 945,009.41 ns | 1,869.211 ns | 1,657.007 ns | 1.00 | 0.00 | 31 | | | | | | | | | | 32 | | **NativeEnumerateAll** | **EnumerateAll** | **10000** | **167,194.49 ns** | **253.321 ns** | **224.562 ns** | **0.86** | **0.00** | 33 | | ManagedEnumerateAll | EnumerateAll | 10000 | 194,746.90 ns | 1,213.956 ns | 1,135.535 ns | 1.00 | 0.00 | 34 | | | | | | | | | | 35 | | **NativeEnumerateAll** | **EnumerateAll** | **100000** | **3,032,551.02 ns** | **67,905.637 ns** | **199,155.478 ns** | **1.10** | **0.08** | 36 | | ManagedEnumerateAll | EnumerateAll | 100000 | 2,778,049.31 ns | 55,453.630 ns | 154,582.555 ns | 1.00 | 0.00 | 37 | | | | | | | | | | 38 | | **NativeEnumerateAll** | **EnumerateAll** | **1000000** | **36,404,652.20 ns** | **171,751.129 ns** | **143,419.971 ns** | **1.09** | **0.01** | 39 | | ManagedEnumerateAll | EnumerateAll | 1000000 | 33,500,559.56 ns | 185,616.319 ns | 173,625.620 ns | 1.00 | 0.00 | 40 | | | | | | | | | | 41 | | **NativeEnumerateAndRemoveFirst10** | **EnumerateFirst10** | **10000** | **26.25 ns** | **0.032 ns** | **0.027 ns** | **0.36** | **0.00** | 42 | | ManagedEnumerateAndRemoveFirst10 | EnumerateFirst10 | 10000 | 73.63 ns | 0.388 ns | 0.344 ns | 1.00 | 0.00 | 43 | | | | | | | | | | 44 | | **NativeEnumerateAndRemoveFirst10** | **EnumerateFirst10** | **100000** | **25.40 ns** | **0.081 ns** | **0.071 ns** | **0.33** | **0.00** | 45 | | ManagedEnumerateAndRemoveFirst10 | EnumerateFirst10 | 100000 | 76.37 ns | 0.116 ns | 0.097 ns | 1.00 | 0.00 | 46 | | | | | | | | | | 47 | | **NativeEnumerateAndRemoveFirst10** | **EnumerateFirst10** | **1000000** | **25.63 ns** | **0.038 ns** | **0.035 ns** | **0.32** | **0.00** | 48 | | ManagedEnumerateAndRemoveFirst10 | EnumerateFirst10 | 1000000 | 79.89 ns | 0.439 ns | 0.390 ns | 1.00 | 0.00 | 49 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/SortedSetTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public unsafe class SortedSetTest 7 | { 8 | [Fact] 9 | public void MinMax() 10 | { 11 | SortedSet nativeSortedSet = new SortedSet(); 12 | System.Collections.Generic.SortedSet managedSortedSet = new System.Collections.Generic.SortedSet(); 13 | 14 | for (int i = 0; i < 1000; i++) 15 | { 16 | int value = Random.Shared.Next(); 17 | nativeSortedSet.Add(value); 18 | managedSortedSet.Add(value); 19 | } 20 | 21 | nativeSortedSet.Min.Should().Be(managedSortedSet.Min); 22 | nativeSortedSet.Max.Should().Be(managedSortedSet.Max); 23 | nativeSortedSet.Clear(); 24 | } 25 | 26 | [Fact] 27 | public void Enumerator() 28 | { 29 | SortedSet nativeSortedSet =new SortedSet(); 30 | System.Collections.Generic.SortedSet managedSortedSet = new System.Collections.Generic.SortedSet(); 31 | 32 | for (int i = 0; i < 1000; i++) 33 | { 34 | int value = Random.Shared.Next(); 35 | nativeSortedSet.Add(value); 36 | managedSortedSet.Add(value); 37 | } 38 | 39 | var nativeSortedSetEnumerrator = nativeSortedSet.GetEnumerator(); 40 | var managedSortedSetEnumerrator = managedSortedSet.GetEnumerator(); 41 | while (managedSortedSetEnumerrator.MoveNext()) 42 | { 43 | nativeSortedSetEnumerrator.MoveNext(); 44 | nativeSortedSetEnumerrator.Current.Should().Be(managedSortedSetEnumerrator.Current); 45 | } 46 | } 47 | 48 | [Fact] 49 | public void Count() 50 | { 51 | SortedSet nativeSortedSet = new SortedSet(); 52 | nativeSortedSet.Count.Should().Be(0); 53 | 54 | for (int i = 0; i < 1000; i++) 55 | { 56 | int value = Random.Shared.Next(); 57 | nativeSortedSet.Add(value); 58 | } 59 | 60 | nativeSortedSet.Count.Should().Be(1000); 61 | nativeSortedSet.Clear(); 62 | nativeSortedSet.Count.Should().Be(0); 63 | } 64 | 65 | [Fact] 66 | public void AddRemove() 67 | { 68 | SortedSet nativeSortedSet = new SortedSet(); 69 | bool addResult; 70 | bool removeResult; 71 | addResult = nativeSortedSet.Add(1); 72 | addResult.Should().Be(true); 73 | nativeSortedSet.Count.Should().Be(1); 74 | addResult = nativeSortedSet.Add(1); 75 | nativeSortedSet.Count.Should().Be(1); 76 | addResult.Should().Be(false); 77 | removeResult = nativeSortedSet.Remove(1); 78 | nativeSortedSet.Count.Should().Be(0); 79 | removeResult.Should().Be(true); 80 | removeResult = nativeSortedSet.Remove(1); 81 | removeResult.Should().Be(false); 82 | } 83 | 84 | [Fact] 85 | public void AddRemoveInEnumerator() 86 | { 87 | SortedSet nativeSortedSet = new SortedSet(); 88 | 89 | for (int i = 0; i < 100; i++) 90 | { 91 | int value = Random.Shared.Next(); 92 | nativeSortedSet.Add(value); 93 | } 94 | 95 | bool hasException = false; 96 | 97 | try 98 | { 99 | foreach (var value in nativeSortedSet) 100 | { 101 | nativeSortedSet.Add(Random.Shared.Next()); 102 | } 103 | } 104 | catch (Exception e) 105 | { 106 | hasException = true; 107 | } 108 | 109 | hasException.Should().Be(true); 110 | hasException = false; 111 | 112 | try 113 | { 114 | foreach (var value in nativeSortedSet) 115 | { 116 | nativeSortedSet.Remove(Random.Shared.Next()); 117 | } 118 | } 119 | catch (Exception e) 120 | { 121 | hasException = true; 122 | } 123 | hasException.Should().Be(true); 124 | } 125 | 126 | [Fact] 127 | public void NativeCollectionClass() 128 | { 129 | SortedSet sortedSet = new SortedSet(); 130 | sortedSet.IsDisposed.Should().Be(false); 131 | for (int i = 0; i < 100; i++) 132 | { 133 | sortedSet.Add(Random.Shared.Next()); 134 | } 135 | sortedSet.Dispose(); 136 | sortedSet.IsDisposed.Should().Be(true); 137 | sortedSet.ReInit(); 138 | sortedSet.IsDisposed.Should().Be(false); 139 | sortedSet.Count.Should().Be(0); 140 | for (int i = 0; i < 100; i++) 141 | { 142 | sortedSet.Add(Random.Shared.Next()); 143 | } 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkMap.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | 4 | namespace Benchmark.Benchmarks; 5 | 6 | [ShortRunJob] 7 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 8 | [CategoriesColumn] 9 | public class BenchmarkMap 10 | { 11 | [Params(128,10000,100000)] 12 | public int KeyCount { get; set; } 13 | 14 | private System.Collections.Generic.List input; 15 | private NativeCollection.Map nativeMap; 16 | private SortedDictionary managedMap; 17 | 18 | [GlobalSetup(Targets = new []{nameof(NativeAddAndRemove),nameof(ManagedAddAndRemove)})] 19 | public void InitAddAndRemove() 20 | { 21 | nativeMap = new NativeCollection.Map(1000); 22 | managedMap = new SortedCollection(); 23 | input = new System.Collections.Generic.List(); 24 | for (int i = 0; i < KeyCount; i++) 25 | { 26 | input.Add(i); 27 | } 28 | foreach (var key in input) 29 | { 30 | nativeMap.Add(key,key); 31 | } 32 | foreach (var key in input) 33 | { 34 | managedMap.Add(key,key); 35 | } 36 | } 37 | 38 | [BenchmarkCategory("AddAndRemove")] 39 | [Benchmark] 40 | public void NativeAddAndRemove() 41 | { 42 | for (int m = KeyCount; m (1000); 72 | managedMap = new SortedCollection(); 73 | input = new System.Collections.Generic.List(); 74 | for (int i = 0; i < KeyCount; i++) 75 | { 76 | input.Add(i); 77 | } 78 | foreach (var key in input) 79 | { 80 | nativeMap.Add(key,key); 81 | } 82 | foreach (var key in input) 83 | { 84 | managedMap.Add(key,key); 85 | } 86 | } 87 | 88 | [BenchmarkCategory("EnumerateAll")] 89 | [Benchmark] 90 | public void NativeEnumerateAll() 91 | { 92 | for (int i = 0; i < 10; i++) 93 | { 94 | foreach (var pair in nativeMap) 95 | { 96 | var value = pair.Value; 97 | } 98 | } 99 | } 100 | 101 | 102 | [BenchmarkCategory("EnumerateAll")] 103 | [Benchmark(Baseline = true)] 104 | public void ManagedEnumerateAll() 105 | { 106 | 107 | for (int i = 0; i < 10; i++) 108 | { 109 | foreach (var pair in managedMap) 110 | { 111 | var value = pair.Value; 112 | } 113 | } 114 | } 115 | 116 | [BenchmarkCategory("EnumerateFirst10")] 117 | [Benchmark] 118 | public void NativeEnumerateAndRemoveFirst10() 119 | { 120 | using var enumerator = nativeMap.GetEnumerator(); 121 | Span list = stackalloc int[10]; 122 | int index = 0; 123 | while (enumerator.MoveNext()&&index<10) 124 | { 125 | var pair = enumerator.Current; 126 | list[index] = pair.Key; 127 | index++; 128 | } 129 | 130 | foreach (var i in list) 131 | { 132 | nativeMap.Remove(i); 133 | } 134 | } 135 | 136 | [BenchmarkCategory("EnumerateFirst10")] 137 | [Benchmark(Baseline = true)] 138 | public void ManagedEnumerateAndRemoveFirst10() 139 | { 140 | using var enumerator = managedMap.GetEnumerator(); 141 | Span list = stackalloc int[10]; 142 | int index = 0; 143 | while (enumerator.MoveNext()&&index<10) 144 | { 145 | var pair = enumerator.Current; 146 | list[index] = pair.Key; 147 | index++; 148 | } 149 | 150 | foreach (var i in list) 151 | { 152 | managedMap.Remove(i); 153 | } 154 | } 155 | 156 | [GlobalCleanup] 157 | public void Dispose() 158 | { 159 | nativeMap.Dispose(); 160 | } 161 | } -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkUnOrderMap.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | 4 | namespace Benchmark.Benchmarks; 5 | [ShortRunJob] 6 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 7 | [CategoriesColumn] 8 | public class BenchmarkUnOrderMap 9 | { 10 | [Params(100,1000,10000,100000,1000000)] 11 | public int KeyCount { get; set; } 12 | 13 | private System.Collections.Generic.List input; 14 | private NativeCollection.UnOrderMap nativeMap; 15 | private Dictionary managedMap; 16 | 17 | [GlobalSetup(Targets = new []{nameof(NativeAddAndRemove),nameof(ManagedAddAndRemove)})] 18 | public void InitAddAndRemove() 19 | { 20 | nativeMap = new NativeCollection.UnOrderMap(1000); 21 | managedMap = new Dictionary(); 22 | input = new System.Collections.Generic.List(); 23 | for (int i = 0; i < KeyCount; i++) 24 | { 25 | input.Add(i); 26 | } 27 | foreach (var key in input) 28 | { 29 | nativeMap.Add(key,key); 30 | } 31 | foreach (var key in input) 32 | { 33 | managedMap.Add(key,key); 34 | } 35 | } 36 | 37 | [BenchmarkCategory("AddAndRemove")] 38 | [Benchmark] 39 | public void NativeAddAndRemove() 40 | { 41 | for (int m = KeyCount; m (1000); 71 | managedMap = new Dictionary(); 72 | input = new System.Collections.Generic.List(); 73 | for (int i = 0; i < KeyCount; i++) 74 | { 75 | input.Add(i); 76 | } 77 | foreach (var key in input) 78 | { 79 | nativeMap.Add(key,key); 80 | } 81 | foreach (var key in input) 82 | { 83 | managedMap.Add(key,key); 84 | } 85 | } 86 | 87 | [BenchmarkCategory("EnumerateAll")] 88 | [Benchmark] 89 | public void NativeEnumerateAll() 90 | { 91 | for (int i = 0; i < 10; i++) 92 | { 93 | foreach (var pair in nativeMap) 94 | { 95 | var value = pair.Value; 96 | } 97 | } 98 | } 99 | 100 | 101 | [BenchmarkCategory("EnumerateAll")] 102 | [Benchmark(Baseline = true)] 103 | public void ManagedEnumerateAll() 104 | { 105 | 106 | for (int i = 0; i < 10; i++) 107 | { 108 | foreach (var pair in managedMap) 109 | { 110 | var value = pair.Value; 111 | } 112 | } 113 | } 114 | 115 | [BenchmarkCategory("EnumerateFirst10")] 116 | [Benchmark] 117 | public void NativeEnumerateAndRemoveFirst10() 118 | { 119 | using var enumerator = nativeMap.GetEnumerator(); 120 | Span list = stackalloc int[10]; 121 | int index = 0; 122 | while (enumerator.MoveNext()&&index<10) 123 | { 124 | var pair = enumerator.Current; 125 | list[index] = pair.Key; 126 | index++; 127 | } 128 | 129 | foreach (var i in list) 130 | { 131 | nativeMap.Remove(i); 132 | } 133 | } 134 | 135 | [BenchmarkCategory("EnumerateFirst10")] 136 | [Benchmark(Baseline = true)] 137 | public void ManagedEnumerateAndRemoveFirst10() 138 | { 139 | using var enumerator = managedMap.GetEnumerator(); 140 | Span list = stackalloc int[10]; 141 | int index = 0; 142 | while (enumerator.MoveNext()&&index<10) 143 | { 144 | var pair = enumerator.Current; 145 | list[index] = pair.Key; 146 | index++; 147 | } 148 | 149 | foreach (var i in list) 150 | { 151 | managedMap.Remove(i); 152 | } 153 | } 154 | 155 | [GlobalCleanup] 156 | public void Dispose() 157 | { 158 | nativeMap.Dispose(); 159 | } 160 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/Queue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace NativeCollection.UnsafeType 5 | { 6 | public unsafe struct Queue : IDisposable where T : unmanaged 7 | { 8 | private T* _array; 9 | private int length; 10 | 11 | private int _head; 12 | private int _tail; 13 | private int _version; 14 | 15 | public static Queue* Create(int capacity = 10) 16 | { 17 | if (capacity < 0) throw new ArgumentOutOfRangeException("Capacity<0"); 18 | 19 | var queue = (Queue*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 20 | queue->_array = (T*)NativeMemoryHelper.Alloc((UIntPtr)capacity, (UIntPtr)Unsafe.SizeOf()); 21 | queue->length = capacity; 22 | queue->_head = 0; 23 | queue->_tail = 0; 24 | queue->Count = 0; 25 | queue->_version = 0; 26 | return queue; 27 | } 28 | 29 | public int Count { get; private set; } 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public void Clear() 33 | { 34 | if (Count != 0) Count = 0; 35 | _head = 0; 36 | _tail = 0; 37 | ++_version; 38 | } 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public void Enqueue(in T item) 42 | { 43 | if (Count == length) 44 | Grow(Count + 1); 45 | _array[_tail] = item; 46 | MoveNext(ref _tail); 47 | ++Count; 48 | ++_version; 49 | } 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public T Dequeue() 53 | { 54 | var head = _head; 55 | var array = _array; 56 | if (Count == 0) 57 | ThrowHelper.QueueEmptyException(); 58 | 59 | var obj = array[head]; 60 | MoveNext(ref _head); 61 | --Count; 62 | ++_version; 63 | return obj; 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public bool TryDequeue(out T result) 68 | { 69 | var head = _head; 70 | var array = _array; 71 | if (Count == 0) 72 | { 73 | result = default; 74 | return false; 75 | } 76 | 77 | result = array[head]; 78 | MoveNext(ref _head); 79 | --Count; 80 | ++_version; 81 | return true; 82 | } 83 | 84 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 | public T Peek() 86 | { 87 | if (Count == 0) 88 | ThrowHelper.QueueEmptyException(); 89 | return _array[_head]; 90 | } 91 | 92 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 93 | public bool TryPeek(out T result) 94 | { 95 | if (Count == 0) 96 | { 97 | result = default; 98 | return false; 99 | } 100 | 101 | result = _array[_head]; 102 | return true; 103 | } 104 | 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | private void Grow(int capacity) 107 | { 108 | var val1 = 2 * length; 109 | if ((uint)val1 > 2147483591U) 110 | val1 = 2147483591; 111 | var capacity1 = Math.Max(val1, length + 4); 112 | if (capacity1 < capacity) 113 | capacity1 = capacity; 114 | SetCapacity(capacity1); 115 | } 116 | 117 | private void SetCapacity(int capacity) 118 | { 119 | var destinationArray = (T*)NativeMemoryHelper.Alloc((UIntPtr)capacity, (UIntPtr)Unsafe.SizeOf()); 120 | if (Count > 0) 121 | { 122 | if (_head < _tail) 123 | { 124 | Unsafe.CopyBlockUnaligned(destinationArray, _array + _head, (uint)(Count * Unsafe.SizeOf())); 125 | } 126 | else 127 | { 128 | Unsafe.CopyBlockUnaligned(destinationArray, _array + _head, 129 | (uint)((length - _head) * Unsafe.SizeOf())); 130 | Unsafe.CopyBlockUnaligned(destinationArray + length - _head, _array, 131 | (uint)(_tail * Unsafe.SizeOf())); 132 | } 133 | } 134 | 135 | NativeMemoryHelper.Free(_array); 136 | NativeMemoryHelper.RemoveNativeMemoryByte(length * Unsafe.SizeOf()); 137 | _array = destinationArray; 138 | _head = 0; 139 | _tail = Count == capacity ? 0 : Count; 140 | length = capacity; 141 | ++_version; 142 | } 143 | 144 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 145 | private void MoveNext(ref int index) 146 | { 147 | var num = index + 1; 148 | if (num == length) 149 | num = 0; 150 | index = num; 151 | } 152 | 153 | public void Dispose() 154 | { 155 | if (_array!=null) 156 | { 157 | NativeMemoryHelper.Free(_array); 158 | NativeMemoryHelper.RemoveNativeMemoryByte(length * Unsafe.SizeOf()); 159 | } 160 | } 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/MultiMap/MultiMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace NativeCollection.UnsafeType 7 | { 8 | public unsafe struct MultiMap : IEnumerable>, IDisposable 9 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 10 | { 11 | private UnsafeType.SortedSet>* _sortedSet; 12 | 13 | private FixedSizeMemoryPool* _listMemoryPool; 14 | 15 | private NativeStackPool>* _listStackPool; 16 | 17 | public static MultiMap* Create(int poolBlockSize,int listPoolSize) 18 | { 19 | MultiMap* multiMap = (MultiMap*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 20 | multiMap->_sortedSet = UnsafeType.SortedSet>.Create(poolBlockSize); 21 | multiMap->_listMemoryPool = FixedSizeMemoryPool.Create(poolBlockSize,Unsafe.SizeOf>()); 22 | multiMap->_listStackPool = NativeStackPool>.Create(listPoolSize); 23 | return multiMap; 24 | } 25 | 26 | public Span this[T key] { 27 | get 28 | { 29 | var list = new MultiMapPair(key); 30 | var node = _sortedSet->FindNode(list); 31 | if (node!=null) 32 | { 33 | return node->Item.Value.WrittenSpan(); 34 | } 35 | return Span.Empty; 36 | } 37 | } 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public void Add(in T key,in K value) 41 | { 42 | var list = new MultiMapPair(key); 43 | var node = _sortedSet->FindNode(list); 44 | if (node != null) 45 | { 46 | list = node->Item; 47 | } 48 | else 49 | { 50 | list = MultiMapPair.Create(key,_listMemoryPool,_listStackPool); 51 | _sortedSet->AddRef(list); 52 | } 53 | list.Value.AddRef(value); 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public bool Remove(in T key,in K value) 58 | { 59 | var list = new MultiMapPair(key); 60 | var node = _sortedSet->FindNode(list); 61 | 62 | if (node == null) return false; 63 | list = node->Item; 64 | if (!list.Value.RemoveRef(value)) return false; 65 | 66 | if (list.Value.Count == 0) Remove(key); 67 | 68 | return true; 69 | } 70 | 71 | 72 | 73 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 74 | public bool Remove(in T key) 75 | { 76 | var list = new MultiMapPair(key); 77 | SortedSet>.Node* node = _sortedSet->FindNode(list); 78 | 79 | if (node == null) return false; 80 | list = node->Item; 81 | var sortedSetRemove = _sortedSet->RemoveRef(list); 82 | list.Dispose(_listMemoryPool,_listStackPool); 83 | return sortedSetRemove; 84 | } 85 | 86 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 | public void Clear() 88 | { 89 | using var enumerator = GetEnumerator(); 90 | do 91 | { 92 | if (enumerator.CurrentPointer != null) 93 | { 94 | enumerator.CurrentPointer->Item.Dispose(_listMemoryPool,_listStackPool); 95 | } 96 | } while (enumerator.MoveNext()); 97 | 98 | List* list = _listStackPool->Alloc(); 99 | while (list!=null) 100 | { 101 | list->Dispose(); 102 | _listMemoryPool->Free(list); 103 | list = _listStackPool->Alloc(); 104 | } 105 | _sortedSet->Clear(); 106 | _listMemoryPool->ReleaseUnUsedSlabs(); 107 | } 108 | 109 | public int Count => _sortedSet->Count; 110 | 111 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 112 | IEnumerator> IEnumerable>.GetEnumerator() 113 | { 114 | return _sortedSet->GetEnumerator(); 115 | } 116 | 117 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 118 | IEnumerator IEnumerable.GetEnumerator() 119 | { 120 | return _sortedSet->GetEnumerator(); 121 | } 122 | 123 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 124 | public UnsafeType.SortedSet>.Enumerator GetEnumerator() 125 | { 126 | return new UnsafeType.SortedSet>.Enumerator(_sortedSet); 127 | } 128 | 129 | public void Dispose() 130 | { 131 | if (_sortedSet != null) 132 | { 133 | Clear(); 134 | _sortedSet->Dispose(); 135 | NativeMemoryHelper.Free(_sortedSet); 136 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>>()); 137 | } 138 | 139 | if (_listStackPool!=null) 140 | { 141 | _listStackPool->Dispose(); 142 | NativeMemoryHelper.Free(_listStackPool); 143 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>>()); 144 | } 145 | 146 | if (_listMemoryPool!=null) 147 | { 148 | _listMemoryPool->Dispose(); 149 | _listMemoryPool = null; 150 | } 151 | } 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /NativeCollection/MemoryProfile/MemoryLeakTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NativeCollection; 3 | using Xunit; 4 | 5 | namespace MemoryProfile; 6 | 7 | public class MemoryLeakTest 8 | { 9 | [Fact] 10 | public void ListMemoryLeak() 11 | { 12 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 13 | 14 | NativeCollection.List list = new NativeCollection.List(); 15 | 16 | list.Clear(); 17 | 18 | for (int i = 0; i < 100; i++) 19 | { 20 | for (int j = 0; j < 10; j++) 21 | { 22 | list.Add(i); 23 | } 24 | for (int j = 0; j < 10; j++) 25 | { 26 | list.Remove(i); 27 | } 28 | } 29 | list.Clear(); 30 | 31 | list.Dispose(); 32 | 33 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 34 | memory.Should().Be(initMemory); 35 | } 36 | 37 | [Fact] 38 | public void QueueMemoryLeak() 39 | { 40 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 41 | 42 | NativeCollection.Queue queue = new NativeCollection.Queue(); 43 | 44 | queue.Clear(); 45 | 46 | for (int i = 0; i < 100; i++) 47 | { 48 | for (int j = 0; j < 10; j++) 49 | { 50 | queue.Enqueue(i); 51 | } 52 | for (int j = 0; j < 10; j++) 53 | { 54 | queue.Dequeue(); 55 | } 56 | } 57 | queue.Clear(); 58 | 59 | queue.Dispose(); 60 | 61 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 62 | memory.Should().Be(initMemory); 63 | } 64 | 65 | [Fact] 66 | public void StackMemoryLeak() 67 | { 68 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 69 | 70 | NativeCollection.Stack stack = new NativeCollection.Stack(); 71 | 72 | stack.Clear(); 73 | 74 | for (int i = 0; i < 100; i++) 75 | { 76 | for (int j = 0; j < 10; j++) 77 | { 78 | stack.Push(i); 79 | } 80 | for (int j = 0; j < 10; j++) 81 | { 82 | stack.Pop(); 83 | } 84 | } 85 | stack.Clear(); 86 | 87 | stack.Dispose(); 88 | 89 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 90 | memory.Should().Be(initMemory); 91 | } 92 | 93 | [Fact] 94 | public void SortedSetMemoryLeak() 95 | { 96 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 97 | 98 | NativeCollection.SortedSet sortedSet = new NativeCollection.SortedSet(); 99 | 100 | for (int i = 0; i < 1000; i++) 101 | { 102 | for (int j = 0; j < 10; j++) 103 | { 104 | sortedSet.Add(i); 105 | } 106 | } 107 | //sortedSet.Clear(); 108 | 109 | sortedSet.Dispose(); 110 | 111 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 112 | memory.Should().Be(initMemory); 113 | } 114 | 115 | [Fact] 116 | public void MultiMapMemoryLeak() 117 | { 118 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 119 | 120 | MultiMap multiMap = new (1000); 121 | 122 | multiMap.Clear(); 123 | 124 | for (int i = 0; i < 129; i++) 125 | { 126 | for (int j = 0; j < 10; j++) 127 | { 128 | multiMap.Add(i,j); 129 | } 130 | } 131 | multiMap.Clear(); 132 | 133 | multiMap.Dispose(); 134 | 135 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 136 | memory.Should().Be(initMemory); 137 | 138 | multiMap.ReInit(); 139 | 140 | for (int i = 0; i < 100; i++) 141 | { 142 | for (int j = 0; j < 10; j++) 143 | { 144 | multiMap.Add(i,j); 145 | } 146 | } 147 | multiMap.Clear(); 148 | multiMap.Dispose(); 149 | memory = NativeMemoryHelper.GetNativeMemoryBytes(); 150 | memory.Should().Be(initMemory); 151 | } 152 | 153 | [Fact] 154 | public void MapMemoryLeak() 155 | { 156 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 157 | 158 | Map map = new (); 159 | 160 | map.Clear(); 161 | 162 | for (int i = 0; i < 100; i++) 163 | { 164 | for (int j = 0; j < 10; j++) 165 | { 166 | map.Add(i,j); 167 | } 168 | } 169 | map.Clear(); 170 | 171 | map.Dispose(); 172 | 173 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 174 | memory.Should().Be(initMemory); 175 | } 176 | 177 | [Fact] 178 | public void HashSetMemoryLeak() 179 | { 180 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 181 | 182 | NativeCollection.HashSet hashSet = new (); 183 | 184 | hashSet.Clear(); 185 | 186 | for (int i = 0; i < 1000; i++) 187 | { 188 | hashSet.Add(i); 189 | 190 | } 191 | hashSet.Clear(); 192 | 193 | hashSet.Dispose(); 194 | 195 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 196 | memory.Should().Be(initMemory); 197 | } 198 | 199 | [Fact] 200 | public void UnOrderMapMemoryLeak() 201 | { 202 | var initMemory = NativeMemoryHelper.GetNativeMemoryBytes(); 203 | 204 | NativeCollection.UnOrderMap unOrderMap = new (); 205 | 206 | unOrderMap.Clear(); 207 | 208 | for (int i = 0; i < 10000; i++) 209 | { 210 | unOrderMap.Add(i,1); 211 | 212 | } 213 | for (int i = 0; i < 10000; i++) 214 | { 215 | unOrderMap.Remove(i); 216 | } 217 | 218 | unOrderMap.Clear(); 219 | 220 | unOrderMap.Dispose(); 221 | 222 | var memory = NativeMemoryHelper.GetNativeMemoryBytes(); 223 | memory.Should().Be(initMemory); 224 | } 225 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/FixedSizeMemoryPool/Slab.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace NativeCollection 7 | { 8 | public unsafe partial struct FixedSizeMemoryPool 9 | { 10 | public struct Slab : IDisposable 11 | { 12 | public int BlockSize; 13 | 14 | public int FreeSize; 15 | 16 | public int ItemSize; 17 | 18 | public ListNode* FreeList; 19 | 20 | public Slab* Prev; 21 | 22 | public Slab* Next; 23 | 24 | public Slab* Self 25 | { 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | get { return (Slab*)Unsafe.AsPointer(ref this); } 28 | } 29 | 30 | public static Slab* Create(int blockSize,int itemSize,Slab* prevSlab , Slab* nextSlab ) 31 | { 32 | int size = itemSize + Unsafe.SizeOf(); 33 | int slabSize =Unsafe.SizeOf() + size * blockSize; 34 | byte* slabBuffer = (byte*)NativeMemoryHelper.Alloc((UIntPtr)slabSize); 35 | Slab* slab = (Slab*)slabBuffer; 36 | slab->BlockSize = blockSize; 37 | slab->FreeSize = blockSize; 38 | slab->ItemSize = itemSize; 39 | slab->Prev = prevSlab; 40 | slab->Next = nextSlab; 41 | slabBuffer+=Unsafe.SizeOf(); 42 | 43 | ListNode* next = null; 44 | 45 | for (int i = blockSize-1; i >= 0; i--) 46 | { 47 | ListNode* listNode = (ListNode*)(slabBuffer + i*size); 48 | listNode->Next = next; 49 | next = listNode; 50 | } 51 | 52 | slab->FreeList = next; 53 | return slab; 54 | } 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public byte* Alloc() 57 | { 58 | Debug.Assert(FreeList!=null && FreeSize>0); 59 | FreeSize--; 60 | ListNode* node = FreeList; 61 | FreeList = FreeList->Next; 62 | node->ParentSlab = Self; 63 | node += 1; 64 | return (byte*)node; 65 | } 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public void Free(ListNode* node) 68 | { 69 | Debug.Assert(FreeSizeNext = FreeList; 72 | FreeList = node; 73 | } 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public bool IsAllFree() 76 | { 77 | return FreeSize == BlockSize; 78 | } 79 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 80 | public bool IsAllAlloc() 81 | { 82 | return FreeSize == 0; 83 | } 84 | 85 | public void Dispose() 86 | { 87 | int slabSize =Unsafe.SizeOf() + (ItemSize + Unsafe.SizeOf()) * BlockSize; 88 | Slab* self = Self; 89 | NativeMemoryHelper.Free(self); 90 | NativeMemoryHelper.RemoveNativeMemoryByte(slabSize); 91 | } 92 | } 93 | 94 | [StructLayout(LayoutKind.Explicit)] 95 | public struct ListNode 96 | { 97 | [FieldOffset(0)] 98 | public ListNode* Next; 99 | [FieldOffset(0)] 100 | public Slab* ParentSlab; 101 | } 102 | 103 | public struct SlabLinkedList 104 | { 105 | public int SlabCount; 106 | public Slab* Top; 107 | public Slab* Bottom; 108 | 109 | public SlabLinkedList(Slab* initSlab) 110 | { 111 | Top = initSlab; 112 | Bottom = initSlab; 113 | SlabCount = initSlab==null?0:1; 114 | } 115 | 116 | public void MoveTopToBottom() 117 | { 118 | Debug.Assert(Top!=null && Bottom!=null); 119 | if (Top==Bottom) 120 | { 121 | return; 122 | } 123 | 124 | Slab* oldTop = Top; 125 | Top = Top->Next; 126 | Top->Prev = null; 127 | Bottom->Next = oldTop; 128 | oldTop->Prev = Bottom; 129 | oldTop->Next = null; 130 | Bottom = oldTop; 131 | } 132 | 133 | public void SplitOut(Slab* splitSlab) 134 | { 135 | Debug.Assert(splitSlab!=null && Top!=null && Bottom!=null); 136 | 137 | SlabCount--; 138 | // 只有一个slab 139 | if (Top==Bottom) 140 | { 141 | splitSlab->Prev = null; 142 | splitSlab->Next = null; 143 | Top = null; 144 | Bottom = null; 145 | return; 146 | } 147 | 148 | // 链表头部 149 | if (splitSlab == Top) 150 | { 151 | Top = splitSlab->Next; 152 | splitSlab->Next = null; 153 | Top->Prev = null; 154 | return; 155 | } 156 | 157 | if (splitSlab == Bottom) 158 | { 159 | Bottom = splitSlab->Prev; 160 | Bottom->Next = null; 161 | splitSlab->Prev = null; 162 | return; 163 | } 164 | 165 | var prev = splitSlab->Prev; 166 | var next = splitSlab->Next; 167 | prev->Next = next; 168 | next->Prev = prev; 169 | splitSlab->Prev = null; 170 | splitSlab->Next = null; 171 | } 172 | 173 | public void AddToTop(Slab* slab) 174 | { 175 | SlabCount++; 176 | if (Top == Bottom) 177 | { 178 | if (Top==null) 179 | { 180 | Top = slab; 181 | Bottom = slab; 182 | slab->Prev = null; 183 | slab->Next = null; 184 | } 185 | else 186 | { 187 | Slab* oldTop = Top; 188 | Top = slab; 189 | Top->Next = oldTop; 190 | Top->Prev = null; 191 | oldTop->Prev = Top; 192 | } 193 | return; 194 | } 195 | 196 | Slab* oldSlab = Top; 197 | Top = slab; 198 | Top->Next = oldSlab; 199 | Top->Prev = null; 200 | oldSlab->Prev = Top; 201 | } 202 | } 203 | } 204 | } 205 | 206 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection.Test/ListTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace NativeCollection.Test; 5 | 6 | public class ListTest 7 | { 8 | [Fact] 9 | public void AddRemove() 10 | { 11 | List nativeList = new List(); 12 | System.Collections.Generic.List managedList = new System.Collections.Generic.List(); 13 | 14 | nativeList.Remove(1).Should().Be(false); 15 | 16 | nativeList.Add(1); 17 | nativeList.Remove(1).Should().Be(true); 18 | int count = 1000; 19 | 20 | for (int i = 0; i < count; i++) 21 | { 22 | int value = Random.Shared.Next(); 23 | nativeList.Add(value); 24 | managedList.Add(value); 25 | nativeList.Count.Should().Be(i + 1); 26 | nativeList.Contains(value).Should().Be(true); 27 | } 28 | 29 | for (int i = count-1; i >= 0; i--) 30 | { 31 | nativeList[i].Should().Be(managedList[i]); 32 | 33 | int value = nativeList[i]; 34 | nativeList.Remove(value); 35 | managedList.Remove(value); 36 | nativeList.Count.Should().Be(managedList.Count); 37 | } 38 | 39 | nativeList.Count.Should().Be(0); 40 | 41 | } 42 | 43 | [Fact] 44 | public void RemoveAt() 45 | { 46 | List nativeList = new List(); 47 | for (int i = 0; i < 1000; i++) 48 | { 49 | nativeList.Add(i); 50 | } 51 | 52 | nativeList.RemoveAt(10); 53 | nativeList.Count.Should().Be(999); 54 | nativeList[10].Should().Be(11); 55 | 56 | nativeList.RemoveAt(998); 57 | nativeList.Count.Should().Be(998); 58 | nativeList[997].Should().Be(998); 59 | 60 | nativeList.Clear(); 61 | 62 | for (int i = -100; i < 100; i++) 63 | { 64 | bool hasException = false; 65 | try 66 | { 67 | nativeList.RemoveAt(0); 68 | } 69 | catch (Exception e) 70 | { 71 | hasException = true; 72 | } 73 | 74 | hasException.Should().Be(true); 75 | } 76 | } 77 | 78 | [Fact] 79 | public void Insert() 80 | { 81 | List list = new List(); 82 | list.Add(1); 83 | list.Add(2); 84 | list.Add(3); 85 | 86 | list.Insert(1,10); 87 | list[0].Should().Be(1); 88 | list[1].Should().Be(10); 89 | list[2].Should().Be(2); 90 | list[3].Should().Be(3); 91 | list.Count.Should().Be(4); 92 | } 93 | 94 | [Fact] 95 | public void InsertRange() 96 | { 97 | List list = new List(); 98 | list.Add(1); 99 | list.Add(2); 100 | list.Add(3); 101 | list.Add(4); 102 | list.Add(5); 103 | Span range = stackalloc int[] { 11, 12, 13 }; 104 | list.InsertRange(2,range); 105 | 106 | list[0].Should().Be(1); 107 | list[1].Should().Be(2); 108 | list[2].Should().Be(11); 109 | list[3].Should().Be(12); 110 | list[4].Should().Be(13); 111 | list[5].Should().Be(3); 112 | list[6].Should().Be(4); 113 | list[7].Should().Be(5); 114 | list.Count.Should().Be(8); 115 | } 116 | 117 | [Fact] 118 | public void RemoveRange() 119 | { 120 | List list = new List(); 121 | list.Add(1); 122 | list.Add(2); 123 | list.Add(3); 124 | list.Add(4); 125 | list.Add(5); 126 | 127 | list.RemoveRange(1,2); 128 | list[0].Should().Be(1); 129 | list[1].Should().Be(4); 130 | list[2].Should().Be(5); 131 | list.Count.Should().Be(3); 132 | } 133 | 134 | 135 | [Fact] 136 | public void Enumerator() 137 | { 138 | List nativeList = new List(); 139 | System.Collections.Generic.List managedList = new System.Collections.Generic.List(); 140 | 141 | for (int i = 0; i < 10000; i++) 142 | { 143 | int value = Random.Shared.Next(); 144 | nativeList.Add(value); 145 | managedList.Add(value); 146 | } 147 | 148 | var nativeEnumerator = nativeList.GetEnumerator(); 149 | var managedEnumerator = managedList.GetEnumerator(); 150 | while (managedEnumerator.MoveNext()) 151 | { 152 | nativeEnumerator.MoveNext(); 153 | nativeEnumerator.Current.Should().Be(managedEnumerator.Current); 154 | } 155 | } 156 | 157 | 158 | [Fact] 159 | public void Contains() 160 | { 161 | List nativeList = new List(); 162 | System.Collections.Generic.List managedList = new System.Collections.Generic.List(); 163 | 164 | for (int i = 0; i < 10000; i++) 165 | { 166 | int value = Random.Shared.Next(0,10000); 167 | nativeList.Add(value); 168 | managedList.Add(value); 169 | } 170 | 171 | for (int i = 0; i < 10000; i++) 172 | { 173 | int value = Random.Shared.Next(0,10000); 174 | nativeList.Contains(value).Should().Be(managedList.Contains(value)); 175 | } 176 | } 177 | 178 | 179 | [Fact] 180 | public void IndexOf() 181 | { 182 | List nativeList = new List(); 183 | for (int i = 0; i < 1000; i++) 184 | { 185 | nativeList.Add(i); 186 | } 187 | 188 | for (int i = 0; i < 1000; i++) 189 | { 190 | nativeList.IndexOf(i).Should().Be(i); 191 | } 192 | } 193 | 194 | [Fact] 195 | public void Clear() 196 | { 197 | List nativeList = new List(); 198 | for (int i = 0; i < 100; i++) 199 | { 200 | for (int j = 0; j < Random.Shared.Next(1,9999); j++) 201 | { 202 | nativeList.Add(Random.Shared.Next()); 203 | } 204 | nativeList.Clear(); 205 | nativeList.Count.Should().Be(0); 206 | } 207 | } 208 | 209 | [Fact] 210 | public void RefIndex() 211 | { 212 | List nativeList = new List(); 213 | nativeList.Add(123); 214 | nativeList.Add(456); 215 | nativeList[0] = 789; 216 | nativeList[0].Should().Be(789); 217 | 218 | ref var value = ref nativeList[0]; 219 | value = 999; 220 | nativeList[0].Should().Be(999); 221 | 222 | nativeList[0] = 0; 223 | value.Should().Be(0); 224 | } 225 | 226 | [Fact] 227 | public void CopyTo() 228 | { 229 | int[] array = new int[100]; 230 | List list = new List(); 231 | for (int i = 0; i < 100; i++) 232 | { 233 | list.Add(i); 234 | } 235 | 236 | list.CopyTo(array,0); 237 | 238 | for (int i = 0; i < 100; i++) 239 | { 240 | array[i].Should().Be(i); 241 | } 242 | 243 | } 244 | 245 | [Fact] 246 | public void NativeCollectionClass() 247 | { 248 | List nativeList = new List(); 249 | nativeList.IsDisposed.Should().Be(false); 250 | for (int i = 0; i < 100; i++) 251 | { 252 | nativeList.Add(Random.Shared.Next()); 253 | } 254 | nativeList.Dispose(); 255 | nativeList.IsDisposed.Should().Be(true); 256 | nativeList.ReInit(); 257 | nativeList.IsDisposed.Should().Be(false); 258 | nativeList.Count.Should().Be(0); 259 | for (int i = 0; i < 100; i++) 260 | { 261 | nativeList.Add(Random.Shared.Next()); 262 | } 263 | } 264 | } -------------------------------------------------------------------------------- /NativeCollection/Benchmark/Benchmarks/BenchmarkMultiMap.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Configs; 3 | using NativeCollection.UnsafeType; 4 | 5 | namespace Benchmark.Benchmarks; 6 | [ShortRunJob] 7 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 8 | [CategoriesColumn] 9 | public class BenchmarkMultiMap 10 | { 11 | [Params(100000)] 12 | public int KeyCount { get; set; } 13 | public int ValueCount { get; set; } 14 | private System.Collections.Generic.List input; 15 | private NativeCollection.MultiMap nativeMultiMap; 16 | private MultiMap managedMultiMap; 17 | 18 | [GlobalSetup(Targets = new []{nameof(NativeAddAndRemove),nameof(ManagedAddAndRemove)})] 19 | public void InitAddAndRemove() 20 | { 21 | nativeMultiMap = new NativeCollection.MultiMap(1000); 22 | managedMultiMap = new MultiMap(1000); 23 | ValueCount = 5; 24 | input = new System.Collections.Generic.List(); 25 | for (int i = 0; i < KeyCount; i++) 26 | { 27 | input.Add(i); 28 | } 29 | foreach (var key in input) 30 | { 31 | for (int i = 0; i < ValueCount; i++) 32 | { 33 | nativeMultiMap.Add(key,i); 34 | } 35 | } 36 | foreach (var key in input) 37 | { 38 | for (int i = 0; i < ValueCount; i++) 39 | { 40 | managedMultiMap.Add(key,i); 41 | } 42 | } 43 | } 44 | 45 | [BenchmarkCategory("AddAndRemove")] 46 | [Benchmark] 47 | public void NativeAddAndRemove() 48 | { 49 | for (int m = KeyCount; m (1000); 80 | managedMultiMap = new MultiMap(1000); 81 | ValueCount = 5; 82 | input = new System.Collections.Generic.List(); 83 | for (int i = 0; i < KeyCount; i++) 84 | { 85 | input.Add(i); 86 | } 87 | foreach (var key in input) 88 | { 89 | for (int i = 0; i < ValueCount; i++) 90 | { 91 | nativeMultiMap.Add(key,i); 92 | } 93 | } 94 | foreach (var key in input) 95 | { 96 | for (int i = 0; i < ValueCount; i++) 97 | { 98 | managedMultiMap.Add(key,i); 99 | } 100 | } 101 | } 102 | 103 | [BenchmarkCategory("EnumerateAll")] 104 | [Benchmark] 105 | public void NativeEnumerateAll() 106 | { 107 | using var enumerator = nativeMultiMap.GetEnumerator(); 108 | while (enumerator.MoveNext()) 109 | { 110 | var pair = enumerator.Current; 111 | 112 | foreach (var listValue in pair.Value) 113 | { 114 | var value = listValue; 115 | } 116 | } 117 | } 118 | 119 | 120 | [BenchmarkCategory("EnumerateAll")] 121 | [Benchmark(Baseline = true)] 122 | public void ManagedEnumerateAll() 123 | { 124 | using var enumerator = managedMultiMap.GetEnumerator(); 125 | while (enumerator.MoveNext()) 126 | { 127 | var pair = enumerator.Current; 128 | foreach (var listValue in pair.Value) 129 | { 130 | var value = listValue; 131 | } 132 | } 133 | } 134 | 135 | 136 | [BenchmarkCategory("EnumerateFirst10")] 137 | [Benchmark] 138 | public void NativeEnumerateAndRemoveFirst10() 139 | { 140 | using var enumerator = nativeMultiMap.GetEnumerator(); 141 | Span list = stackalloc int[10]; 142 | int index = 0; 143 | while (enumerator.MoveNext()&&index<10) 144 | { 145 | var pair = enumerator.Current; 146 | list[index] = pair.Key; 147 | index++; 148 | foreach (var listValue in pair.Value) 149 | { 150 | var value = listValue; 151 | } 152 | } 153 | 154 | foreach (var i in list) 155 | { 156 | nativeMultiMap.Remove(i); 157 | } 158 | } 159 | 160 | [BenchmarkCategory("EnumerateFirst10")] 161 | [Benchmark(Baseline = true)] 162 | public void ManagedEnumerateAndRemoveFirst10() 163 | { 164 | using var enumerator = managedMultiMap.GetEnumerator(); 165 | Span list = stackalloc int[10]; 166 | int index = 0; 167 | while (enumerator.MoveNext()&&index<10) 168 | { 169 | var pair = enumerator.Current; 170 | list[index] = pair.Key; 171 | index++; 172 | foreach (var listValue in pair.Value) 173 | { 174 | var value = listValue; 175 | } 176 | } 177 | 178 | foreach (var i in list) 179 | { 180 | managedMultiMap.Remove(i); 181 | } 182 | } 183 | 184 | 185 | 186 | 187 | 188 | [GlobalCleanup] 189 | public void Dispose() 190 | { 191 | nativeMultiMap.Dispose(); 192 | } 193 | } 194 | 195 | public class SortedCollection: SortedDictionary 196 | { 197 | } 198 | 199 | public class MultiMap: SortedCollection> 200 | { 201 | private readonly System.Collections.Generic.List Empty = new(); 202 | private readonly int maxPoolCount; 203 | private readonly System.Collections.Generic.Queue> pool; 204 | 205 | public MultiMap(int maxPoolCount = 0) 206 | { 207 | this.maxPoolCount = maxPoolCount; 208 | this.pool = new System.Collections.Generic.Queue>(maxPoolCount); 209 | } 210 | 211 | private System.Collections.Generic.List FetchList() 212 | { 213 | if (this.pool.Count > 0) 214 | { 215 | return this.pool.Dequeue(); 216 | } 217 | return new System.Collections.Generic.List(10); 218 | } 219 | 220 | private void Recycle(System.Collections.Generic.List list) 221 | { 222 | if (list == null) 223 | { 224 | return; 225 | } 226 | if (this.pool.Count == this.maxPoolCount) 227 | { 228 | return; 229 | } 230 | list.Clear(); 231 | this.pool.Enqueue(list); 232 | } 233 | 234 | public void Add(T t, K k) 235 | { 236 | System.Collections.Generic.List list; 237 | this.TryGetValue(t, out list); 238 | if (list == null) 239 | { 240 | list = this.FetchList(); 241 | this.Add(t, list); 242 | } 243 | list.Add(k); 244 | } 245 | 246 | public bool Remove(T t, K k) 247 | { 248 | System.Collections.Generic.List list; 249 | this.TryGetValue(t, out list); 250 | if (list == null) 251 | { 252 | return false; 253 | } 254 | if (!list.Remove(k)) 255 | { 256 | return false; 257 | } 258 | if (list.Count == 0) 259 | { 260 | this.Remove(t); 261 | } 262 | return true; 263 | } 264 | 265 | public new bool Remove(T t) 266 | { 267 | System.Collections.Generic.List list; 268 | this.TryGetValue(t, out list); 269 | if (list == null) 270 | { 271 | return false; 272 | } 273 | this.Recycle(list); 274 | return base.Remove(t); 275 | } 276 | 277 | /// 278 | /// 不返回内部的list,copy一份出来 279 | /// 280 | /// 281 | /// 282 | public K[] GetAll(T t) 283 | { 284 | System.Collections.Generic.List list; 285 | this.TryGetValue(t, out list); 286 | if (list == null) 287 | { 288 | return Array.Empty(); 289 | } 290 | return list.ToArray(); 291 | } 292 | 293 | /// 294 | /// 返回内部的list 295 | /// 296 | /// 297 | /// 298 | public new System.Collections.Generic.List this[T t] 299 | { 300 | get 301 | { 302 | this.TryGetValue(t, out System.Collections.Generic.List list); 303 | return list ?? Empty; 304 | } 305 | } 306 | 307 | public K GetOne(T t) 308 | { 309 | System.Collections.Generic.List list; 310 | this.TryGetValue(t, out list); 311 | if (list != null && list.Count > 0) 312 | { 313 | return list[0]; 314 | } 315 | return default; 316 | } 317 | 318 | public bool Contains(T t, K k) 319 | { 320 | System.Collections.Generic.List list; 321 | this.TryGetValue(t, out list); 322 | if (list == null) 323 | { 324 | return false; 325 | } 326 | return list.Contains(k); 327 | } 328 | } -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/List.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace NativeCollection.UnsafeType 9 | { 10 | public unsafe struct List : ICollection, IDisposable, IPool where T : unmanaged, IEquatable 11 | { 12 | private List* self; 13 | 14 | private int _arrayLength; 15 | 16 | private T* _items; 17 | 18 | private const int _defaultCapacity = 4; 19 | 20 | public static List* Create(int initialCapacity = _defaultCapacity) 21 | { 22 | if (initialCapacity < 0) ThrowHelper.ListInitialCapacityException(); 23 | 24 | var list = (List*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 25 | 26 | if (initialCapacity < _defaultCapacity) 27 | initialCapacity = _defaultCapacity; // Simplify doubling logic in Push. 28 | 29 | list->_items = (T*)NativeMemoryHelper.Alloc((UIntPtr)initialCapacity, (UIntPtr)Unsafe.SizeOf()); 30 | list->_arrayLength = initialCapacity; 31 | list->Count = 0; 32 | list->self = list; 33 | return list; 34 | } 35 | 36 | public static List* AllocFromMemoryPool(FixedSizeMemoryPool* memoryPool,int initialCapacity = _defaultCapacity) 37 | { 38 | if (initialCapacity < 0) ThrowHelper.ListInitialCapacityException(); 39 | 40 | var list = (List*)memoryPool->Alloc(); 41 | 42 | if (initialCapacity < _defaultCapacity) 43 | initialCapacity = _defaultCapacity; // Simplify doubling logic in Push. 44 | 45 | list->_items = (T*)NativeMemoryHelper.Alloc((UIntPtr)initialCapacity, (UIntPtr)Unsafe.SizeOf()); 46 | list->_arrayLength = initialCapacity; 47 | list->Count = 0; 48 | list->self = list; 49 | return list; 50 | } 51 | 52 | 53 | public ref T this[int index] 54 | { 55 | get 56 | { 57 | if (index>=Count) 58 | { 59 | ThrowHelper.IndexMustBeLessException(); 60 | } 61 | return ref *(_items + index); 62 | } 63 | } 64 | 65 | public int Capacity 66 | { 67 | get => _arrayLength; 68 | set 69 | { 70 | if (value < Count) ThrowHelper.ListSmallCapacity(); 71 | 72 | if (value != _arrayLength) 73 | { 74 | if (value > 0) 75 | { 76 | var newArray = (T*)NativeMemoryHelper.Alloc((UIntPtr)value, (UIntPtr)Unsafe.SizeOf()); 77 | if (Count > 0) 78 | Unsafe.CopyBlockUnaligned(newArray, _items, (uint)(_arrayLength * Unsafe.SizeOf())); 79 | NativeMemoryHelper.Free(_items); 80 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf() * _arrayLength); 81 | _items = newArray; 82 | _arrayLength = value; 83 | } 84 | else 85 | { 86 | ThrowHelper.ListSmallCapacity(); 87 | } 88 | } 89 | } 90 | } 91 | 92 | 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 | public void Add(T value) 95 | { 96 | AddRef(value); 97 | } 98 | 99 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 100 | public void AddRef(T value) 101 | { 102 | var array = _items; 103 | var size = Count; 104 | if ((uint)size < (uint)_arrayLength) 105 | { 106 | Count = size + 1; 107 | array[size] = value; 108 | } 109 | else 110 | { 111 | AddWithResize(value); 112 | } 113 | } 114 | 115 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 116 | public void CopyTo(T[] array, int arrayIndex) 117 | { 118 | if (array==null) 119 | { 120 | ThrowHelper.ArgumentNullException("array"); 121 | } 122 | if (arrayIndex<0 &&arrayIndex >= array.Length) 123 | { 124 | ThrowHelper.ArgumentOutOfRangeException("arrayIndex"); 125 | } 126 | WrittenSpan().CopyTo(array.AsSpan(arrayIndex)); 127 | } 128 | 129 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 130 | public bool Remove(T item) 131 | { 132 | return RemoveRef(item); 133 | } 134 | 135 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 | public bool RemoveRef(in T item) 137 | { 138 | var index = IndexOf(item); 139 | //Console.WriteLine($"index: {index}"); 140 | if (index >= 0) 141 | { 142 | RemoveAt(index); 143 | return true; 144 | } 145 | 146 | return false; 147 | } 148 | 149 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 150 | public int IndexOf(in T item) 151 | { 152 | return new Span(_items, Count).IndexOf(item); 153 | } 154 | 155 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 156 | public void RemoveAt(int index) 157 | { 158 | if ((uint)index >= (uint)Count) ThrowHelper.IndexMustBeLessException(); 159 | Count--; 160 | if (index < Count) 161 | Unsafe.CopyBlockUnaligned(_items + index, _items + index + 1, (uint)((Count - index) * Unsafe.SizeOf())); 162 | } 163 | 164 | 165 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 166 | public void Clear() 167 | { 168 | Count = 0; 169 | } 170 | 171 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 172 | public bool Contains(T item) 173 | { 174 | return IndexOf(item) >= 0; 175 | } 176 | 177 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 178 | public void Insert(int index, T item) 179 | { 180 | // Note that insertions at the end are legal. 181 | if ((uint)index > (uint)Count) 182 | { 183 | ThrowHelper.IndexMustBeLessException(); 184 | } 185 | if (Count == _arrayLength) Grow(Count + 1); 186 | if (index < Count) 187 | { 188 | Unsafe.CopyBlockUnaligned(_items+index+1,_items+index,(uint)((Count-index)*Unsafe.SizeOf())); 189 | } 190 | _items[index] = item; 191 | Count++; 192 | } 193 | 194 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 195 | public void AddRange(Span collection) 196 | { 197 | InsertRange(Count, collection); 198 | } 199 | 200 | public void InsertRange(int index, Span collection) 201 | { 202 | 203 | if ((uint)index > (uint)Count) 204 | { 205 | ThrowHelper.ListIndexOutOfRange(); 206 | } 207 | 208 | int count = collection.Length; 209 | if (count > 0) 210 | { 211 | if (_arrayLength - Count < count) 212 | { 213 | Grow(Count + count); 214 | } 215 | if (index < Count) 216 | { 217 | Unsafe.CopyBlockUnaligned(_items+index+count,_items+index,(uint)((Count-index)*Unsafe.SizeOf())); 218 | } 219 | 220 | var span = TotalSpan(); 221 | var a = span[0]; 222 | 223 | collection.CopyTo(new Span(_items+index, count)); 224 | Count += count; 225 | } 226 | 227 | } 228 | 229 | public void RemoveRange(int index, int count) 230 | { 231 | if (index < 0) 232 | { 233 | ThrowHelper.ListIndexOutOfRange(); 234 | } 235 | 236 | if (count < 0) 237 | { 238 | ThrowHelper.ListIndexOutOfRange(); 239 | } 240 | 241 | if (Count - index < count) 242 | ThrowHelper.ListIndexOutOfRange(); 243 | 244 | if (count > 0) 245 | { 246 | Count -= count; 247 | if (index < Count) 248 | { 249 | Unsafe.CopyBlockUnaligned(_items+index,_items+index+count,(uint)((Count-index)*Unsafe.SizeOf())); 250 | } 251 | } 252 | } 253 | 254 | 255 | public void FillDefaultValue() 256 | { 257 | for (int i = 0; i < _arrayLength; i++) 258 | { 259 | _items[i] = default; 260 | } 261 | Count = _arrayLength; 262 | } 263 | 264 | public void ResizeWithDefaultValue(int newSize) 265 | { 266 | var size = _arrayLength; 267 | Capacity = newSize; 268 | for (int i = size; i < _arrayLength; i++) 269 | { 270 | _items[i] = default; 271 | } 272 | } 273 | 274 | public int Count { get; private set; } 275 | 276 | public bool IsReadOnly => false; 277 | 278 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 279 | private void AddWithResize(in T item) 280 | { 281 | var size = Count; 282 | Grow(size + 1); 283 | Count = size + 1; 284 | _items[size] = item; 285 | } 286 | 287 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 288 | private void Grow(int capacity) 289 | { 290 | Debug.Assert(_arrayLength < capacity); 291 | 292 | var newcapacity = _arrayLength == 0 ? _defaultCapacity : 2 * Count; 293 | 294 | // Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow. 295 | // Note that this check works even when _items.Length overflowed thanks to the (uint) cast 296 | if ((uint)newcapacity > 0X7FFFFFC7) newcapacity = 0X7FFFFFC7; 297 | 298 | // If the computed capacity is still less than specified, set to the original argument. 299 | // Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize. 300 | if (newcapacity < capacity) newcapacity = capacity; 301 | 302 | Capacity = newcapacity; 303 | } 304 | 305 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 306 | public Span WrittenSpan() 307 | { 308 | return new Span(_items, Count); 309 | } 310 | 311 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 312 | public Span TotalSpan() 313 | { 314 | return new Span(_items, _arrayLength); 315 | } 316 | 317 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 318 | public void Dispose() 319 | { 320 | NativeMemoryHelper.Free(_items); 321 | NativeMemoryHelper.RemoveNativeMemoryByte(_arrayLength * Unsafe.SizeOf()); 322 | } 323 | 324 | 325 | IEnumerator IEnumerable.GetEnumerator() 326 | { 327 | return GetEnumerator(); 328 | } 329 | 330 | IEnumerator IEnumerable.GetEnumerator() 331 | { 332 | return GetEnumerator(); 333 | } 334 | 335 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 336 | public Enumerator GetEnumerator() 337 | { 338 | return new Enumerator(self); 339 | } 340 | 341 | public override string ToString() 342 | { 343 | var sb = new StringBuilder(); 344 | foreach (var item in *self) sb.Append($"{item} "); 345 | return sb.ToString(); 346 | } 347 | 348 | public struct Enumerator : IEnumerator 349 | { 350 | object IEnumerator.Current => Current; 351 | 352 | private int CurrentIndex; 353 | 354 | private T CurrentItem; 355 | 356 | private readonly List* Items; 357 | 358 | internal Enumerator(List* items) 359 | { 360 | Items = items; 361 | CurrentIndex = 0; 362 | CurrentItem = default; 363 | } 364 | 365 | private void Initialize() 366 | { 367 | CurrentIndex = 0; 368 | CurrentItem = default; 369 | } 370 | 371 | public bool MoveNext() 372 | { 373 | if (CurrentIndex == Items->Count) return false; 374 | 375 | CurrentItem = Items->_items[CurrentIndex]; 376 | 377 | CurrentIndex++; 378 | return true; 379 | } 380 | 381 | public void Reset() 382 | { 383 | Initialize(); 384 | } 385 | 386 | public T Current => CurrentItem; 387 | 388 | public void Dispose() 389 | { 390 | } 391 | } 392 | 393 | public void OnReturnToPool() 394 | { 395 | Clear(); 396 | } 397 | 398 | public void OnGetFromPool() 399 | { 400 | 401 | } 402 | } 403 | } 404 | 405 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/SortedSet/Node.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace NativeCollection.UnsafeType 7 | { 8 | public unsafe partial struct SortedSet 9 | { 10 | internal enum NodeColor : byte 11 | { 12 | Black, 13 | Red 14 | } 15 | 16 | internal enum TreeRotation : byte 17 | { 18 | Left, 19 | LeftRight, 20 | Right, 21 | RightLeft 22 | } 23 | internal struct Node : IEquatable, IPool 24 | { 25 | public Node* Left; 26 | 27 | public Node* Right; 28 | 29 | public NodeColor Color; 30 | 31 | public T Item; 32 | 33 | public Node* Self 34 | { 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | get { return (Node*)Unsafe.AsPointer(ref this); } 37 | } 38 | 39 | public bool IsBlack 40 | { 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | get { return Color == NodeColor.Black; } 43 | } 44 | 45 | public bool IsRed 46 | { 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | get { return Color == NodeColor.Red; } 49 | } 50 | 51 | public bool Is2Node 52 | { 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | get { return IsBlack && IsNullOrBlack(Left) && IsNullOrBlack(Right); } 55 | } 56 | 57 | public bool Is4Node 58 | { 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | get 61 | { 62 | return IsNonNullRed(Left) && IsNonNullRed(Right); 63 | } 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public void ColorBlack() 68 | { 69 | Color = NodeColor.Black; 70 | } 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | public void ColorRed() 73 | { 74 | Color = NodeColor.Red; 75 | } 76 | 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | public static bool IsNonNullBlack(Node* node) 79 | { 80 | return node != null && node->IsBlack; 81 | } 82 | 83 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 84 | public static bool IsNonNullRed(Node* node) 85 | { 86 | return node != null && node->IsRed; 87 | } 88 | 89 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 90 | public static bool IsNullOrBlack(Node* node) 91 | { 92 | return node == null || node->IsBlack; 93 | } 94 | 95 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 96 | public static Node* Create(in T item, NodeColor nodeColor) 97 | { 98 | var node = (Node*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf()); 99 | node->Item = item; 100 | node->Color = nodeColor; 101 | node->Left = null; 102 | node->Right = null; 103 | return node; 104 | } 105 | 106 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 | public static Node* AllocFromMemoryPool(in T item, NodeColor nodeColor, FixedSizeMemoryPool* memoryPool) 108 | { 109 | Node* node = (Node*)memoryPool->Alloc(); 110 | node->Item = item; 111 | node->Color = nodeColor; 112 | node->Left = null; 113 | node->Right = null; 114 | return node; 115 | } 116 | 117 | public struct NodeSourceTarget : IEquatable 118 | { 119 | public Node* Source; 120 | public Node* Target; 121 | 122 | public NodeSourceTarget(Node* source, Node* target) 123 | { 124 | Source = source; 125 | Target = target; 126 | } 127 | 128 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 129 | public bool Equals(NodeSourceTarget other) 130 | { 131 | return Source == other.Source && Target == other.Target; 132 | } 133 | 134 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 135 | public override int GetHashCode() 136 | { 137 | return HashCode.Combine(unchecked((int)(long)Source), unchecked((int)(long)Target)); 138 | } 139 | } 140 | 141 | public Node* DeepClone(int count) 142 | { 143 | #if DEBUG 144 | Debug.Assert(count == GetCount()); 145 | #endif 146 | var newRoot = ShallowClone(); 147 | 148 | var pendingNodes = UnsafeType.Stack.Create(2 * Log2(count) + 2); 149 | pendingNodes->Push(new NodeSourceTarget(Self, newRoot)); 150 | 151 | while (pendingNodes->TryPop(out var next)) 152 | { 153 | Node* clonedNode; 154 | 155 | var left = next.Source->Left; 156 | var right = next.Source->Right; 157 | if (left != null) 158 | { 159 | clonedNode = left->ShallowClone(); 160 | next.Target->Left = clonedNode; 161 | pendingNodes->Push(new NodeSourceTarget(left, clonedNode)); 162 | } 163 | 164 | if (right != null) 165 | { 166 | clonedNode = right->ShallowClone(); 167 | next.Target->Right = clonedNode; 168 | pendingNodes->Push(new NodeSourceTarget(right, clonedNode)); 169 | } 170 | } 171 | 172 | pendingNodes->Dispose(); 173 | NativeMemoryHelper.Free(pendingNodes); 174 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf>()); 175 | return newRoot; 176 | } 177 | 178 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 179 | public TreeRotation GetRotation(Node* current, Node* sibling) 180 | { 181 | Debug.Assert(IsNonNullRed(sibling->Left) || IsNonNullRed(sibling->Right)); 182 | #if DEBUG 183 | Debug.Assert(HasChildren(current, sibling)); 184 | #endif 185 | var currentIsLeftChild = Left == current; 186 | return IsNonNullRed(sibling->Left) ? currentIsLeftChild ? TreeRotation.RightLeft : TreeRotation.Right : 187 | currentIsLeftChild ? TreeRotation.Left : TreeRotation.LeftRight; 188 | } 189 | 190 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 191 | public Node* GetSibling(Node* node) 192 | { 193 | Debug.Assert(node != null); 194 | Debug.Assert((node == Left) ^ (node == Right)); 195 | 196 | return node == Left ? Right : Left; 197 | } 198 | 199 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 200 | public Node* ShallowClone() 201 | { 202 | return Create(Item, Color); 203 | } 204 | 205 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 206 | public void Split4Node() 207 | { 208 | Debug.Assert(Left != null); 209 | Debug.Assert(Right != null); 210 | 211 | ColorRed(); 212 | Left->ColorBlack(); 213 | Right->ColorBlack(); 214 | } 215 | 216 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 217 | public Node* Rotate(TreeRotation rotation) 218 | { 219 | Node* removeRed; 220 | switch (rotation) 221 | { 222 | case TreeRotation.Right: 223 | removeRed = Left == null ? Left : Left->Left; 224 | Debug.Assert(removeRed->IsRed); 225 | removeRed->ColorBlack(); 226 | return RotateRight(); 227 | case TreeRotation.Left: 228 | removeRed = Right == null ? Right : Right->Right!; 229 | Debug.Assert(removeRed->IsRed); 230 | removeRed->ColorBlack(); 231 | return RotateLeft(); 232 | case TreeRotation.RightLeft: 233 | Debug.Assert(Right->Left->IsRed); 234 | return RotateRightLeft(); 235 | case TreeRotation.LeftRight: 236 | Debug.Assert(Left->Right->IsRed); 237 | return RotateLeftRight(); 238 | default: 239 | Debug.Fail($"{nameof(rotation)}: {rotation} is not a defined {nameof(TreeRotation)} value."); 240 | return null; 241 | } 242 | } 243 | 244 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 245 | public Node* RotateLeft() 246 | { 247 | var child = Right; 248 | Right = child->Left; 249 | child->Left = Self; 250 | return child; 251 | } 252 | 253 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 254 | public Node* RotateLeftRight() 255 | { 256 | var child = Left; 257 | var grandChild = child->Right!; 258 | 259 | Left = grandChild->Right; 260 | grandChild->Right = Self; 261 | child->Right = grandChild->Left; 262 | grandChild->Left = child; 263 | return grandChild; 264 | } 265 | 266 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 267 | public Node* RotateRight() 268 | { 269 | var child = Left; 270 | Left = child->Right; 271 | child->Right = Self; 272 | return child; 273 | } 274 | 275 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 276 | public Node* RotateRightLeft() 277 | { 278 | var child = Right; 279 | var grandChild = child->Left; 280 | 281 | Right = grandChild->Left; 282 | grandChild->Left = Self; 283 | child->Left = grandChild->Right; 284 | grandChild->Right = child; 285 | return grandChild; 286 | } 287 | 288 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 289 | public void Merge2Nodes() 290 | { 291 | Debug.Assert(IsRed); 292 | Debug.Assert(Left->Is2Node); 293 | Debug.Assert(Right->Is2Node); 294 | 295 | // Combine two 2-nodes into a 4-node. 296 | ColorBlack(); 297 | Left->ColorRed(); 298 | Right->ColorRed(); 299 | } 300 | 301 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 302 | public void ReplaceChild(Node* child, Node* newChild) 303 | { 304 | #if DEBUG 305 | Debug.Assert(HasChild(child)); 306 | #endif 307 | 308 | if (Left == child) 309 | Left = newChild; 310 | else 311 | Right = newChild; 312 | } 313 | 314 | 315 | #if DEBUG 316 | private int GetCount() 317 | { 318 | var value = 1; 319 | if (Left != null) value += Left->GetCount(); 320 | 321 | if (Right != null) value += Right->GetCount(); 322 | return value; 323 | } 324 | 325 | private bool HasChild(Node* child) 326 | { 327 | return child == Left || child == Right; 328 | } 329 | 330 | private bool HasChildren(Node* child1, Node* child2) 331 | { 332 | Debug.Assert(child1 != child2); 333 | 334 | return (Left == child1 && Right == child2) 335 | || (Left == child2 && Right == child1); 336 | } 337 | #endif 338 | 339 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 340 | public bool Equals(Node other) 341 | { 342 | return (Item).Equals(other.Item) && Self == other.Self && Color == other.Color && Left == other.Left && 343 | Right == other.Right; 344 | } 345 | 346 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 347 | public override int GetHashCode() 348 | { 349 | return HashCode.Combine(Item, unchecked((int)(long)Self), (int)Color, unchecked((int)(long)Left), 350 | unchecked((int)(long)Right)); 351 | } 352 | 353 | public void Dispose() 354 | { 355 | 356 | } 357 | 358 | public void OnReturnToPool() 359 | { 360 | 361 | } 362 | 363 | public void OnGetFromPool() 364 | { 365 | 366 | } 367 | } 368 | } 369 | } 370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/HashSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace NativeCollection.UnsafeType 9 | {public unsafe struct HashSet : ICollection, IDisposable where T : unmanaged, IEquatable 10 | { 11 | /// Cutoff point for stackallocs. This corresponds to the number of ints. 12 | private const int StackAllocThreshold = 100; 13 | 14 | /// 15 | /// When constructing a hashset from an existing collection, it may contain duplicates, 16 | /// so this is used as the max acceptable excess ratio of capacity to count. Note that 17 | /// this is only used on the ctor and not to automatically shrink if the hashset has, e.g, 18 | /// a lot of adds followed by removes. Users must explicitly shrink by calling TrimExcess. 19 | /// This is set to 3 because capacity is acceptable as 2x rounded up to nearest prime. 20 | /// 21 | private const int ShrinkThreshold = 3; 22 | 23 | private const int StartOfFreeList = -3; 24 | 25 | private HashSet* _self; 26 | private int* _buckets; 27 | private int _bucketLength; 28 | private Entry* _entries; 29 | private int _entryLength; 30 | #if TARGET_64BIT 31 | private ulong _fastModMultiplier; 32 | #endif 33 | private int _count; 34 | private int _freeList; 35 | private int _freeCount; 36 | private int _version; 37 | 38 | public static HashSet* Create(int capacity = 0) 39 | { 40 | HashSet* hashSet = (HashSet*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 41 | hashSet->_buckets = null; 42 | hashSet->_entries = null; 43 | hashSet->_self = hashSet; 44 | hashSet->Initialize(capacity); 45 | return hashSet; 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public bool Add(T item) => AddIfNotPresent(item, out _); 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public bool AddRef(in T item) => AddIfNotPresent(item, out _); 53 | 54 | IEnumerator IEnumerable.GetEnumerator() 55 | { 56 | return GetEnumerator(); 57 | } 58 | 59 | IEnumerator IEnumerable.GetEnumerator() 60 | { 61 | return GetEnumerator(); 62 | } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public Enumerator GetEnumerator() => new Enumerator(_self); 66 | 67 | #region ICollection methods 68 | 69 | void ICollection.Add(T item) 70 | { 71 | AddIfNotPresent(item, out _); 72 | } 73 | 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public void Clear() 76 | { 77 | int count = _count; 78 | if (count > 0) 79 | { 80 | Debug.Assert(_buckets != null, "_buckets should be non-null"); 81 | Debug.Assert(_entries != null, "_entries should be non-null"); 82 | Unsafe.InitBlockUnaligned(_buckets,0,(uint)(Unsafe.SizeOf()*_bucketLength)); 83 | Unsafe.InitBlockUnaligned(_entries,0,(uint)(Unsafe.SizeOf()*count)); 84 | _count = 0; 85 | _freeList = -1; 86 | _freeCount = 0; 87 | } 88 | } 89 | 90 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 91 | public bool Contains(T item) 92 | { 93 | return FindItemIndex(item) >= 0; 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public bool ContainsRef(in T item) 98 | { 99 | return FindItemIndex(item) >= 0; 100 | } 101 | 102 | #endregion 103 | 104 | 105 | public void CopyTo(T[] array, int arrayIndex) 106 | { 107 | if (array==null) 108 | { 109 | ThrowHelper.ArgumentNullException("array"); 110 | } 111 | 112 | if (arrayIndex<0 &&arrayIndex >= array.Length) 113 | { 114 | ThrowHelper.ArgumentOutOfRangeException("arrayIndex"); 115 | } 116 | 117 | var arraySpan = array.AsSpan(arrayIndex); 118 | for (int i = 0; i < _count && _count!=0; i++) 119 | { 120 | ref Entry entry = ref _entries[i]; 121 | if (entry.Next >= -1) 122 | { 123 | arraySpan[i] = entry.Value; 124 | } 125 | } 126 | } 127 | 128 | public bool RemoveRef(in T item) 129 | { 130 | //if (_buckets == null) return false; 131 | var entries = _entries; 132 | Debug.Assert(entries != null, "entries should be non-null"); 133 | 134 | uint collisionCount = 0; 135 | int last = -1; 136 | int hashCode = item.GetHashCode(); 137 | 138 | ref int bucket = ref GetBucketRef(hashCode); 139 | int i = bucket - 1; // Value in buckets is 1-based 140 | 141 | while (i >= 0) 142 | { 143 | ref Entry entry = ref entries[i]; 144 | 145 | if (entry.HashCode == hashCode && (item.Equals(entry.Value))) 146 | { 147 | if (last < 0) 148 | { 149 | bucket = entry.Next + 1; // Value in buckets is 1-based 150 | } 151 | else 152 | { 153 | entries[last].Next = entry.Next; 154 | } 155 | 156 | Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); 157 | entry.Next = StartOfFreeList - _freeList; 158 | 159 | _freeList = i; 160 | _freeCount++; 161 | return true; 162 | } 163 | 164 | last = i; 165 | i = entry.Next; 166 | 167 | collisionCount++; 168 | if (collisionCount > (uint)_entryLength) 169 | { 170 | // The chain of entries forms a loop; which means a concurrent update has happened. 171 | // Break out of the loop and throw, rather than looping forever. 172 | ThrowHelper.ConcurrentOperationsNotSupported(); 173 | } 174 | } 175 | 176 | return false; 177 | } 178 | 179 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 180 | public bool Remove(T item) 181 | { 182 | return RemoveRef(item); 183 | } 184 | 185 | public bool TryGetValue(in T equalValue, out T actualValue) 186 | { 187 | int index = FindItemIndex(equalValue); 188 | if (index>=0) 189 | { 190 | actualValue = _entries[index].Value; 191 | return true; 192 | } 193 | actualValue = default; 194 | return false; 195 | } 196 | 197 | internal T* GetValuePointer(in T key) 198 | { 199 | int index = FindItemIndex(key); 200 | if (index>=0) 201 | { 202 | return &(_entries + index)->Value; 203 | } 204 | return null; 205 | } 206 | 207 | public int Count => _count - _freeCount; 208 | bool ICollection.IsReadOnly => false; 209 | 210 | public void Dispose() 211 | { 212 | NativeMemoryHelper.Free(_buckets); 213 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_bucketLength); 214 | 215 | NativeMemoryHelper.Free(_entries); 216 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_entryLength); 217 | } 218 | 219 | #region Helper methods 220 | 221 | /// 222 | /// Initializes buckets and slots arrays. Uses suggested capacity by finding next prime 223 | /// greater than or equal to capacity. 224 | /// 225 | private int Initialize(int capacity) 226 | { 227 | int size = HashHelpers.GetPrime(capacity); 228 | _buckets = (int*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * size)); 229 | _bucketLength = size; 230 | _entries = (Entry*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * size)); 231 | _entryLength = size; 232 | // Assign member variables after both arrays are allocated to guard against corruption from OOM if second fails. 233 | _freeList = -1; 234 | _freeCount = 0; 235 | _count = 0; 236 | _version = 0; 237 | #if TARGET_64BIT 238 | _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size); 239 | #endif 240 | 241 | return size; 242 | } 243 | 244 | /// Adds the specified element to the set if it's not already contained. 245 | /// The element to add to the set. 246 | /// The index into of the element. 247 | /// true if the element is added to the object; false if the element is already present. 248 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 249 | private bool AddIfNotPresent(in T value, out int location) 250 | { 251 | //Console.WriteLine($"AddIfNotPresent:{value}"); 252 | //if (_buckets == null) Initialize(0); 253 | Debug.Assert(_buckets != null); 254 | 255 | Entry* entries = _entries; 256 | Debug.Assert(entries != null, "expected entries to be non-null"); 257 | 258 | //var comparer = _comparer; 259 | int hashCode; 260 | 261 | uint collisionCount = 0; 262 | ref var bucket = ref Unsafe.NullRef(); 263 | 264 | hashCode = value.GetHashCode(); 265 | bucket = ref GetBucketRef(hashCode); 266 | 267 | var i = bucket - 1; // Value in _buckets is 1-based 268 | // Console.WriteLine($"i:{i}"); 269 | while (i >= 0) 270 | { 271 | // Console.WriteLine($"i:{i}"); 272 | ref Entry entry = ref _entries[i]; 273 | // Console.WriteLine($"entry.HashCode:{entry.HashCode} hashCode:{hashCode} Equals:{comparer.Equals(entry.Value, value)}"); 274 | if (entry.HashCode == hashCode && entry.Value.Equals(value)) 275 | { 276 | location = i; 277 | return false; 278 | } 279 | 280 | i = entry.Next; 281 | 282 | collisionCount++; 283 | // Console.WriteLine($"collisionCount :{collisionCount} i:{i}"); 284 | if (collisionCount > (uint)_entryLength) 285 | // The chain of entries forms a loop, which means a concurrent update has happened. 286 | ThrowHelper.ConcurrentOperationsNotSupported(); 287 | } 288 | 289 | 290 | int index; 291 | if (_freeCount > 0) 292 | { 293 | index = _freeList; 294 | _freeCount--; 295 | Debug.Assert(StartOfFreeList - _entries[_freeList].Next >= -1, 296 | "shouldn't overflow because `next` cannot underflow"); 297 | _freeList = StartOfFreeList - _entries[_freeList].Next; 298 | } 299 | else 300 | { 301 | var count = _count; 302 | if (count == _entryLength) 303 | { 304 | Resize(); 305 | bucket = ref GetBucketRef(hashCode); 306 | } 307 | 308 | index = count; 309 | _count = count + 1; 310 | } 311 | 312 | { 313 | ref Entry entry = ref _entries[index]; 314 | entry.HashCode = hashCode; 315 | entry.Next = bucket - 1; // Value in _buckets is 1-based 316 | entry.Value = value; 317 | bucket = index + 1; 318 | _version++; 319 | location = index; 320 | } 321 | 322 | return true; 323 | } 324 | 325 | /// Gets a reference to the specified hashcode's bucket, containing an index into . 326 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 327 | private ref int GetBucketRef(int hashCode) 328 | { 329 | //var buckets = _buckets; 330 | 331 | #if TARGET_64BIT 332 | return ref _buckets[HashHelpers.FastMod((uint)hashCode, (uint)_buckets.Length, _fastModMultiplier)]; 333 | #else 334 | int index = (int)((uint)hashCode %(uint)_bucketLength); 335 | return ref _buckets[index]; 336 | #endif 337 | } 338 | 339 | #endregion 340 | 341 | 342 | 343 | /// Ensures that this hash set can hold the specified number of elements without growing. 344 | public int EnsureCapacity(int capacity) 345 | { 346 | if (capacity < 0) 347 | { 348 | ThrowHelper.HashSetCapacityOutOfRange(); 349 | } 350 | 351 | int currentCapacity = _entries == null ? 0 : _entryLength; 352 | if (currentCapacity >= capacity) 353 | { 354 | return currentCapacity; 355 | } 356 | 357 | if (_buckets == null) 358 | { 359 | return Initialize(capacity); 360 | } 361 | 362 | int newSize = HashHelpers.GetPrime(capacity); 363 | Resize(newSize, forceNewHashCodes: false); 364 | return newSize; 365 | } 366 | 367 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 368 | private void Resize() => Resize(HashHelpers.ExpandPrime(_count), forceNewHashCodes: false); 369 | 370 | private void Resize(int newSize, bool forceNewHashCodes) 371 | { 372 | // Console.WriteLine($"before resize:{*self}"); 373 | // Value types never rehash 374 | Debug.Assert(!forceNewHashCodes || !typeof(T).IsValueType); 375 | Debug.Assert(_entries != null, "_entries should be non-null"); 376 | Debug.Assert(newSize >= _entryLength); 377 | // Console.WriteLine($"Resize newSize:{newSize} byteSize:{Unsafe.SizeOf() * newSize}"); 378 | var newEntries = (Entry*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * newSize)); 379 | Unsafe.CopyBlockUnaligned(newEntries,_entries,(uint)(Unsafe.SizeOf()*_entryLength)); 380 | int count = _count; 381 | // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails 382 | var newBucket = (int*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * newSize)); 383 | NativeMemoryHelper.Free(_buckets); 384 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_bucketLength); 385 | _buckets = newBucket; 386 | _bucketLength = newSize; 387 | #if TARGET_64BIT 388 | _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); 389 | #endif 390 | for (int i = 0; i < count; i++) 391 | { 392 | ref Entry entry = ref newEntries[i]; 393 | if (entry.Next >= -1) 394 | { 395 | ref int bucket = ref GetBucketRef(entry.HashCode); 396 | entry.Next = bucket - 1; // Value in _buckets is 1-based 397 | // Console.WriteLine($"entry.Next:{entry.Next} bucket:{bucket}"); 398 | bucket = i + 1; 399 | } 400 | } 401 | NativeMemoryHelper.Free(_entries); 402 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_entryLength); 403 | _entries = newEntries; 404 | _entryLength = newSize; 405 | 406 | //Console.WriteLine($"after resize:{*self} totalSize:{_entryLength}"); 407 | } 408 | 409 | /// Gets the index of the item in , or -1 if it's not in the set. 410 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 411 | private int FindItemIndex(in T item) 412 | { 413 | //if (_buckets == null) return -1; 414 | var entries = _entries; 415 | Debug.Assert(entries != null, "Expected _entries to be initialized"); 416 | 417 | uint collisionCount = 0; 418 | //IEqualityComparer? comparer = _comparer; 419 | 420 | int hashCode = item.GetHashCode(); 421 | int i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based 422 | while (i >= 0) 423 | { 424 | ref Entry entry = ref entries[i]; 425 | if (entry.HashCode == hashCode && item.Equals(entry.Value)) 426 | { 427 | return i; 428 | } 429 | i = entry.Next; 430 | 431 | collisionCount++; 432 | if (collisionCount > (uint)_entryLength) 433 | { 434 | // The chain of entries forms a loop, which means a concurrent update has happened. 435 | ThrowHelper.ConcurrentOperationsNotSupported(); 436 | } 437 | } 438 | 439 | return -1; 440 | } 441 | 442 | 443 | private struct Entry : IEquatable 444 | { 445 | public int HashCode; 446 | 447 | /// 448 | /// 0-based index of next entry in chain: -1 means end of chain 449 | /// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, 450 | /// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. 451 | /// 452 | public int Next; 453 | 454 | public T Value; 455 | public bool Equals(Entry other) 456 | { 457 | return HashCode == other.HashCode && Next == other.Next && Value.Equals(other.Value); 458 | } 459 | } 460 | 461 | public override string ToString() 462 | { 463 | StringBuilder sb = new StringBuilder(); 464 | foreach (var value in *_self) 465 | { 466 | sb.Append($"{value} "); 467 | } 468 | 469 | sb.Append("\n"); 470 | return sb.ToString(); 471 | } 472 | 473 | public struct Enumerator : IEnumerator 474 | { 475 | private readonly HashSet* _hashSet; 476 | private readonly int _version; 477 | private int _index; 478 | private T _current; 479 | 480 | internal Enumerator(HashSet* hashSet) 481 | { 482 | _hashSet = hashSet; 483 | _version = hashSet->_version; 484 | _index = 0; 485 | _current = default!; 486 | } 487 | 488 | public bool MoveNext() 489 | { 490 | if (_version != _hashSet->_version) 491 | ThrowHelper.HashSetEnumFailedVersion(); 492 | 493 | // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. 494 | // dictionary.count+1 could be negative if dictionary.count is int.MaxValue 495 | while ((uint)_index < (uint)_hashSet->_count) 496 | { 497 | ref Entry entry = ref _hashSet->_entries[_index++]; 498 | if (entry.Next >= -1) 499 | { 500 | _current = entry.Value; 501 | return true; 502 | } 503 | } 504 | 505 | _index = _hashSet->_count + 1; 506 | _current = default!; 507 | return false; 508 | } 509 | 510 | public T Current => _current; 511 | 512 | public void Dispose() 513 | { 514 | } 515 | 516 | object IEnumerator.Current => Current; 517 | 518 | public void Reset() 519 | { 520 | if (_version != _hashSet->_version) 521 | ThrowHelper.HashSetEnumFailedVersion(); 522 | 523 | _index = 0; 524 | _current = default!; 525 | } 526 | } 527 | } 528 | } 529 | 530 | -------------------------------------------------------------------------------- /NativeCollection/NativeCollection/UnsafeType/UnOrderMap/UnOrderMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace NativeCollection.UnsafeType 9 | { 10 | public unsafe struct UnOrderMap : IEnumerable> 11 | where T : unmanaged, IEquatable, IComparable where K : unmanaged, IEquatable 12 | { 13 | /// Cutoff point for stackallocs. This corresponds to the number of ints. 14 | private const int StackAllocThreshold = 100; 15 | 16 | /// 17 | /// When constructing a hashset from an existing collection, it may contain duplicates, 18 | /// so this is used as the max acceptable excess ratio of capacity to count. Note that 19 | /// this is only used on the ctor and not to automatically shrink if the hashset has, e.g, 20 | /// a lot of adds followed by removes. Users must explicitly shrink by calling TrimExcess. 21 | /// This is set to 3 because capacity is acceptable as 2x rounded up to nearest prime. 22 | /// 23 | private const int ShrinkThreshold = 3; 24 | 25 | private const int StartOfFreeList = -3; 26 | 27 | private UnOrderMap* _self; 28 | private int* _buckets; 29 | private int _bucketLength; 30 | private Entry* _entries; 31 | private int _entryLength; 32 | #if TARGET_64BIT 33 | private ulong _fastModMultiplier; 34 | #endif 35 | private int _count; 36 | private int _freeList; 37 | private int _freeCount; 38 | private int _version; 39 | 40 | public static UnOrderMap* Create(int capacity = 0) 41 | { 42 | UnOrderMap* unOrderMap = (UnOrderMap*)NativeMemoryHelper.Alloc((UIntPtr)Unsafe.SizeOf>()); 43 | unOrderMap->_buckets = null; 44 | unOrderMap->_entries = null; 45 | unOrderMap->_self = unOrderMap; 46 | unOrderMap->Initialize(capacity); 47 | return unOrderMap; 48 | } 49 | 50 | public K this[T key] 51 | { 52 | get 53 | { 54 | bool contains = TryGetValue(key, out var value); 55 | if (contains) 56 | { 57 | return value; 58 | } 59 | return default; 60 | } 61 | set 62 | { 63 | bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting); 64 | Debug.Assert(modified); 65 | } 66 | } 67 | 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public bool Add(T key,K value) => AddRef(key,value); 70 | 71 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 72 | public bool AddRef(in T key, in K value) => TryInsert(key,value, InsertionBehavior.ThrowOnExisting); 73 | 74 | IEnumerator> IEnumerable>.GetEnumerator() 75 | { 76 | return GetEnumerator(); 77 | } 78 | 79 | IEnumerator IEnumerable.GetEnumerator() 80 | { 81 | return GetEnumerator(); 82 | } 83 | 84 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 | public Enumerator GetEnumerator() => new Enumerator(_self); 86 | 87 | #region ICollection methods 88 | 89 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 90 | public void Clear() 91 | { 92 | int count = _count; 93 | if (count > 0) 94 | { 95 | Debug.Assert(_buckets != null, "_buckets should be non-null"); 96 | Debug.Assert(_entries != null, "_entries should be non-null"); 97 | Unsafe.InitBlockUnaligned(_buckets,0,(uint)(Unsafe.SizeOf()*_bucketLength)); 98 | Unsafe.InitBlockUnaligned(_entries,0,(uint)(Unsafe.SizeOf()*count)); 99 | _count = 0; 100 | _freeList = -1; 101 | _freeCount = 0; 102 | } 103 | } 104 | 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | public bool ContainsKey(T key) 107 | { 108 | return FindItemIndex(key) >= 0; 109 | } 110 | 111 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 112 | public bool ContainsKeyRef(in T key) 113 | { 114 | return FindItemIndex(key) >= 0; 115 | } 116 | 117 | #endregion 118 | 119 | public bool RemoveRef(in T key) 120 | { 121 | //if (_buckets == null) return false; 122 | var entries = _entries; 123 | Debug.Assert(entries != null, "entries should be non-null"); 124 | 125 | uint collisionCount = 0; 126 | int last = -1; 127 | int hashCode = key.GetHashCode(); 128 | 129 | ref int bucket = ref GetBucketRef(hashCode); 130 | int i = bucket - 1; // Value in buckets is 1-based 131 | 132 | while (i >= 0) 133 | { 134 | ref Entry entry = ref entries[i]; 135 | 136 | if (entry.HashCode == hashCode && (key.Equals(entry.Key))) 137 | { 138 | if (last < 0) 139 | { 140 | bucket = entry.Next + 1; // Value in buckets is 1-based 141 | } 142 | else 143 | { 144 | entries[last].Next = entry.Next; 145 | } 146 | 147 | Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); 148 | entry.Next = StartOfFreeList - _freeList; 149 | 150 | _freeList = i; 151 | _freeCount++; 152 | return true; 153 | } 154 | 155 | last = i; 156 | i = entry.Next; 157 | 158 | collisionCount++; 159 | if (collisionCount > (uint)_entryLength) 160 | { 161 | // The chain of entries forms a loop; which means a concurrent update has happened. 162 | // Break out of the loop and throw, rather than looping forever. 163 | ThrowHelper.ConcurrentOperationsNotSupported(); 164 | } 165 | } 166 | 167 | return false; 168 | } 169 | 170 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 171 | public bool Remove(T item) 172 | { 173 | return RemoveRef(item); 174 | } 175 | 176 | public bool TryGetValue(in T key, out K actualValue) 177 | { 178 | int index = FindItemIndex(key); 179 | if (index>=0) 180 | { 181 | actualValue = _entries[index].Value; 182 | return true; 183 | } 184 | actualValue = default; 185 | return false; 186 | } 187 | 188 | public int Count => _count - _freeCount; 189 | 190 | public void Dispose() 191 | { 192 | NativeMemoryHelper.Free(_buckets); 193 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_bucketLength); 194 | 195 | NativeMemoryHelper.Free(_entries); 196 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_entryLength); 197 | } 198 | 199 | #region Helper methods 200 | 201 | /// 202 | /// Initializes buckets and slots arrays. Uses suggested capacity by finding next prime 203 | /// greater than or equal to capacity. 204 | /// 205 | private int Initialize(int capacity) 206 | { 207 | int size = HashHelpers.GetPrime(capacity); 208 | _buckets = (int*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * size)); 209 | _bucketLength = size; 210 | _entries = (Entry*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * size)); 211 | _entryLength = size; 212 | // Assign member variables after both arrays are allocated to guard against corruption from OOM if second fails. 213 | _freeList = -1; 214 | _freeCount = 0; 215 | _count = 0; 216 | _version = 0; 217 | #if TARGET_64BIT 218 | _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size); 219 | #endif 220 | 221 | return size; 222 | } 223 | 224 | 225 | /// Adds the specified element to the set if it's not already contained. 226 | /// The element to add to the set. 227 | /// The index into of the element. 228 | /// true if the element is added to the object; false if the element is already present. 229 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 230 | private bool TryInsert(in T key,in K value,InsertionBehavior insertionBehavior) 231 | { 232 | //Console.WriteLine($"AddIfNotPresent:{value}"); 233 | //if (_buckets == null) Initialize(0); 234 | Debug.Assert(_buckets != null); 235 | 236 | Entry* entries = _entries; 237 | Debug.Assert(entries != null, "expected entries to be non-null"); 238 | 239 | //var comparer = _comparer; 240 | int hashCode; 241 | 242 | uint collisionCount = 0; 243 | ref var bucket = ref Unsafe.NullRef(); 244 | 245 | hashCode = key.GetHashCode(); 246 | bucket = ref GetBucketRef(hashCode); 247 | 248 | var i = bucket - 1; // Value in _buckets is 1-based 249 | // Console.WriteLine($"i:{i}"); 250 | while (i >= 0) 251 | { 252 | // Console.WriteLine($"i:{i}"); 253 | ref Entry entry = ref _entries[i]; 254 | // Console.WriteLine($"entry.HashCode:{entry.HashCode} hashCode:{hashCode} Equals:{comparer.Equals(entry.Value, value)}"); 255 | if (entry.HashCode == hashCode && entry.Key.Equals(key)) 256 | { 257 | if (insertionBehavior== InsertionBehavior.OverwriteExisting) 258 | { 259 | entries[i].Value = value; 260 | return true; 261 | } 262 | if (insertionBehavior == InsertionBehavior.ThrowOnExisting) 263 | { 264 | ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(); 265 | } 266 | 267 | return false; 268 | } 269 | 270 | i = entry.Next; 271 | 272 | collisionCount++; 273 | // Console.WriteLine($"collisionCount :{collisionCount} i:{i}"); 274 | if (collisionCount > (uint)_entryLength) 275 | // The chain of entries forms a loop, which means a concurrent update has happened. 276 | ThrowHelper.ConcurrentOperationsNotSupported(); 277 | } 278 | 279 | 280 | int index; 281 | if (_freeCount > 0) 282 | { 283 | index = _freeList; 284 | _freeCount--; 285 | Debug.Assert(StartOfFreeList - _entries[_freeList].Next >= -1, 286 | "shouldn't overflow because `next` cannot underflow"); 287 | _freeList = StartOfFreeList - _entries[_freeList].Next; 288 | } 289 | else 290 | { 291 | var count = _count; 292 | if (count == _entryLength) 293 | { 294 | Resize(); 295 | bucket = ref GetBucketRef(hashCode); 296 | } 297 | 298 | index = count; 299 | _count = count + 1; 300 | } 301 | 302 | { 303 | ref Entry entry = ref _entries[index]; 304 | entry.HashCode = hashCode; 305 | entry.Next = bucket - 1; // Value in _buckets is 1-based 306 | entry.Key = key; 307 | entry.Value = value; 308 | bucket = index + 1; 309 | _version++; 310 | } 311 | 312 | return true; 313 | } 314 | 315 | /// Gets a reference to the specified hashcode's bucket, containing an index into . 316 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 317 | private ref int GetBucketRef(int hashCode) 318 | { 319 | //var buckets = _buckets; 320 | 321 | #if TARGET_64BIT 322 | return ref _buckets[HashHelpers.FastMod((uint)hashCode, (uint)_buckets.Length, _fastModMultiplier)]; 323 | #else 324 | int index = (int)((uint)hashCode %(uint)_bucketLength); 325 | return ref _buckets[index]; 326 | #endif 327 | } 328 | 329 | #endregion 330 | 331 | 332 | 333 | /// Ensures that this hash set can hold the specified number of elements without growing. 334 | public int EnsureCapacity(int capacity) 335 | { 336 | if (capacity < 0) 337 | { 338 | ThrowHelper.HashSetCapacityOutOfRange(); 339 | } 340 | 341 | int currentCapacity = _entries == null ? 0 : _entryLength; 342 | if (currentCapacity >= capacity) 343 | { 344 | return currentCapacity; 345 | } 346 | 347 | if (_buckets == null) 348 | { 349 | return Initialize(capacity); 350 | } 351 | 352 | int newSize = HashHelpers.GetPrime(capacity); 353 | Resize(newSize, forceNewHashCodes: false); 354 | return newSize; 355 | } 356 | 357 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 358 | private void Resize() => Resize(HashHelpers.ExpandPrime(_count), forceNewHashCodes: false); 359 | 360 | private void Resize(int newSize, bool forceNewHashCodes) 361 | { 362 | // Console.WriteLine($"before resize:{*self}"); 363 | // Value types never rehash 364 | Debug.Assert(!forceNewHashCodes || !typeof(T).IsValueType); 365 | Debug.Assert(_entries != null, "_entries should be non-null"); 366 | Debug.Assert(newSize >= _entryLength); 367 | // Console.WriteLine($"Resize newSize:{newSize} byteSize:{Unsafe.SizeOf() * newSize}"); 368 | var newEntries = (Entry*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * newSize)); 369 | Unsafe.CopyBlockUnaligned(newEntries,_entries,(uint)(Unsafe.SizeOf()*_entryLength)); 370 | int count = _count; 371 | // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails 372 | var newBucket = (int*)NativeMemoryHelper.AllocZeroed((UIntPtr)(Unsafe.SizeOf() * newSize)); 373 | NativeMemoryHelper.Free(_buckets); 374 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_bucketLength); 375 | _buckets = newBucket; 376 | _bucketLength = newSize; 377 | #if TARGET_64BIT 378 | _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); 379 | #endif 380 | for (int i = 0; i < count; i++) 381 | { 382 | ref Entry entry = ref newEntries[i]; 383 | if (entry.Next >= -1) 384 | { 385 | ref int bucket = ref GetBucketRef(entry.HashCode); 386 | entry.Next = bucket - 1; // Value in _buckets is 1-based 387 | // Console.WriteLine($"entry.Next:{entry.Next} bucket:{bucket}"); 388 | bucket = i + 1; 389 | } 390 | } 391 | NativeMemoryHelper.Free(_entries); 392 | NativeMemoryHelper.RemoveNativeMemoryByte(Unsafe.SizeOf()*_entryLength); 393 | _entries = newEntries; 394 | _entryLength = newSize; 395 | 396 | //Console.WriteLine($"after resize:{*self} totalSize:{_entryLength}"); 397 | } 398 | 399 | /// Gets the index of the item in , or -1 if it's not in the set. 400 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 401 | private int FindItemIndex(in T key) 402 | { 403 | //if (_buckets == null) return -1; 404 | var entries = _entries; 405 | Debug.Assert(entries != null, "Expected _entries to be initialized"); 406 | 407 | uint collisionCount = 0; 408 | //IEqualityComparer? comparer = _comparer; 409 | 410 | int hashCode = key.GetHashCode(); 411 | int i = GetBucketRef(hashCode) - 1; // Value in _buckets is 1-based 412 | while (i >= 0) 413 | { 414 | ref Entry entry = ref entries[i]; 415 | if (entry.HashCode == hashCode && key.Equals(entry.Key)) 416 | { 417 | return i; 418 | } 419 | i = entry.Next; 420 | 421 | collisionCount++; 422 | if (collisionCount > (uint)_entryLength) 423 | { 424 | // The chain of entries forms a loop, which means a concurrent update has happened. 425 | ThrowHelper.ConcurrentOperationsNotSupported(); 426 | } 427 | } 428 | 429 | return -1; 430 | } 431 | 432 | 433 | private struct Entry : IEquatable 434 | { 435 | public int HashCode; 436 | 437 | /// 438 | /// 0-based index of next entry in chain: -1 means end of chain 439 | /// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, 440 | /// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. 441 | /// 442 | public int Next; 443 | public T Key; 444 | public K Value; 445 | public bool Equals(Entry other) 446 | { 447 | return HashCode == other.HashCode && Next == other.Next && Key.Equals(other.Key); 448 | } 449 | } 450 | 451 | public override string ToString() 452 | { 453 | StringBuilder sb = new StringBuilder(); 454 | foreach (var value in *_self) 455 | { 456 | sb.Append($"{value} "); 457 | } 458 | 459 | sb.Append("\n"); 460 | return sb.ToString(); 461 | } 462 | 463 | internal enum InsertionBehavior : byte 464 | { 465 | None, 466 | OverwriteExisting, 467 | ThrowOnExisting, 468 | } 469 | 470 | public struct Enumerator : IEnumerator> 471 | { 472 | private readonly UnOrderMap* _unOrderMap; 473 | private readonly int _version; 474 | private int _index; 475 | private MapPair _current; 476 | 477 | internal Enumerator(UnOrderMap* unOrderMap) 478 | { 479 | _unOrderMap = unOrderMap; 480 | _version = unOrderMap->_version; 481 | _index = 0; 482 | _current = default!; 483 | } 484 | 485 | public bool MoveNext() 486 | { 487 | if (_version != _unOrderMap->_version) 488 | ThrowHelper.HashSetEnumFailedVersion(); 489 | 490 | // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. 491 | // dictionary.count+1 could be negative if dictionary.count is int.MaxValue 492 | while ((uint)_index < (uint)_unOrderMap->_count) 493 | { 494 | ref Entry entry = ref _unOrderMap->_entries[_index++]; 495 | if (entry.Next >= -1) 496 | { 497 | _current = new MapPair(entry.Key,entry.Value); 498 | return true; 499 | } 500 | } 501 | 502 | _index = _unOrderMap->_count + 1; 503 | _current = default!; 504 | return false; 505 | } 506 | 507 | public MapPair Current => _current; 508 | 509 | public void Dispose() 510 | { 511 | } 512 | 513 | object IEnumerator.Current => Current; 514 | 515 | public void Reset() 516 | { 517 | if (_version != _unOrderMap->_version) 518 | ThrowHelper.HashSetEnumFailedVersion(); 519 | 520 | _index = 0; 521 | _current = default!; 522 | } 523 | } 524 | } 525 | } 526 | 527 | --------------------------------------------------------------------------------