Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object Pooling #822

Merged
merged 6 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mzLib/MzLibUtil/MzLibUtil.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageReference Include="CsvHelper" Version="32.0.3" />
<PackageReference Include="Easy.Common" Version="6.7.0" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup>

Expand Down
2 changes: 2 additions & 0 deletions mzLib/MzLibUtil/MzLibUtil.csproj.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=objectpools/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
94 changes: 94 additions & 0 deletions mzLib/MzLibUtil/ObjectPools/DictionaryPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.ObjectPool;

namespace MzLibUtil;

// Example Usage:
// var pool = new DictionaryPool<int, int>();
// var dictionary = pool.Get();
// try {
// dictionary.Add(1,1);
// Do Work
// }
// finally {
// pool.Return(dictionary);
// }

/// <summary>
/// Provides a pool for <see cref="Dictionary{TKey, TValue}"/> instances to reduce memory allocations.
/// This class uses the <see cref="ObjectPool{T}"/> from Microsoft.Extensions.ObjectPool
/// to manage the pooling of <see cref="Dictionary{TKey, TValue}"/> objects.
/// </summary>
/// <typeparam name="TKey">The type of keys in the <see cref="Dictionary{TKey, TValue}"/>.</typeparam>
/// <typeparam name="TValue">The type of values in the <see cref="Dictionary{TKey, TValue}"/>.</typeparam>
/// <remarks>
/// 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.
/// </remarks>
public class DictionaryPool<TKey, TValue> where TKey : notnull
{
private readonly ObjectPool<Dictionary<TKey, TValue>> _pool;

/// <summary>
/// Initializes a new instance of the <see cref="DictionaryPool{TKey, TValue}"/> class.
/// </summary>
/// <param name="initialCapacity">Initial capacity for the pooled Dictionary instances.</param>
public DictionaryPool(int initialCapacity = 16)
{
var policy = new DictionaryPooledObjectPolicy<TKey, TValue>(initialCapacity);
var provider = new DefaultObjectPoolProvider { MaximumRetained = Environment.ProcessorCount * 2 };
_pool = provider.Create(policy);
}

/// <summary>
/// Retrieves a Dictionary instance from the pool.
/// </summary>
/// <returns>A Dictionary instance.</returns>
public Dictionary<TKey, TValue> Get() => _pool.Get();

/// <summary>
/// Returns a Dictionary instance back to the pool.
/// </summary>
/// <param name="dictionary">The Dictionary instance to return.</param>
public void Return(Dictionary<TKey, TValue> 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);
}

/// <summary>
/// Policy for pooling Dictionary instances with a specified initial capacity.
/// </summary>
/// <typeparam name="TKeyItem">The type of keys in the Dictionary.</typeparam>
/// <typeparam name="TValueItem">The type of values in the Dictionary.</typeparam>
/// <param name="initialCapacity">The initial capacity for the pooled Dictionary instances.</param>
private class DictionaryPooledObjectPolicy<TKeyItem, TValueItem>(int initialCapacity)
: PooledObjectPolicy<Dictionary<TKeyItem, TValueItem>>
where TKeyItem : notnull
{
private int InitialCapacity { get; } = initialCapacity;

/// <summary>
/// Creates a new Dictionary instance with the specified initial capacity.
/// </summary>
/// <returns>A new Dictionary instance.</returns>
public override Dictionary<TKeyItem, TValueItem> Create()
{
return new Dictionary<TKeyItem, TValueItem>(capacity: InitialCapacity);
}

/// <summary>
/// Returns a Dictionary instance to the pool after clearing it.
/// </summary>
/// <param name="obj">The Dictionary instance to return.</param>
/// <returns>True if the Dictionary instance can be reused; otherwise, false.</returns>
public override bool Return(Dictionary<TKeyItem, TValueItem> obj)
{
// Ensure the Dictionary can be safely reused
obj.Clear();
return true;
}
}
}
88 changes: 88 additions & 0 deletions mzLib/MzLibUtil/ObjectPools/HashSetPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.ObjectPool;

namespace MzLibUtil;


// Example Usage:
// var pool = new HashSetPool<int>();
// var hashSet = pool.Get();
// try {
// hashSet.Add(1);
// Do Work
// }
// finally {
// pool.Return(hashSet);
// }

/// <summary>
/// Provides a pool for <see cref="HashSet{T}"/> instances to reduce memory allocations.
/// This class uses the <see cref="ObjectPool{T}"/> from Microsoft.Extensions.ObjectPool
/// to manage the pooling of <see cref="HashSet{T}"/> objects.
/// </summary>
/// <typeparam name="T">The type of elements in the <see cref="HashSet{T}"/>.</typeparam>
/// <remarks>
/// 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
/// </remarks>
public class HashSetPool<T>
{
private readonly ObjectPool<HashSet<T>> _pool;

/// <summary>
/// Initializes a new instance of the <see cref="HashSetPool{T}"/> class.
/// </summary>
/// <param name="initialCapacity">Initial capacity for the pooled HashSet instances.</param>
public HashSetPool(int initialCapacity = 16)
{
var policy = new HashSetPooledObjectPolicy<T>(initialCapacity);
_pool = new DefaultObjectPool<HashSet<T>>(policy);
}

/// <summary>
/// Retrieves a <see cref="HashSet{T}"/> instance from the pool.
/// </summary>
/// <returns>A <see cref="HashSet{T}"/> instance.</returns>
public HashSet<T> Get() => _pool.Get();

/// <summary>
/// Returns a <see cref="HashSet{T}"/> instance back to the pool.
/// </summary>
/// <param name="hashSet">The <see cref="HashSet{T}"/> instance to return.</param>
public void Return(HashSet<T> 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);
}

