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..0e3efec5c
--- /dev/null
+++ b/mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.ObjectPool;
+
+namespace MzLibUtil;
+
+// Example Usage:
+// var pool = new DictionaryPool();
+// var dictionary = pool.Get();
+// try {
+// dictionary.Add(1,1);
+// Do Work
+// }
+// finally {
+// pool.Return(dictionary);
+// }
+
+///
+/// Provides a pool for instances to reduce memory allocations.
+/// This class uses the from Microsoft.Extensions.ObjectPool
+/// to manage the pooling of objects.
+///
+/// The type of keys in the .
+/// The type of values in the .
+///
+/// This class is not thread-safe and should not be shared between threads.
+/// This class should be pulled from outside a try finally loop and finally should return the Dictionary to the pool to ensure proper pooling in the case of a caught exception.
+///
+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);
+ }
+
+ ///
+ /// Policy for pooling Dictionary instances with a specified initial capacity.
+ ///
+ /// The type of keys in the Dictionary.
+ /// The type of values in the Dictionary.
+ /// The initial capacity for the pooled Dictionary instances.
+ private class DictionaryPooledObjectPolicy(int initialCapacity)
+ : PooledObjectPolicy>
+ where TKeyItem : notnull
+ {
+ private int InitialCapacity { get; } = initialCapacity;
+
+ ///
+ /// Creates a new Dictionary instance with the specified initial capacity.
+ ///
+ /// A new Dictionary instance.
+ public override Dictionary Create()
+ {
+ return new Dictionary(capacity: InitialCapacity);
+ }
+
+ ///
+ /// Returns a Dictionary instance to the pool after clearing it.
+ ///
+ /// The Dictionary instance to return.
+ /// True if the Dictionary instance can be reused; otherwise, false.
+ public override bool Return(Dictionary obj)
+ {
+ // Ensure the Dictionary can be safely reused
+ obj.Clear();
+ return true;
+ }
+ }
+}
diff --git a/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs b/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs
new file mode 100644
index 000000000..ae33ffd99
--- /dev/null
+++ b/mzLib/MzLibUtil/ObjectPools/HashSetPool.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.ObjectPool;
+
+namespace MzLibUtil;
+
+
+// Example Usage:
+// var pool = new HashSetPool();
+// var hashSet = pool.Get();
+// try {
+// hashSet.Add(1);
+// Do Work
+// }
+// finally {
+// pool.Return(hashSet);
+// }
+
+///
+/// Provides a pool for instances to reduce memory allocations.
+/// This class uses the from Microsoft.Extensions.ObjectPool
+/// to manage the pooling of objects.
+///
+/// The type of elements in the .
+///
+/// This class is not thread-safe and should not be shared between threads.
+/// This class should be pulled from outside a try finally loop and finally should return the HashSet to the pool to ensure proper pooling in the case of a caught exception
+/// See example found in DigestionAgent.GetDigestionSiteIndices() for proper usage
+///
+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 instance from the pool.
+ ///
+ /// A instance.
+ public HashSet Get() => _pool.Get();
+
+ ///
+ /// Returns a instance back to the pool.
+ ///
+ /// The 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);
+ }
+
+ ///
+ /// Defines the policy for creating and returning instances to the pool.
+ ///
+ /// The type of elements in the .
+ private class HashSetPooledObjectPolicy(int initialCapacity) : PooledObjectPolicy>
+ {
+ ///
+ /// Creates a new instance with the specified initial capacity.
+ ///
+ /// A new instance.
+ public override HashSet Create()
+ {
+ return new HashSet(capacity: initialCapacity);
+ }
+
+ ///
+ /// Returns a instance to the pool after clearing it.
+ ///
+ /// The instance to return.
+ /// Always returns true.
+ public override bool Return(HashSet obj)
+ {
+ // Ensure the HashSet can be safely reused
+ obj.Clear();
+ return true;
+ }
+ }
+}
diff --git a/mzLib/MzLibUtil/ObjectPools/ListPool.cs b/mzLib/MzLibUtil/ObjectPools/ListPool.cs
new file mode 100644
index 000000000..9f25a7926
--- /dev/null
+++ b/mzLib/MzLibUtil/ObjectPools/ListPool.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.ObjectPool;
+
+namespace MzLibUtil;
+
+// Example Usage:
+// var pool = new ListPool();
+// var list = pool.Get();
+// try {
+// list.Add(1);
+// Do Work
+// }
+// finally {
+// pool.Return(list);
+// }
+
+///
+/// Provides a pool for instances to reduce memory allocations.
+/// This class uses the from Microsoft.Extensions.ObjectPool
+/// to manage the pooling of objects.
+///
+/// The type of elements in the .
+///
+/// This class is not thread-safe and should not be shared between threads.
+/// This class should be pulled from outside a try finally loop and finally should return the List to the pool to ensure proper pooling in the case of a caught exception.
+///
+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);
+ }
+
+ ///
+ /// Policy for pooling List instances with a specified initial capacity.
+ ///
+ /// The type of elements in the List.
+ /// The initial capacity for the pooled List instances.
+ private class ListPooledObjectPolicy(int initialCapacity) : PooledObjectPolicy>
+ {
+ private int InitialCapacity { get; } = initialCapacity;
+
+ ///
+ /// Creates a new List instance with the specified initial capacity.
+ ///
+ /// A new List instance.
+ public override List Create()
+ {
+ return new List(capacity: InitialCapacity);
+ }
+
+ ///
+ /// Resets the List instance to a clean state before returning it to the pool.
+ ///
+ /// The List instance to reset and return.
+ /// True if the List instance can be returned to the pool; otherwise, false.
+ public override bool Return(List obj)
+ {
+ // Ensure the List can be safely reused
+ obj.Clear();
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/mzLib/Omics/Digestion/DigestionAgent.cs b/mzLib/Omics/Digestion/DigestionAgent.cs
index 88794de06..734d5e65f 100644
--- a/mzLib/Omics/Digestion/DigestionAgent.cs
+++ b/mzLib/Omics/Digestion/DigestionAgent.cs
@@ -1,9 +1,12 @@
-using Omics.Modifications;
+using MzLibUtil;
+using Omics.Modifications;
namespace Omics.Digestion
{
public abstract class DigestionAgent
{
+ protected static readonly HashSetPool HashSetPool = new HashSetPool(8);
+
protected DigestionAgent(string name, CleavageSpecificity cleavageSpecificity, List motifList, Modification cleavageMod)
{
Name = name;
@@ -73,40 +76,48 @@ protected static bool ValidMaxLength(int? length, int maxLength)
///
public List GetDigestionSiteIndices(string sequence)
{
- var indices = new List();
-
- for (int r = 0; r < sequence.Length; r++)
+ var indices = HashSetPool.Get(); // use hash set to ensure no duplicates
+ try // Try block is to ensure that, even if an error gets thrown, the hashset is returned to the pool
{
- var cutSiteIndex = -1;
- bool cleavagePrevented = false;
+ indices.Add(0); // The start of the protein is treated as a cleavage site to retain the n-terminal peptide
- foreach (DigestionMotif motif in DigestionMotifs)
+ for (int r = 0; r < sequence.Length; r++)
{
- var motifResults = motif.Fits(sequence, r);
- bool motifFits = motifResults.Item1;
- bool motifPreventsCleavage = motifResults.Item2;
+ var cutSiteIndex = -1;
+ bool cleavagePrevented = false;
- if (motifFits && r + motif.CutIndex < sequence.Length)
+ foreach (DigestionMotif motif in DigestionMotifs)
{
- cutSiteIndex = Math.Max(r + motif.CutIndex, cutSiteIndex);
+ var motifResults = motif.Fits(sequence, r);
+ bool motifFits = motifResults.Item1;
+ bool motifPreventsCleavage = motifResults.Item2;
+
+ if (motifFits && r + motif.CutIndex < sequence.Length)
+ {
+ cutSiteIndex = Math.Max(r + motif.CutIndex, cutSiteIndex);
+ }
+
+ if (motifPreventsCleavage) // if any motif prevents cleave
+ {
+ cleavagePrevented = true;
+ }
}
- if (motifPreventsCleavage) // if any motif prevents cleave
+ // if no motif prevents cleave
+ if (!cleavagePrevented && cutSiteIndex != -1)
{
- cleavagePrevented = true;
+ indices.Add(cutSiteIndex);
}
}
- // if no motif prevents cleave
- if (!cleavagePrevented && cutSiteIndex != -1)
- {
- indices.Add(cutSiteIndex);
- }
+ indices.Add(sequence.Length); // The end of the protein is treated as a cleavage site to retain the c-terminal peptide
+ return indices.ToList(); // convert the hashset to a list for return.
+ }
+ finally
+ {
+ // return hashset to pool. This clears it and gets it ready for the next time it is needed from the pool.
+ HashSetPool.Return(indices);
}
-
- indices.Add(0); // The start of the protein is treated as a cleavage site to retain the n-terminal peptide
- indices.Add(sequence.Length); // The end of the protein is treated as a cleavage site to retain the c-terminal peptide
- return indices.Distinct().OrderBy(i => i).ToList();
}
}
}
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 6cc79db4b..b9c326d5d 100644
--- a/mzLib/mzLib.nuspec
+++ b/mzLib/mzLib.nuspec
@@ -23,6 +23,7 @@
+
@@ -36,6 +37,7 @@
+