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

Batch overloads using an array pool #856

Merged
merged 35 commits into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bdaabf0
Add "Batch" overloads to get pooled buckets
atifaziz Oct 20, 2022
4d8464b
Refactor to return buckets cursor
atifaziz Oct 20, 2022
123a12e
Rename batch bucket into more general list view
atifaziz Oct 27, 2022
b49d27d
Add test to assert in-place updates
atifaziz Oct 27, 2022
ff023a2
Use test pool to assert rentals/returns
atifaziz Oct 27, 2022
9fb0a91
Make helper static
atifaziz Oct 27, 2022
c2d98fa
Fix code formatting
atifaziz Oct 27, 2022
851cde0
Test last return to the pool
atifaziz Oct 27, 2022
fa81c48
Fix disposal bugs
atifaziz Oct 27, 2022
02c1c1c
Test disposal of source
atifaziz Oct 27, 2022
8701b88
Review name and virtual members
atifaziz Oct 29, 2022
b2c3e02
Add overload with current list query that returns a sequence
atifaziz Oct 29, 2022
5d45266
Keep just the array pool version
atifaziz Oct 29, 2022
0a4a3a7
Fix type parameter doc comment
atifaziz Oct 29, 2022
aec464c
Split current list from its provider
atifaziz Oct 29, 2022
3175ce9
Retain just the simpler overload publicly
atifaziz Oct 29, 2022
07e7ce6
Add simpler overload
atifaziz Oct 29, 2022
1e70366
Add test to assert in-place updates
atifaziz Oct 29, 2022
55daf66
Fix current list provider implementation name
atifaziz Oct 29, 2022
cca8965
Call query selector before iterating source
atifaziz Oct 29, 2022
ff226ef
Rename query to bucket
atifaziz Oct 29, 2022
70bb45b
Rename query to bucket projection
atifaziz Oct 29, 2022
e707297
Revise conditional constants
atifaziz Oct 29, 2022
d61ec03
Remove extra blank line
atifaziz Oct 29, 2022
61dbc84
Remove "AsSpan" from current list
atifaziz Oct 29, 2022
41fcf74
Update overload count in read-me doc
atifaziz Oct 30, 2022
b555c3d
Reuse "Enumerable.Empty" enumerator
atifaziz Oct 31, 2022
be1f763
Remove "AsSpan" from current list abstract
atifaziz Oct 31, 2022
d1cd8cc
Fix doc about bucket streaming/buffering
atifaziz Oct 31, 2022
0fc8901
Rename list in current list types to buffer
atifaziz Oct 31, 2022
03b8112
Merge remote-tracking branch 'upstream/master' into batch-pool
atifaziz Nov 3, 2022
43f7582
Expand "bucketProjectionSelector" doc
atifaziz Nov 12, 2022
2356f60
Merge remote-tracking branch 'upstream/master' into batch-pool
atifaziz Nov 12, 2022
bdf76a7
Reword remarks note to read clearer.
atifaziz Nov 12, 2022
ec82d1b
Reword remarks note to read clearer.
atifaziz Nov 12, 2022
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
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
]
},
"dotnet-reportgenerator-globaltool": {
"version": "4.6.4",
"version": "5.1.10",
"commands": [
"reportgenerator"
]
Expand Down
272 changes: 261 additions & 11 deletions MoreLinq.Test/BatchTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,12 @@ namespace MoreLinq.Test
[TestFixture]
public class BatchTest
{
[Test]
public void BatchZeroSize()
{
AssertThrowsArgument.OutOfRangeException("size",() =>
new object[0].Batch(0));
}

[Test]
public void BatchNegativeSize()
[TestCase(0)]
[TestCase(-1)]
public void BatchBadSize(int size)
{
AssertThrowsArgument.OutOfRangeException("size",() =>
new object[0].Batch(-1));
AssertThrowsArgument.OutOfRangeException("size", () =>
new object[0].Batch(size));
}

[Test]
Expand Down Expand Up @@ -141,3 +135,259 @@ public void BatchEmptySource(SourceKind kind)
}
}
}

#if NETCOREAPP3_1_OR_GREATER

namespace MoreLinq.Test
{
using System;
using System.Buffers;
using System.Collections.Generic;
using MoreLinq.Experimental;
using NUnit.Framework;

[TestFixture]
public class BatchPoolTest
{
[TestCase(0)]
[TestCase(-1)]
public void BatchBadSize(int size)
{
AssertThrowsArgument.OutOfRangeException("size", () =>
new object[0].Batch(size, ArrayPool<object>.Shared,
BreakingFunc.Of<ICurrentBuffer<object>, IEnumerable<object>>(),
BreakingFunc.Of<IEnumerable<object>, object>()));
}

[Test]
public void BatchEvenlyDivisibleSequence()
{
using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();

var result = input.Batch(3, pool, Enumerable.ToArray);

using var reader = result.Read();
reader.Read().AssertSequenceEqual(1, 2, 3);
reader.Read().AssertSequenceEqual(4, 5, 6);
reader.Read().AssertSequenceEqual(7, 8, 9);
reader.ReadEnd();
}

[Test]
public void BatchUnevenlyDivisibleSequence()
{
using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();

var result = input.Batch(4, pool, Enumerable.ToArray);

using var reader = result.Read();
reader.Read().AssertSequenceEqual(1, 2, 3, 4);
reader.Read().AssertSequenceEqual(5, 6, 7, 8);
reader.Read().AssertSequenceEqual(9);
reader.ReadEnd();
}

[Test]
public void BatchIsLazy()
{
var input = new BreakingSequence<object>();
_ = input.Batch(1, ArrayPool<object>.Shared,
BreakingFunc.Of<ICurrentBuffer<object>, IEnumerable<object>>(),
BreakingFunc.Of<IEnumerable<object>, object>());
}

[TestCase(SourceKind.BreakingList , 0)]
[TestCase(SourceKind.BreakingReadOnlyList, 0)]
[TestCase(SourceKind.BreakingList , 1)]
[TestCase(SourceKind.BreakingReadOnlyList, 1)]
[TestCase(SourceKind.BreakingList , 2)]
[TestCase(SourceKind.BreakingReadOnlyList, 2)]
public void BatchCollectionSmallerThanSize(SourceKind kind, int oversize)
{
var xs = new[] { 1, 2, 3, 4, 5 };
using var pool = new TestArrayPool<int>();

var result = xs.ToSourceKind(kind)
.Batch(xs.Length + oversize, pool, Enumerable.ToArray);

using var reader = result.Read();
reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5);
reader.ReadEnd();
}

[Test]
public void BatchReadOnlyCollectionSmallerThanSize()
{
var collection = ReadOnlyCollection.From(1, 2, 3, 4, 5);
using var pool = new TestArrayPool<int>();

var result = collection.Batch(collection.Count * 2, pool,
Enumerable.ToArray);

using var reader = result.Read();
reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5);
reader.ReadEnd();
}

