-
-
Save VisualMelon/dfc9b49807ba5e467964fd1dd0af8274 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Running; | |
namespace SkipLastBenchmark | |
{ | |
public static class Exts | |
{ | |
static public IEnumerable<T> SkipLast1<T>(this IEnumerable<T> data, int count) | |
{ | |
if (data == null || count < 0) yield break; | |
Queue<T> queue = new Queue<T>(data.Take(count)); | |
foreach (T item in data.Skip(count)) | |
{ | |
queue.Enqueue(item); | |
yield return queue.Dequeue(); | |
} | |
} | |
static public IEnumerable<T> SkipLast2<T>(this IEnumerable<T> data, int count) | |
{ | |
if (data == null || count < 0) | |
yield break; | |
if (count == 0) | |
{ | |
foreach (T item in data) | |
yield return item; | |
} | |
else | |
{ | |
T[] queue = data.Take(count).ToArray(); | |
int index = 0; | |
foreach (T item in data.Skip(count)) | |
{ | |
index %= count; | |
yield return queue[index]; | |
queue[index] = item; | |
index++; | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast3<T>(this IEnumerable<T> source, int count) | |
{ | |
if (source == null) | |
throw new ArgumentNullException("Source Enumeration may not be null", nameof(source)); | |
if (count <= 0) | |
{ | |
foreach (T item in source) | |
yield return item; | |
} | |
else | |
{ | |
bool yielding = false; | |
T[] buffer = new T[count]; | |
int index = 0; | |
foreach (T item in source) | |
{ | |
if (index == count) | |
{ | |
index = 0; | |
yielding = true; | |
} | |
if (yielding) | |
yield return buffer[index]; | |
buffer[index] = item; | |
index++; | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast4<T>(this IEnumerable<T> source, int count) | |
{ | |
if (source == null) | |
throw new ArgumentNullException("Source Enumeration may not be null", nameof(source)); | |
if (count <= 0) | |
{ | |
foreach (T item in source) | |
yield return item; | |
} | |
else | |
{ | |
T[] buffer = new T[count]; | |
using (var e = source.GetEnumerator()) | |
{ | |
// initial filling of buffer | |
for (int i = 0; i < buffer.Length; i++) | |
{ | |
if (!e.MoveNext()) | |
yield break; | |
buffer[i] = e.Current; | |
} | |
int index = 0; | |
while (e.MoveNext()) | |
{ | |
yield return buffer[index]; | |
buffer[index] = e.Current; | |
index = (index + 1) % count; | |
} | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast5<T>(this IEnumerable<T> source, int count) | |
{ | |
if (source == null) | |
throw new ArgumentNullException("Source Enumeration may not be null", nameof(source)); | |
if (count <= 0) | |
{ | |
foreach (T item in source) | |
yield return item; | |
} | |
else | |
{ | |
Queue<T> buffer = new Queue<T>(); | |
foreach (T item in source) | |
{ | |
if (buffer.Count == count) | |
{ | |
yield return buffer.Dequeue(); | |
} | |
buffer.Enqueue(item); | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast6<T>(this IEnumerable<T> source, int count) | |
{ | |
if (source == null) | |
throw new ArgumentNullException("Source Enumeration may not be null", nameof(source)); | |
if (count <= 0) | |
{ | |
foreach (T item in source) | |
yield return item; | |
} | |
else | |
{ | |
Queue<T> buffer = new Queue<T>(); | |
using (var e = source.GetEnumerator()) | |
{ | |
// initial filling of buffer | |
for (int i = 0; i < count; i++) | |
{ | |
if (!e.MoveNext()) | |
yield break; | |
buffer.Enqueue(e.Current); | |
} | |
while (e.MoveNext()) | |
{ | |
yield return buffer.Dequeue(); | |
buffer.Enqueue(e.Current); | |
} | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast7<T>(this IEnumerable<T> data, int count) | |
{ | |
if (data == null) throw new ArgumentNullException(nameof(data)); | |
if (count <= 0) return data; | |
if (data is ICollection<T> collection) | |
return collection.Take(collection.Count - count); | |
return Skipper(); | |
IEnumerable<T> Skipper() | |
{ | |
using (var enumer = data.GetEnumerator()) | |
{ | |
T[] queue = new T[count]; | |
int index = 0; | |
while (index < count && enumer.MoveNext()) | |
queue[index++] = enumer.Current; | |
index = -1; | |
while (enumer.MoveNext()) | |
{ | |
index = (index + 1) % count; | |
yield return queue[index]; | |
queue[index] = enumer.Current; | |
} | |
} | |
} | |
} | |
static public IEnumerable<T> SkipLast8<T>(this IEnumerable<T> data, int count) | |
{ | |
if (data == null) throw new ArgumentNullException(nameof(data)); | |
if (count <= 0) return data; | |
if (data is ICollection<T> collection) | |
return collection.Take(collection.Count - count); | |
return Skipper(); | |
IEnumerable<T> Skipper() | |
{ | |
using (var enumer = data.GetEnumerator()) | |
{ | |
T[] queue = new T[count]; | |
int index = 0; | |
while (index < queue.Length && enumer.MoveNext()) | |
queue[index++] = enumer.Current; | |
index = -1; | |
while (enumer.MoveNext()) | |
{ | |
index = (index + 1) % count; | |
yield return queue[index]; | |
queue[index] = enumer.Current; | |
} | |
} | |
} | |
} | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
if (args[0].Equals("Test", System.StringComparison.InvariantCultureIgnoreCase)) | |
Test(); | |
if (args[0].Equals("Bench1", System.StringComparison.InvariantCultureIgnoreCase)) | |
BenchmarkRunner.Run<Bench1>(); | |
if (args[0].Equals("Bench2", System.StringComparison.InvariantCultureIgnoreCase)) | |
BenchmarkRunner.Run<Bench2>(); | |
if (args[0].Equals("Bench3", System.StringComparison.InvariantCultureIgnoreCase)) | |
BenchmarkRunner.Run<Bench3>(); | |
} | |
static void Test() | |
{ | |
// check everyone agrees with the BCL | |
// (they do, excepting in c=-1 case, where methods 1 and 2 do not agree: we won't benchmark those) | |
var nominal = new Func<IEnumerable<int>, int, IEnumerable<int>>((s, c) => s.SkipLast(c)); | |
var methods = new Func<IEnumerable<int>, int, IEnumerable<int>>[] | |
{ | |
(s, c) => s.SkipLast(c), // include nominal | |
(s, c) => s.SkipLast1(c), | |
(s, c) => s.SkipLast2(c), | |
(s, c) => s.SkipLast3(c), | |
(s, c) => s.SkipLast4(c), | |
(s, c) => s.SkipLast5(c), | |
(s, c) => s.SkipLast6(c), | |
(s, c) => s.SkipLast7(c), | |
(s, c) => s.SkipLast8(c), | |
}; | |
var arrs = new int[][] | |
{ | |
Enumerable.Range(0, 0).ToArray(), | |
Enumerable.Range(0, 1).ToArray(), | |
Enumerable.Range(0, 10).ToArray(), | |
}; | |
var counts = Enumerable.Range(-1, 21).ToArray(); | |
foreach (var s in arrs) | |
foreach (var c in counts) | |
{ | |
var t = nominal(s, c).ToArray(); | |
int mi = 0; | |
foreach (var m in methods) | |
{ | |
var r = m(s, c).ToArray(); | |
if (!ArrEqual(t, r)) | |
Console.WriteLine($"m{mi}, s{s.Length}, c{c}: {string.Join(",", r)}"); | |
mi++; | |
} | |
} | |
} | |
static bool ArrEqual<T>(T[] l, T[] r) | |
{ | |
if (l == r) | |
return true; // covers 2 nulls case | |
if (l == null || r == null) | |
return false; | |
if (l.Length != r.Length) | |
return false; | |
for (int i = 0; i < l.Length; i++) | |
if (!l[i].Equals(r[i])) | |
return false; | |
return true; | |
} | |
} | |
public class Bench1 | |
{ | |
public IEnumerable<int> Lengths => new int[] { 0, 100, 100000 }; | |
public IEnumerable<int> Counts => new int[] { 0, 10, 1000 }; | |
[ParamsSource(nameof(Lengths))] | |
public int Length { get; set; } | |
[ParamsSource(nameof(Counts))] | |
public int Count { get; set; } | |
public IEnumerable<int> Array { get; set; } | |
public IEnumerable<int> Range { get; set; } | |
public IEnumerable<int> Thing { get; set; } | |
[GlobalSetup] | |
public void GlobalSetup() | |
{ | |
Array = new int[Length]; | |
Range = Enumerable.Range(0, Length); | |
Thing = MysteryRange(Length); | |
} | |
static IEnumerable<int> MysteryRange(int count) | |
{ | |
for (int i = 0; i < count; i++) | |
yield return i; | |
} | |
[GlobalCleanup] | |
public void GlobalCleanup() | |
{ | |
// nix | |
} | |
[Benchmark] | |
public void SkipLastNetCoreArrays() { foreach (var a in Array.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Arrays() { foreach (var a in Array.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Arrays() { foreach (var a in Array.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Arrays() { foreach (var a in Array.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Arrays() { foreach (var a in Array.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreRanges() { foreach (var a in Range.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Ranges() { foreach (var a in Range.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Ranges() { foreach (var a in Range.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Ranges() { foreach (var a in Range.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Ranges() { foreach (var a in Range.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreThings() { foreach (var a in Thing.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Things() { foreach (var a in Thing.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Things() { foreach (var a in Thing.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Things() { foreach (var a in Thing.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Things() { foreach (var a in Thing.SkipLast4(Count)); } | |
} | |
public class Bench2 | |
{ | |
public IEnumerable<int> Lengths => new int[] { 0, 1, 10, 100, 1000, 10000, 100000 }; | |
[ParamsSource(nameof(Lengths))] | |
public int Length { get; set; } | |
public int Count { get; set; } | |
public IEnumerable<int> Array { get; set; } | |
public IEnumerable<int> Range { get; set; } | |
public IEnumerable<int> Thing { get; set; } | |
[GlobalSetup] | |
public void GlobalSetup() | |
{ | |
Count = Length / 5; | |
Array = new int[Length]; | |
Range = Enumerable.Range(0, Length); | |
Thing = MysteryRange(Length); | |
} | |
static IEnumerable<int> MysteryRange(int count) | |
{ | |
for (int i = 0; i < count; i++) | |
yield return i; | |
} | |
[GlobalCleanup] | |
public void GlobalCleanup() | |
{ | |
// nix | |
} | |
[Benchmark] | |
public void SkipLastNetCoreArrays() { foreach (var a in Array.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Arrays() { foreach (var a in Array.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Arrays() { foreach (var a in Array.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Arrays() { foreach (var a in Array.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Arrays() { foreach (var a in Array.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast5Arrays() { foreach (var a in Array.SkipLast5(Count)); } | |
[Benchmark] | |
public void SkipLast6Arrays() { foreach (var a in Array.SkipLast6(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreRanges() { foreach (var a in Range.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Ranges() { foreach (var a in Range.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Ranges() { foreach (var a in Range.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Ranges() { foreach (var a in Range.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Ranges() { foreach (var a in Range.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast5Ranges() { foreach (var a in Range.SkipLast5(Count)); } | |
[Benchmark] | |
public void SkipLast6Ranges() { foreach (var a in Range.SkipLast6(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreThings() { foreach (var a in Thing.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast1Things() { foreach (var a in Thing.SkipLast1(Count)); } | |
[Benchmark] | |
public void SkipLast2Things() { foreach (var a in Thing.SkipLast2(Count)); } | |
[Benchmark] | |
public void SkipLast3Things() { foreach (var a in Thing.SkipLast3(Count)); } | |
[Benchmark] | |
public void SkipLast4Things() { foreach (var a in Thing.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast5Things() { foreach (var a in Thing.SkipLast5(Count)); } | |
[Benchmark] | |
public void SkipLast6Things() { foreach (var a in Thing.SkipLast6(Count)); } | |
} | |
public class Bench3 | |
{ | |
public IEnumerable<int> Lengths => new int[] { 0, 100, 100000, 100000000 }; | |
public IEnumerable<int> Counts => new int[] { 0, 10, 1000 }; | |
[ParamsSource(nameof(Lengths))] | |
public int Length { get; set; } | |
[ParamsSource(nameof(Counts))] | |
public int Count { get; set; } | |
public IEnumerable<int> Array { get; set; } | |
public IEnumerable<int> Range { get; set; } | |
public IEnumerable<int> Thing { get; set; } | |
[GlobalSetup] | |
public void GlobalSetup() | |
{ | |
Array = new int[Length]; | |
Range = Enumerable.Range(0, Length); | |
Thing = MysteryRange(Length); | |
} | |
static IEnumerable<int> MysteryRange(int count) | |
{ | |
for (int i = 0; i < count; i++) | |
yield return i; | |
} | |
[GlobalCleanup] | |
public void GlobalCleanup() | |
{ | |
// nix | |
} | |
[Benchmark] | |
public void SkipLastNetCoreArrays() { foreach (var a in Array.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast4Arrays() { foreach (var a in Array.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast7Arrays() { foreach (var a in Array.SkipLast7(Count)); } | |
[Benchmark] | |
public void SkipLast8Arrays() { foreach (var a in Array.SkipLast8(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreRanges() { foreach (var a in Range.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast4Ranges() { foreach (var a in Range.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast7Ranges() { foreach (var a in Range.SkipLast7(Count)); } | |
[Benchmark] | |
public void SkipLast8Ranges() { foreach (var a in Range.SkipLast8(Count)); } | |
[Benchmark] | |
public void SkipLastNetCoreThings() { foreach (var a in Thing.SkipLast(Count)); } | |
[Benchmark] | |
public void SkipLast4Things() { foreach (var a in Thing.SkipLast4(Count)); } | |
[Benchmark] | |
public void SkipLast7Things() { foreach (var a in Thing.SkipLast7(Count)); } | |
[Benchmark] | |
public void SkipLast8Things() { foreach (var a in Thing.SkipLast8(Count)); } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment