Skip to content

Instantly share code, notes, and snippets.

@VisualMelon
Created July 7, 2019 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VisualMelon/dfc9b49807ba5e467964fd1dd0af8274 to your computer and use it in GitHub Desktop.
Save VisualMelon/dfc9b49807ba5e467964fd1dd0af8274 to your computer and use it in GitHub Desktop.
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