[TestCase(SourceKind.Sequence)]
[TestCase(SourceKind.BreakingList)]
[TestCase(SourceKind.BreakingReadOnlyList)]
[TestCase(SourceKind.BreakingReadOnlyCollection)]
[TestCase(SourceKind.BreakingCollection)]
public void BatchEmptySource(SourceKind kind)
{
using var pool = new TestArrayPool<int>();

var result = Enumerable.Empty<int>()
.ToSourceKind(kind)
.Batch(100, pool, Enumerable.ToArray);

Assert.That(result, Is.Empty);
}

[Test]
public void BatchFilterBucket()
{
const int scale = 2;
var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();

var result = input.Batch(3, pool,
current => from n in current
where n % 2 == 0
select n * scale,
Enumerable.ToArray);

using var reader = result.Read();
reader.Read().AssertSequenceEqual(2 * scale);
reader.Read().AssertSequenceEqual(4 * scale, 6 * scale);
reader.Read().AssertSequenceEqual(8 * scale);
reader.ReadEnd();
}

[Test]
public void BatchSumBucket()
{
var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();

var result = input.Batch(3, pool, Enumerable.Sum);

using var reader = result.Read();
Assert.That(reader.Read(), Is.EqualTo(1 + 2 + 3));
Assert.That(reader.Read(), Is.EqualTo(4 + 5 + 6));
Assert.That(reader.Read(), Is.EqualTo(7 + 8 + 9));
reader.ReadEnd();
}

/// <remarks>
/// This test does not exercise the intended usage!
/// </remarks>

[Test]
public void BatchUpdatesCurrentListInPlace()
{
var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();

var result = input.Batch(4, pool, current => current, current => (ICurrentBuffer<int>)current);

using var reader = result.Read();
var current = reader.Read();
current.AssertSequenceEqual(1, 2, 3, 4);
_ = reader.Read();
current.AssertSequenceEqual(5, 6, 7, 8);
_ = reader.Read();
current.AssertSequenceEqual(9);

reader.ReadEnd();

Assert.That(current, Is.Empty);
}

[Test]
public void BatchCallsBucketSelectorBeforeIteratingSource()
{
var iterations = 0;
IEnumerable<int> Source()
{
iterations++;
yield break;
}

var input = Source();
using var pool = new TestArrayPool<int>();
var initIterations = -1;

var result = input.Batch(4, pool,
current =>
{
initIterations = iterations;
return current;
},
_ => 0);

using var enumerator = result.GetEnumerator();
Assert.That(enumerator.MoveNext(), Is.False);
Assert.That(initIterations, Is.Zero);
}

[Test]
public void BatchBucketSelectorCurrentList()
{
var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9);
using var pool = new TestArrayPool<int>();
int[] bucketSelectorItems = null;

var result = input.Batch(4, pool, current => bucketSelectorItems = current.ToArray(), _ => 0);

using var reader = result.Read();
_ = reader.Read();
Assert.That(bucketSelectorItems, Is.Not.Null);
Assert.That(bucketSelectorItems, Is.Empty);
}

/// <summary>
/// An <see cref="ArrayPool{T}"/> implementation for testing purposes that holds only
/// one array in the pool and ensures that it is returned when the pool is disposed.
/// </summary>

sealed class TestArrayPool<T> : ArrayPool<T>, IDisposable
{
T[] _pooledArray;
T[] _rentedArray;

public override T[] Rent(int minimumLength)
{
if (_pooledArray is null && _rentedArray is null)
_pooledArray = new T[minimumLength * 2];

if (_pooledArray is null)
throw new InvalidOperationException("The pool is exhausted.");

(_pooledArray, _rentedArray) = (null, _pooledArray);

return _rentedArray;
}

public override void Return(T[] array, bool clearArray = false)
{
if (_rentedArray is null)
throw new InvalidOperationException("Cannot return when nothing has been rented from this pool.");

if (array != _rentedArray)
throw new InvalidOperationException("Cannot return what has not been rented from this pool.");

_pooledArray = array;
_rentedArray = null;
}

public void Dispose() =>
Assert.That(_rentedArray, Is.Null);
}
}
}

#endif // NETCOREAPP3_1_OR_GREATER
Loading