/// <summary>
/// Defines the policy for creating and returning <see cref="HashSet{T}"/> instances to the pool.
/// </summary>
/// <typeparam name="TItem">The type of elements in the <see cref="HashSet{T}"/>.</typeparam>
private class HashSetPooledObjectPolicy<TItem>(int initialCapacity) : PooledObjectPolicy<HashSet<TItem>>
{
/// <summary>
/// Creates a new <see cref="HashSet{T}"/> instance with the specified initial capacity.
/// </summary>
/// <returns>A new <see cref="HashSet{T}"/> instance.</returns>
public override HashSet<TItem> Create()
{
return new HashSet<TItem>(capacity: initialCapacity);
}

/// <summary>
/// Returns a <see cref="HashSet{T}"/> instance to the pool after clearing it.
/// </summary>
/// <param name="obj">The <see cref="HashSet{T}"/> instance to return.</param>
/// <returns>Always returns true.</returns>
public override bool Return(HashSet<TItem> obj)
{
// Ensure the HashSet can be safely reused
obj.Clear();
return true;
}
}
}
90 changes: 90 additions & 0 deletions mzLib/MzLibUtil/ObjectPools/ListPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.ObjectPool;

namespace MzLibUtil;

// Example Usage:
// var pool = new ListPool<int>();
// var list = pool.Get();
// try {
// list.Add(1);
// Do Work
// }
// finally {
// pool.Return(list);
// }

/// <summary>
/// Provides a pool for <see cref="List{T}"/> instances to reduce memory allocations.
/// This class uses the <see cref="ObjectPool{T}"/> from Microsoft.Extensions.ObjectPool
/// to manage the pooling of <see cref="List{T}"/> objects.
/// </summary>
/// <typeparam name="T">The type of elements in the <see cref="List{T}"/>.</typeparam>
/// <remarks>
/// 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.
/// </remarks>
public class ListPool<T>
{
private readonly ObjectPool<List<T>> _pool;

/// <summary>
/// Initializes a new instance of the <see cref="ListPool{T}"/> class.
/// </summary>
/// <param name="initialCapacity">Initial capacity for the pooled HashSet instances.</param>
public ListPool(int initialCapacity = 16)
{
var policy = new ListPooledObjectPolicy<T>(initialCapacity);
var provider = new DefaultObjectPoolProvider { MaximumRetained = Environment.ProcessorCount * 2 };
_pool = provider.Create(policy);
}

/// <summary>
/// Retrieves a HashSet instance from the pool.
/// </summary>
/// <returns>A HashSet instance.</returns>
public List<T> Get() => _pool.Get();

/// <summary>
/// Returns a HashSet instance back to the pool.
/// </summary>
/// <param name="list">The HashSet instance to return.</param>
public void Return(List<T> 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);
}

/// <summary>
/// Policy for pooling List instances with a specified initial capacity.
/// </summary>
/// <typeparam name="TItem">The type of elements in the List.</typeparam>
/// <param name="initialCapacity">The initial capacity for the pooled List instances.</param>
private class ListPooledObjectPolicy<TItem>(int initialCapacity) : PooledObjectPolicy<List<TItem>>
{
private int InitialCapacity { get; } = initialCapacity;

/// <summary>
/// Creates a new List instance with the specified initial capacity.
/// </summary>
/// <returns>A new List instance.</returns>
public override List<TItem> Create()
{
return new List<TItem>(capacity: InitialCapacity);
}

/// <summary>
/// Resets the List instance to a clean state before returning it to the pool.
/// </summary>
/// <param name="obj">The List instance to reset and return.</param>
/// <returns>True if the List instance can be returned to the pool; otherwise, false.</returns>
public override bool Return(List<TItem> obj)
{
// Ensure the List can be safely reused
obj.Clear();
return true;
}
}
}
57 changes: 34 additions & 23 deletions mzLib/Omics/Digestion/DigestionAgent.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Omics.Modifications;
using MzLibUtil;
using Omics.Modifications;

namespace Omics.Digestion
{
public abstract class DigestionAgent
{
protected static readonly HashSetPool<int> HashSetPool = new HashSetPool<int>(8);

protected DigestionAgent(string name, CleavageSpecificity cleavageSpecificity, List<DigestionMotif> motifList, Modification cleavageMod)
{
Name = name;
Expand Down Expand Up @@ -73,40 +76,48 @@ protected static bool ValidMaxLength(int? length, int maxLength)
/// <returns></returns>
public List<int> GetDigestionSiteIndices(string sequence)
{
var indices = new List<int>();

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();
}
}
}
Loading
Loading