Skip to content

Commit

Permalink
Merge pull request #51 from faustodavid/AddSupportToSpanFromConstruct…
Browse files Browse the repository at this point in the history
…orListPool

Add support to span from constructor in ListPool
  • Loading branch information
faustodavid authored Feb 7, 2020
2 parents c08370b + 00720f4 commit a23937e
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 227 deletions.
2 changes: 1 addition & 1 deletion perf/ListPool.Benchmarks/ListPoolCopyToBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ namespace ListPool.Benchmarks
public class ListPoolCopyToBenchmarks
{
private List<int> _list;
private ListPool<int> _listPool;
private int[] _listCopy;
private ListPool<int> _listPool;

[Params(100, 1_000, 10_000)]
public int N { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;
using Utf8Json;

namespace ListPool.Benchmarks
{
[RPlotExporter, RankColumn]
[RPlotExporter]
[RankColumn]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[MemoryDiagnoser]
[GcServer(true)]
Expand All @@ -20,26 +22,28 @@ public class Utf8JsonDeserializeListOfIntBenchmarks
[GlobalSetup]
public void GlobalSetup()
{
_serializedList = Utf8Json.JsonSerializer.Serialize(Enumerable.Range(0, N));
_serializedList = JsonSerializer.Serialize(Enumerable.Range(0, N));
}

[Benchmark(Baseline = true)]
public int List()
{
List<int> list = Utf8Json.JsonSerializer.Deserialize<List<int>>(_serializedList);
List<int> list = JsonSerializer.Deserialize<List<int>>(_serializedList);
return list.Count;
}

[Benchmark]
public int ListPool()
{
using ListPool<int> list = Utf8Json.JsonSerializer.Deserialize<ListPool<int>>(_serializedList);
using ListPool<int> list = JsonSerializer.Deserialize<ListPool<int>>(_serializedList);
return list.Count;
}

[Benchmark]
public int ListPool_Spreads()
{
using ListPool<int> list = Spreads.Serialization.Utf8Json.JsonSerializer.Deserialize<ListPool<int>>(_serializedList);
using ListPool<int> list =
Spreads.Serialization.Utf8Json.JsonSerializer.Deserialize<ListPool<int>>(_serializedList);
return list.Count;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public int ListPool()
return serializedItems.Length;
}


[Benchmark]
public int ListPool_Spreads()
{
Expand Down
82 changes: 69 additions & 13 deletions src/ListPool/ListPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ public ListPool(int capacity)
}

/// <summary>
/// Construct ListPool from the given source.
/// Construct ListPool and copy the given source into a pooled buffer.
/// </summary>
/// <param name="source"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ListPool(IEnumerable<T> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));

if (source is ICollection<T> collection)
{
T[] buffer = ArrayPool<T>.Shared.Rent(collection.Count > MinimumCapacity ? collection.Count : MinimumCapacity);
T[] buffer =
ArrayPool<T>.Shared.Rent(collection.Count > MinimumCapacity ? collection.Count : MinimumCapacity);

collection.CopyTo(buffer, 0);

Expand All @@ -75,8 +77,8 @@ public ListPool(IEnumerable<T> source)
else
{
Count = count;
count++;
AddWithResize(enumerator.Current);
count++;
buffer = _buffer;
}
}
Expand All @@ -86,7 +88,37 @@ public ListPool(IEnumerable<T> source)
}

/// <summary>
/// Capacity of the underlying array.
/// Construct ListPool and copy source into new pooled buffer
/// </summary>
/// <param name="source"></param>
public ListPool(T[] source)
{
if (source == null) throw new ArgumentNullException(nameof(source));

int capacity = source.Length > MinimumCapacity ? source.Length : MinimumCapacity;
T[] buffer = ArrayPool<T>.Shared.Rent(capacity);
source.CopyTo(buffer, 0);

_buffer = buffer;
Count = source.Length;
}

/// <summary>
/// Construct ListPool and copy source into new pooled buffer
/// </summary>
/// <param name="source"></param>
public ListPool(ReadOnlySpan<T> source)
{
int capacity = source.Length > MinimumCapacity ? source.Length : MinimumCapacity;
T[] buffer = ArrayPool<T>.Shared.Rent(capacity);
source.CopyTo(buffer);

_buffer = buffer;
Count = source.Length;
}

/// <summary>
/// Capacity of the underlying pooled array.
/// </summary>
public int Capacity => _buffer.Length;

Expand All @@ -113,7 +145,7 @@ object ICollection.SyncRoot
{
if (_syncRoot is null)
{
_ = Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
}

return _syncRoot;
Expand Down Expand Up @@ -240,9 +272,19 @@ public void Add(T item)
}
}

/// <summary>
/// Clears the contents of List.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear() => Count = 0;

/// <summary>
/// Contains returns true if the specified element is in the List.
/// It does a linear, O(n) search. Equality is determined by calling
/// EqualityComparer&lt;T&gt;.Default.Equals().
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(T item) => IndexOf(item) > -1;

Expand All @@ -266,7 +308,13 @@ public bool Remove(T item)
return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary>
/// Inserts an element into this list at a given index. The size of the list
/// is increased by one. If required, the capacity of the list is doubled
/// before inserting the new element.
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void Insert(int index, T item)
{
int count = Count;
Expand Down Expand Up @@ -326,6 +374,12 @@ public T this[int index]
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(_buffer, Count);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_buffer, Count);

public void AddRange(Span<T> items)
{
int count = Count;
Expand Down Expand Up @@ -400,9 +454,17 @@ public void AddRange(IEnumerable<T> items)
}
}

