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

Utilize ArrayPool for ECC calculations #519

Merged
merged 8 commits into from
May 12, 2024
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
266 changes: 260 additions & 6 deletions QRCoder/QRCodeGenerator.Polynom.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace QRCoder
Expand All @@ -8,21 +10,142 @@ public partial class QRCodeGenerator
/// <summary>
/// Represents a polynomial, which is a sum of polynomial terms.
/// </summary>
private struct Polynom
private struct Polynom : IDisposable
{
private PolynomItem[] _polyItems;
private int _length;

/// <summary>
/// Initializes a new instance of the <see cref="Polynom"/> struct with a specified number of initial capacity for polynomial terms.
/// </summary>
/// <param name="count">The initial capacity of the polynomial items list.</param>
public Polynom(int count)
{
this.PolyItems = new List<PolynomItem>(count);
_length = 0;
_polyItems = RentArray(count);
}

/// <summary>
/// Adds a polynomial term to the polynomial.
/// </summary>
public void Add(PolynomItem item)
{
AssertCapacity(_length + 1);
_polyItems[_length++] = item;
}

/// <summary>
/// Removes the polynomial term at the specified index.
/// </summary>
public void RemoveAt(int index)
{
if ((uint)index >= (uint)_length)
throw new IndexOutOfRangeException();
Shane32 marked this conversation as resolved.
Show resolved Hide resolved

if (index < _length - 1)
Array.Copy(_polyItems, index + 1, _polyItems, index, _length - index - 1);

_length--;
}

/// <summary>
/// Gets or sets a polynomial term at the specified index.
/// </summary>
public PolynomItem this[int index]
{
get {
if ((uint)index >= _length)
ThrowIndexOutOfRangeException();
return _polyItems[index];
}
set {
if ((uint)index >= _length)
ThrowIndexOutOfRangeException();
_polyItems[index] = value;
}
}

#if NET6_0_OR_GREATER
[StackTraceHidden]
#endif
private static void ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}


/// <summary>
/// Gets the number of polynomial terms in the polynomial.
/// </summary>
public int Count => _length;

/// <summary>
/// Removes all polynomial terms from the polynomial.
/// </summary>
public void Clear()
{
_length = 0;
}

/// <summary>
/// Gets or sets the list of polynomial items, where each item represents a term in the polynomial.
/// Clones the polynomial, creating a new instance with the same polynomial terms.
/// </summary>
public List<PolynomItem> PolyItems { get; set; }
public Polynom Clone()
{
var newPolynom = new Polynom(_length);
Array.Copy(_polyItems, newPolynom._polyItems, _length);
newPolynom._length = _length;
return newPolynom;
}

/// <summary>
/// Sorts the collection of <see cref="PolynomItem"/> using a custom comparer function.
/// </summary>
/// <param name="comparer">
/// A function that compares two <see cref="PolynomItem"/> objects and returns an integer indicating their relative order:
/// less than zero if the first is less than the second, zero if they are equal, or greater than zero if the first is greater than the second.
/// </param>
public void Sort(Func<PolynomItem, PolynomItem, int> comparer)
{
if (comparer == null) throw new ArgumentNullException(nameof(comparer));

var items = _polyItems;
if (items == null) throw new ObjectDisposedException(nameof(Polynom));

if (_length <= 1)
{
return; // Nothing to sort if the list is empty or contains only one element
}

void QuickSort(int left, int right)
{
int i = left;
int j = right;
PolynomItem pivot = items[(left + right) / 2];

while (i <= j)
{
while (comparer(items[i], pivot) < 0) i++;
while (comparer(items[j], pivot) > 0) j--;

if (i <= j)
{
// Swap items[i] and items[j]
PolynomItem temp = items[i];
items[i] = items[j];
items[j] = temp;
i++;
j--;
}
}

// Recursively sort the sub-arrays
if (left < j) QuickSort(left, j);
if (i < right) QuickSort(i, right);
}

QuickSort(0, _length - 1);
}

