From 8620b8facfb1ec0406f4100820e744ced6abf648 Mon Sep 17 00:00:00 2001 From: nbollis Date: Mon, 13 Jan 2025 18:32:18 -0600 Subject: [PATCH] Added bassic object pools --- mzLib/MzLibUtil/MzLibUtil.csproj | 1 + mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings | 2 + mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs | 58 +++++++++ mzLib/MzLibUtil/ObjectPools/HashSetPool.cs | 60 +++++++++ mzLib/MzLibUtil/ObjectPools/ListPool.cs | 56 ++++++++ mzLib/Test/ObjectPoolTests.cs | 120 ++++++++++++++++++ mzLib/mzLib.nuspec | 2 + 7 files changed, 299 insertions(+) create mode 100644 mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings create mode 100644 mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs create mode 100644 mzLib/MzLibUtil/ObjectPools/HashSetPool.cs create mode 100644 mzLib/MzLibUtil/ObjectPools/ListPool.cs create mode 100644 mzLib/Test/ObjectPoolTests.cs diff --git a/mzLib/MzLibUtil/MzLibUtil.csproj b/mzLib/MzLibUtil/MzLibUtil.csproj index c6b5cf526..ae8fef5ea 100644 --- a/mzLib/MzLibUtil/MzLibUtil.csproj +++ b/mzLib/MzLibUtil/MzLibUtil.csproj @@ -14,6 +14,7 @@ + diff --git a/mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings b/mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings new file mode 100644 index 000000000..52f9c2892 --- /dev/null +++ b/mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs b/mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs new file mode 100644 index 000000000..ca2f2f712 --- /dev/null +++ b/mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.ObjectPool; + +namespace MzLibUtil; + +// Used to pool HashSet instances to reduce memory allocations +public class DictionaryPool where TKey : notnull +{ + private readonly ObjectPool> _pool; + + /// + /// Initializes a new instance of the class. + /// + /// Initial capacity for the pooled Dictionary instances. + public DictionaryPool(int initialCapacity = 16) + { + var policy = new DictionaryPooledObjectPolicy(initialCapacity); + var provider = new DefaultObjectPoolProvider { MaximumRetained = Environment.ProcessorCount * 2 }; + _pool = provider.Create(policy); + } + + /// + /// Retrieves a Dictionary instance from the pool. + /// + /// A Dictionary instance. + public Dictionary Get() => _pool.Get(); + + /// + /// Returns a Dictionary instance back to the pool. + /// + /// The Dictionary instance to return. + public void Return(Dictionary dictionary) + { + if (dictionary == null) throw new ArgumentNullException(nameof(dictionary)); + dictionary.Clear(); // Ensure the Dictionary is clean before returning it to the pool + _pool.Return(dictionary); + } + + private class DictionaryPooledObjectPolicy(int initialCapacity) + : PooledObjectPolicy> + where TKeyItem : notnull + { + private int InitialCapacity { get; } = initialCapacity; + + public override Dictionary Create() + { + return new Dictionary(capacity: InitialCapacity); + } + + public override bool Return(Dictionary obj) + { + // Ensure the Dictionary can be safely reused + obj.Clear(); + return true; + } + } +} \ No newline at end of file diff --git a/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs b/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs new file mode 100644 index 000000000..ef3e152d1 --- /dev/null +++ b/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.ObjectPool; + +namespace MzLibUtil; + +// Example Usage: +// var pool = new HashSetPool(); +// var hashSet = pool.Get(); +// hashSet.Add(1); +// Do Work +// pool.Return(hashSet); + +// Used to pool HashSet instances to reduce memory allocations +public class HashSetPool +{ + private readonly ObjectPool> _pool; + + /// + /// Initializes a new instance of the class. + /// + /// Initial capacity for the pooled HashSet instances. + public HashSetPool(int initialCapacity = 16) + { + var policy = new HashSetPooledObjectPolicy(initialCapacity); + _pool = new DefaultObjectPool>(policy); + } + + /// + /// Retrieves a HashSet instance from the pool. + /// + /// A HashSet instance. + public HashSet Get() => _pool.Get(); + + /// + /// Returns a HashSet instance back to the pool. + /// + /// The HashSet instance to return. + public void Return(HashSet hashSet) + { + if (hashSet == null) throw new ArgumentNullException(nameof(hashSet)); + hashSet.Clear(); // Ensure the HashSet is clean before returning it to the pool + _pool.Return(hashSet); + } + + private class HashSetPooledObjectPolicy(int initialCapacity) : PooledObjectPolicy> + { + public override HashSet Create() + { + return new HashSet(capacity: initialCapacity); + } + + public override bool Return(HashSet obj) + { + // Ensure the HashSet can be safely reused + obj.Clear(); + return true; + } + } +} \ No newline at end of file diff --git a/mzLib/MzLibUtil/ObjectPools/ListPool.cs b/mzLib/MzLibUtil/ObjectPools/ListPool.cs new file mode 100644 index 000000000..310369b90 --- /dev/null +++ b/mzLib/MzLibUtil/ObjectPools/ListPool.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.ObjectPool; + +namespace MzLibUtil; + +// Used to pool HashSet instances to reduce memory allocations +public class ListPool +{ + private readonly ObjectPool> _pool; + + /// + /// Initializes a new instance of the class. + /// + /// Initial capacity for the pooled HashSet instances. + public ListPool(int initialCapacity = 16) + { + var policy = new ListPooledObjectPolicy(initialCapacity); + var provider = new DefaultObjectPoolProvider { MaximumRetained = Environment.ProcessorCount * 2 }; + _pool = provider.Create(policy); + } + + /// + /// Retrieves a HashSet instance from the pool. + /// + /// A HashSet instance. + public List Get() => _pool.Get(); + + /// + /// Returns a HashSet instance back to the pool. + /// + /// The HashSet instance to return. + public void Return(List list) + { + if (list == null) throw new ArgumentNullException(nameof(list)); + list.Clear(); // Ensure the HashSet is clean before returning it to the pool + _pool.Return(list); + } + + private class ListPooledObjectPolicy(int initialCapacity) : PooledObjectPolicy> + { + private int InitialCapacity { get; } = initialCapacity; + + public override List Create() + { + return new List(capacity: InitialCapacity); + } + + public override bool Return(List obj) + { + // Ensure the HashSet can be safely reused + obj.Clear(); + return true; + } + } +} \ No newline at end of file diff --git a/mzLib/Test/ObjectPoolTests.cs b/mzLib/Test/ObjectPoolTests.cs new file mode 100644 index 000000000..bac0600eb --- /dev/null +++ b/mzLib/Test/ObjectPoolTests.cs @@ -0,0 +1,120 @@ +using MzLibUtil; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Test; + +[TestFixture] +[ExcludeFromCodeCoverage] +public class HashSetPoolTests +{ + [Test] + public void Get_ReturnsHashSetInstance() + { + var pool = new HashSetPool(); + var hashSet = pool.Get(); + Assert.That(hashSet, Is.Not.Null); + pool.Return(hashSet); + } + + [Test] + public void Return_ClearsHashSetBeforeReturningToPool() + { + var pool = new HashSetPool(); + var hashSet = pool.Get(); + hashSet.Add(1); + pool.Return(hashSet); + Assert.That(hashSet.Count, Is.EqualTo(0)); + } + + [Test] + public void Return_ThrowsArgumentNullException_WhenHashSetIsNull() + { + var pool = new HashSetPool(); + Assert.Throws(() => pool.Return(null)); + } +} + +[TestFixture] +[ExcludeFromCodeCoverage] +public class DictionaryPoolTests +{ + [Test] + public void Get_ReturnsDictionaryInstance() + { + var dictionaryPool = new DictionaryPool(); + var dictionary = dictionaryPool.Get(); + Assert.That(dictionary, Is.Not.Null); + Assert.That(dictionary, Is.InstanceOf>()); + } + + [Test] + public void Return_ClearsAndReturnsDictionaryToPool() + { + var dictionaryPool = new DictionaryPool(); + var dictionary = dictionaryPool.Get(); + dictionary["key"] = 42; + + dictionaryPool.Return(dictionary); + + Assert.That(dictionary.Count, Is.EqualTo(0)); + } + + [Test] + public void Return_ThrowsArgumentNullException_WhenDictionaryIsNull() + { + var dictionaryPool = new DictionaryPool(); + Assert.Throws(() => dictionaryPool.Return(null)); + } +} + +[TestFixture] +[ExcludeFromCodeCoverage] +public class ListPoolTests +{ + [Test] + public void ListPool_Get_ReturnsListWithInitialCapacity() + { + // Arrange + int initialCapacity = 16; + var listPool = new ListPool(initialCapacity); + + // Act + var list = listPool.Get(); + + // Assert + Assert.That(list, Is.Not.Null); + Assert.That(list.Capacity, Is.EqualTo(initialCapacity)); + } + + [Test] + public void ListPool_Return_ClearsListBeforeReturningToPool() + { + // Arrange + var listPool = new ListPool(); + var list = listPool.Get(); + list.Add(1); + list.Add(2); + + // Act + listPool.Return(list); + var returnedList = listPool.Get(); + + // Assert + Assert.That(returnedList, Is.Not.Null); + Assert.That(returnedList, Is.Empty); + } + + [Test] + public void ListPool_Return_ThrowsArgumentNullException_WhenListIsNull() + { + // Arrange + var listPool = new ListPool(); + + // Act & Assert + Assert.That(() => listPool.Return(null), Throws.ArgumentNullException); + } +} + diff --git a/mzLib/mzLib.nuspec b/mzLib/mzLib.nuspec index 3aa393afe..8cee705ec 100644 --- a/mzLib/mzLib.nuspec +++ b/mzLib/mzLib.nuspec @@ -23,6 +23,7 @@ + @@ -36,6 +37,7 @@ +