/// <summary>
/// Get span of the items added
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> AsSpan() => _buffer.AsSpan(0, Count);

/// <summary>
/// Get memory of the items added
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Memory<T> AsMemory() => _buffer.AsMemory(0, Count);

Expand Down Expand Up @@ -435,12 +497,6 @@ private void GrowBuffer(int capacity)
arrayPool.Return(oldBuffer);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(_buffer, Count);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_buffer, Count);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new Enumerator(_buffer, Count);

Expand Down
7 changes: 4 additions & 3 deletions src/ListPool/ListPool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
<RepositoryUrl>https://github.com/faustodavid/ListPool</RepositoryUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageVersion>2.3.0</PackageVersion>
<PackageVersion>2.3.1</PackageVersion>
<PackageReleaseNotes>
Changelog:
* Breaking change for ValueListPool, now it doesn't implement IList but in exachnge is a ref struct and it supports stackalloc as a buffer for high performance with small dataset.
* We added support for ReadOnlySpan and Span in ListPool from constructor and extension methods
* Optimize creation of ListPool from Array

ListPool is the general use of the implementation. ValueListPool is the zero heap allocations implementation. Note, because it is a struct it is passed by value, not by reference.
ListPool is the general use of the implementation, we recommend to use ListPool for most of the cases. ValueListPool is the zero heap allocations implementation, it is optimal working along stackalloc initial buffer for small lists. Note, because it is a struct it is passed by value, not by reference.
</PackageReleaseNotes>
<Copyright>Copyright © Fausto David Suarez Rosario 2020</Copyright>
</PropertyGroup>
Expand Down
34 changes: 20 additions & 14 deletions src/ListPool/ListPoolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,29 @@ namespace ListPool
public static class ListPoolExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ListPool<T> ToListPool<T>(this IEnumerable<T> source)
{
if (source is null) throw new ArgumentNullException(nameof(source));
return new ListPool<T>(source);
}
public static ListPool<T> ToListPool<T>(this IEnumerable<T> source) => new ListPool<T>(source);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueListPool<T> ToValueListPool<T>(this Span<T> source, ValueListPool<T>.SourceType sourceType = ValueListPool<T>.SourceType.UseAsReferenceData) where T : IEquatable<T>
{
return new ValueListPool<T>(source, sourceType);
}
public static ListPool<T> ToListPool<T>(this Span<T> source) => new ListPool<T>(source);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueListPool<T> ToValueListPool<T>(this T[] source, ValueListPool<T>.SourceType sourceType = ValueListPool<T>.SourceType.UseAsReferenceData) where T : IEquatable<T>
{
if (source is null) throw new ArgumentNullException(nameof(source));
return new ValueListPool<T>(source, sourceType);
}
public static ListPool<T> ToListPool<T>(this ReadOnlySpan<T> source) => new ListPool<T>(source);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ListPool<T> ToListPool<T>(this T[] source) => new ListPool<T>(source);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueListPool<T> ToValueListPool<T>(this Span<T> source,
ValueListPool<T>.SourceType sourceType =
ValueListPool<T>.SourceType.UseAsReferenceData)
where T : IEquatable<T>
=> new ValueListPool<T>(source, sourceType);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ValueListPool<T> ToValueListPool<T>(this T[] source,
ValueListPool<T>.SourceType sourceType =
ValueListPool<T>.SourceType.UseAsReferenceData)
where T : IEquatable<T>
=> new ValueListPool<T>(source, sourceType);
}
}
1 change: 0 additions & 1 deletion src/ListPool/ValueListPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ public bool Remove(T item)
return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Insert(int index, T item)
{
int count = Count;
Expand Down
Loading

0 comments on commit a23937e

Please sign in to comment.