Skip to content

Commit

Permalink
Merge pull request #21 from thomaslevesque/perf-optim
Browse files Browse the repository at this point in the history
Performance optimization of Batch, IndexOf and LastIndexOf
  • Loading branch information
thomaslevesque authored Oct 12, 2020
2 parents 55d4b5f + 80ac4dd commit f85700f
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 17 deletions.
25 changes: 20 additions & 5 deletions src/Linq.Extras/Batch.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Linq.Extras.Internal;

Expand All @@ -22,10 +21,26 @@ public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
source.CheckArgumentNull(nameof(source));
size.CheckArgumentOutOfRange(nameof(size), 1, int.MaxValue);

return source
.WithIndex()
.GroupBy(x => x.Index / size)
.Select(g => g.WithoutIndex());
return BatchImpl(source, size);

static IEnumerable<IEnumerable<TSource>> BatchImpl(IEnumerable<TSource> source, int size)
{
var batch = new List<TSource>();
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == size)
{
yield return batch;
batch = new List<TSource>();
}
}

if (batch.Count > 0)
{
yield return batch;
}
}
}
}
}
51 changes: 40 additions & 11 deletions src/Linq.Extras/IndexOf.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Linq.Extras.Internal;

Expand All @@ -19,8 +18,17 @@ partial class XEnumerable
[Pure]
public static int IndexOf<TSource>([NotNull] this IEnumerable<TSource> source, TSource item, IEqualityComparer<TSource>? comparer = null)
{
source.CheckArgumentNull(nameof(source));
comparer = comparer ?? EqualityComparer<TSource>.Default;
return source.IndexOf(i => comparer.Equals(i, item));

int i = 0;
foreach (var currentItem in source)
{
if (comparer.Equals(currentItem, item))
return i;
i++;
}
return -1;
}

/// <summary>
Expand All @@ -35,10 +43,15 @@ public static int IndexOf<TSource>([NotNull] this IEnumerable<TSource> source, [
{
source.CheckArgumentNull(nameof(source));
predicate.CheckArgumentNull(nameof(predicate));
return source.WithIndex()
.Where(i => predicate(i.Item))
.Select(i => i.Index)
.FirstOrDefault(-1);

int i = 0;
foreach (var item in source)
{
if (predicate(item))
return i;
i++;
}
return -1;
}

/// <summary>
Expand All @@ -52,8 +65,18 @@ public static int IndexOf<TSource>([NotNull] this IEnumerable<TSource> source, [
[Pure]
public static int LastIndexOf<TSource>([NotNull] this IEnumerable<TSource> source, TSource item, IEqualityComparer<TSource>? comparer = null)
{
source.CheckArgumentNull(nameof(source));
comparer = comparer ?? EqualityComparer<TSource>.Default;
return source.LastIndexOf(i => comparer.Equals(i, item));

int i = 0;
int lastIndex = -1;
foreach (var currentItem in source)
{
if (comparer.Equals(currentItem, item))
lastIndex = i;
i++;
}
return lastIndex;
}

/// <summary>
Expand All @@ -68,10 +91,16 @@ public static int LastIndexOf<TSource>([NotNull] this IEnumerable<TSource> sourc
{
source.CheckArgumentNull(nameof(source));
predicate.CheckArgumentNull(nameof(predicate));
return source.WithIndex()
.Where(i => predicate(i.Item))
.Select(i => i.Index)
.LastOrDefault(-1);

int i = 0;
int lastIndex = -1;
foreach (var item in source)
{
if (predicate(item))
lastIndex = i;
i++;
}
return lastIndex;
}
}
}
49 changes: 49 additions & 0 deletions tests/Linq.Extras.Benchmarks/BatchBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Linq.Extras.Benchmarks
{
public class BatchBenchmark
{
private int[] _array;

[Params(100, 1000, 10000)]
public int N { get; set; }

[Params(10, 30, 600)]
public int BatchSize { get; set; }

[GlobalSetup]
public void Prepare()
{
_array = Enumerable.Range(0, N).ToArray();
}

[Benchmark(Baseline = true)]
public void RegularForLoop()
{
for (int i = 0; i < _array.Length; i += BatchSize)
{
int size = Math.Min(BatchSize, _array.Length - i);
var batch = new int[size];
for (int j = 0; j < size; j++)
{
batch[j] = _array[i + j];
}
}
}

[Benchmark]
public void UsingBatch()
{
foreach (var batch in _array.Batch(BatchSize))
{
}
}

}
}
45 changes: 45 additions & 0 deletions tests/Linq.Extras.Benchmarks/IndexOfBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Linq.Extras.Benchmarks
{
public class IndexOfBenchmark
{
private int[] _array;

[Params(10, 500, 10000)]
public int N { get; set; }

private const int SearchedValue = 42;

[GlobalSetup]
public void Prepare()
{
_array = Enumerable.Repeat(0, N).ToArray();
_array[N - 1] = SearchedValue;
}

[Benchmark(Baseline = true)]
public int RegularForLoop()
{
int value = SearchedValue;
for (int i = 0; i < N; i++)
{
if (_array[i] == value)
{
return i;
}
}

return -1;
}

[Benchmark]
public int UsingArrayIndexOf() => Array.IndexOf(_array, SearchedValue);

[Benchmark]
public int UsingIndexOf() => _array.IndexOf(SearchedValue);
}
}
3 changes: 2 additions & 1 deletion tests/Linq.Extras.Benchmarks/Linq.Extras.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit f85700f

Please sign in to comment.