/// <summary>
/// Returns a string that represents the polynomial in standard algebraic notation.
Expand All @@ -32,7 +155,7 @@ public override string ToString()
{
var sb = new StringBuilder();

foreach (var polyItem in this.PolyItems)
foreach (var polyItem in _polyItems)
{
sb.Append("a^" + polyItem.Coefficient + "*x^" + polyItem.Exponent + " + ");
}
Expand All @@ -43,6 +166,137 @@ public override string ToString()

return sb.ToString();
}

/// <inheritdoc/>
public void Dispose()
{
ReturnArray(_polyItems);
_polyItems = null;
}

/// <summary>
/// Ensures that the polynomial has enough capacity to store the specified number of polynomial terms.
/// </summary>
private void AssertCapacity(int min)
{
if (_polyItems.Length < min)
{
// All math by QRCoder should be done with fixed polynomials, so we don't need to grow the capacity.
ThrowNotSupportedException();

// Sample code for growing the capacity:
//var newArray = RentArray(Math.Max(min - 1, 8) * 2); // Grow by 2x, but at least by 8
//Array.Copy(_polyItems, newArray, _length);
//ReturnArray(_polyItems);
//_polyItems = newArray;
}

#if NET6_0_OR_GREATER
[StackTraceHidden]
#endif
void ThrowNotSupportedException()
{
throw new NotSupportedException("The polynomial capacity is fixed and cannot be increased.");
}
}

#if NETCOREAPP
/// <summary>
/// Rents memory for the polynomial terms from the shared memory pool.
/// </summary>
private static PolynomItem[] RentArray(int count)
{
return System.Buffers.ArrayPool<PolynomItem>.Shared.Rent(count);
}

/// <summary>
/// Returns memory allocated for the polynomial terms back to the shared memory pool.
/// </summary>
private static void ReturnArray(PolynomItem[] array)
{
System.Buffers.ArrayPool<PolynomItem>.Shared.Return(array);
}
#else
// Implement a poor-man's array pool for .NET Framework
[ThreadStatic]
private static List<PolynomItem[]> _arrayPool;

/// <summary>
/// Rents memory for the polynomial terms from a shared memory pool.
/// </summary>
private static PolynomItem[] RentArray(int count)
{
if (count <= 0)
ThrowArgumentOutOfRangeException();

// Search for a suitable array in the thread-local pool, if it has been initialized
if (_arrayPool != null)
{
for (int i = 0; i < _arrayPool.Count; i++)
{
var array = _arrayPool[i];
if (array.Length >= count)
{
_arrayPool.RemoveAt(i);
return array;
}
}
}

// No suitable buffer found; create a new one
return new PolynomItem[count];

void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException(nameof(count), "The count must be a positive number.");
}
}

/// <summary>
/// Returns memory allocated for the polynomial terms back to a shared memory pool.
/// </summary>
private static void ReturnArray(PolynomItem[] array)
{
if (array == null)
ThrowArgumentNullException();

// Initialize the thread-local pool if it's not already done
if (_arrayPool == null)
_arrayPool = new List<PolynomItem[]>(8);

// Add the buffer back to the pool
_arrayPool.Add(array);

void ThrowArgumentNullException()
{
throw new ArgumentNullException(nameof(array));
}
}
#endif

/// <summary>
/// Returns an enumerator that iterates through the polynomial terms.
/// </summary>
public PolynumEnumerator GetEnumerator() => new PolynumEnumerator(this);

/// <summary>
/// Value type enumerator for the <see cref="Polynom"/> struct.
/// </summary>
public struct PolynumEnumerator
{
private Polynom _polynom;
private int _index;

public PolynumEnumerator(Polynom polynom)
{
_polynom = polynom;
_index = -1;
}

public PolynomItem Current => _polynom[_index];

public bool MoveNext() => ++_index < _polynom._length;
}
}
}
}
Loading