Приветствую!
Хотел использовать в проекте расширение EnumerableExtensions.Concat<T>(this IEnumerable<T> source, T element).
Посмотрел реализацию: появилось подозрение, что они делают обход коллекции неэффективно.
Набросал свою реализацию.
Код и результаты ниже.
[SimpleJob(RuntimeMoniker.Net472)]
[SimpleJob(RuntimeMoniker.NetCoreApp22)]
public class ConcatBenchmark
{
private const int N = 3000000;
public int[] startInt = Enumerable.Repeat(1, N).ToArray();
public long[] startLong = Enumerable.Repeat(1l, N).ToArray();
public string[] startStr = Enumerable.Repeat(1, N).Select(i => i.ToString()).ToArray();
[Benchmark]
public int ReferenceCodeJam() => this.startStr.Concat("").Count();
[Benchmark]
public int ReferenceMy() => Concat(this.startStr, "").Count();
[Benchmark]
public int Referencex5CodeJam() => this.startStr.Concat("").Concat("").Concat("").Concat("").Concat("").Count();
[Benchmark]
public int Referencex5My() => Concat(Concat(Concat(Concat(Concat(this.startStr, ""), ""), ""), ""), "").Count();
[Benchmark]
public int intCodeJam() => this.startInt.Concat(N).Sum();
[Benchmark]
public int intMy() => Concat(this.startInt, N).Sum();
[Benchmark]
public int intx5CodeJam() => this.startInt.Concat(N).Concat(N).Concat(N).Concat(N).Concat(N).Sum();
[Benchmark]
public int intx5My() => Concat(Concat(Concat(Concat(Concat(this.startInt, N), N), N), N), N).Sum();
[Benchmark]
public long longCodeJam() => this.startLong.Concat(N).Sum();
[Benchmark]
public long longMy() => Concat(this.startLong, N).Sum();
[Benchmark]
public long longx5CodeJam() => this.startLong.Concat(N).Concat(N).Concat(N).Concat(N).Concat(N).Sum();
[Benchmark]
public long longx5My() => Concat(Concat(Concat(Concat(Concat(this.startLong, N), N), N), N), N).Sum();
public class ConcatEnumerator<T> : IEnumerator<T>
{
private State state;
private IEnumerator<T> start;
private readonly T last;
public ConcatEnumerator(IEnumerator<T> start, T last)
{
this.state = State.ConsumingFirst;
this.start = start;
this.last = last;
}
public void Dispose()
{
}
public Boolean MoveNext()
{
switch (this.state)
{
case State.ConsumingFirst:
if (this.start.MoveNext())
{
return true;
}
else
{
this.state = State.StartConsumed;
return true;
}
case State.StartConsumed:
this.state = State.Empty;
return false;
}
return false;
}
public void Reset()
{
this.start.Reset();
this.state = State.ConsumingFirst;
}
public T Current
{
get
{
switch (this.state)
{
case State.ConsumingFirst:
return this.start.Current;
case State.StartConsumed:
return this.last;
default:
throw new InvalidOperationException();
}
}
}
Object IEnumerator.Current => this.Current;
enum State
{
ConsumingFirst,
StartConsumed,
Empty
}
}
public class ConcatEnumerable<T> : IEnumerable<T>
{
private IEnumerable<T> start;
private readonly T last;
public ConcatEnumerable(IEnumerable<T> start, T last)
{
this.start = start;
this.last = last;
}
public IEnumerator<T> GetEnumerator()
{
return new ConcatEnumerator<T>(this.start.GetEnumerator(), this.last);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public static IEnumerable<T> Concat<T>(IEnumerable<T> first, T last)
{
return new ConcatEnumerable<T>(first, last);
}
}
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.836 (1909/November2018Update/19H2)
Intel Core i7-10710U CPU 1.10GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=2.2.106
[Host] : .NET Core 2.2.4 (CoreCLR 4.6.27521.02, CoreFX 4.6.27521.01), X64 RyuJIT
.NET 4.7.2 : .NET Framework 4.8 (4.8.4180.0), X64 RyuJIT
.NET Core 2.2 : .NET Core 2.2.4 (CoreCLR 4.6.27521.02, CoreFX 4.6.27521.01), X64 RyuJIT
| Method | Job | Runtime | Mean | Error | StdDev | Median |
|------------------- |-------------- |-------------- |----------:|---------:|---------:|----------:|
| ReferenceCodeJam | .NET 4.7.2 | .NET 4.7.2 | 34.36 ms | 1.132 ms | 3.247 ms | 33.16 ms |
| ReferenceMy | .NET 4.7.2 | .NET 4.7.2 | 15.28 ms | 0.359 ms | 1.037 ms | 14.76 ms |
| Referencex5CodeJam | .NET 4.7.2 | .NET 4.7.2 | 157.93 ms | 0.961 ms | 0.852 ms | 157.82 ms |
| Referencex5My | .NET 4.7.2 | .NET 4.7.2 | 53.54 ms | 1.066 ms | 1.923 ms | 52.89 ms |
| intCodeJam | .NET 4.7.2 | .NET 4.7.2 | 33.20 ms | 0.633 ms | 0.561 ms | 33.07 ms |
| intMy | .NET 4.7.2 | .NET 4.7.2 | 26.45 ms | 0.251 ms | 0.209 ms | 26.42 ms |
| intx5CodeJam | .NET 4.7.2 | .NET 4.7.2 | 119.49 ms | 2.170 ms | 1.695 ms | 119.04 ms |
| intx5My | .NET 4.7.2 | .NET 4.7.2 | 87.00 ms | 1.227 ms | 1.148 ms | 86.95 ms |
| longCodeJam | .NET 4.7.2 | .NET 4.7.2 | 32.32 ms | 0.283 ms | 0.251 ms | 32.30 ms |
| longMy | .NET 4.7.2 | .NET 4.7.2 | 28.16 ms | 0.202 ms | 0.189 ms | 28.10 ms |
| longx5CodeJam | .NET 4.7.2 | .NET 4.7.2 | 118.99 ms | 1.045 ms | 0.978 ms | 118.84 ms |
| longx5My | .NET 4.7.2 | .NET 4.7.2 | 89.98 ms | 1.082 ms | 0.904 ms | 90.05 ms |
| ReferenceCodeJam | .NET Core 2.2 | .NET Core 2.2 | 32.73 ms | 0.438 ms | 0.388 ms | 32.81 ms |
| ReferenceMy | .NET Core 2.2 | .NET Core 2.2 | 15.08 ms | 0.098 ms | 0.092 ms | 15.05 ms |
| Referencex5CodeJam | .NET Core 2.2 | .NET Core 2.2 | 166.51 ms | 3.323 ms | 3.955 ms | 165.46 ms |
| Referencex5My | .NET Core 2.2 | .NET Core 2.2 | 51.68 ms | 0.361 ms | 0.320 ms | 51.69 ms |
| intCodeJam | .NET Core 2.2 | .NET Core 2.2 | 35.83 ms | 0.311 ms | 0.275 ms | 35.74 ms |
| intMy | .NET Core 2.2 | .NET Core 2.2 | 31.74 ms | 0.625 ms | 0.522 ms | 31.60 ms |
| intx5CodeJam | .NET Core 2.2 | .NET Core 2.2 | 123.43 ms | 0.978 ms | 0.867 ms | 123.31 ms |
| intx5My | .NET Core 2.2 | .NET Core 2.2 | 98.99 ms | 0.880 ms | 0.780 ms | 98.77 ms |
| longCodeJam | .NET Core 2.2 | .NET Core 2.2 | 34.29 ms | 0.303 ms | 0.283 ms | 34.36 ms |
| longMy | .NET Core 2.2 | .NET Core 2.2 | 30.74 ms | 0.154 ms | 0.128 ms | 30.77 ms |
| longx5CodeJam | .NET Core 2.2 | .NET Core 2.2 | 124.48 ms | 2.454 ms | 2.826 ms | 123.33 ms |
| longx5My | .NET Core 2.2 | .NET Core 2.2 | 101.79 ms | 0.816 ms | 0.763 ms | 101.74 ms |
Здравствуйте, StatujaLeha, Вы писали:
SL>Приветствую!
Лайк и за реализацию, и за код, хорошо сделано
Насчёт втащить в CJ я в раздумьях

С одной стороны имеем 2x выигрыш в бенчмарке, с другой — кучу кода вместо
[Pure, NotNull, LinqTunnel]
private static IEnumerable<T> ConcatCore<T>([NotNull] IEnumerable<T> source, T element)
{
foreach (var item in source)
yield return item;
yield return element;
}
с третьей — в core 3.1 разница уже не так впечатляет
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.300
[Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
.NET Core 3.1 : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Job=.NET Core 3.1 Runtime=.NET Core 3.1
| Method | Mean | Error | StdDev | Median |
|------------------- |----------:|----------:|----------:|----------:|
| ReferenceCodeJam | 25.34 ms | 0.141 ms | 0.110 ms | 25.36 ms |
| ReferenceMy | 12.04 ms | 0.087 ms | 0.082 ms | 12.01 ms |
| Referencex5CodeJam | 131.43 ms | 2.199 ms | 3.154 ms | 131.42 ms |
| Referencex5My | 42.65 ms | 0.694 ms | 0.580 ms | 42.75 ms |
| intCodeJam | 23.51 ms | 0.217 ms | 0.193 ms | 23.50 ms |
| intMy | 19.04 ms | 0.115 ms | 0.096 ms | 19.06 ms |
| intx5CodeJam | 105.39 ms | 6.257 ms | 16.373 ms | 100.47 ms |
| intx5My | 71.85 ms | 1.427 ms | 1.805 ms | 71.79 ms |
| longCodeJam | 23.76 ms | 0.231 ms | 0.216 ms | 23.68 ms |
| longMy | 20.00 ms | 0.184 ms | 0.172 ms | 19.97 ms |
| longx5CodeJam | 135.20 ms | 15.707 ms | 46.314 ms | 110.50 ms |
| longx5My | 80.00 ms | 4.103 ms | 10.882 ms | 76.13 ms |
Ну, т.е. конкретно этот метод мы втащим, не проблема. А с остальными хэлперами для IEnumerable чего делать?

Переписывать всё — не вариант, код итераторов достаточно тяжёлый бывает. Переписать выборочно можно, но кто потом этот код будет поддерживать?
Короче, я в раздумьях и не готов топить ни за, ни против.
Здравствуйте, Sinix, Вы писали:
S>с третьей — в core 3.1 разница уже не так впечатляет
Видимо, это железом объясняется. У меня схожие результаты для Core 3.1.
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.836 (1909/November2018Update/19H2)
Intel Core i7-10710U CPU 1.10GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.201
[Host] : .NET Core 2.2.4 (CoreCLR 4.6.27521.02, CoreFX 4.6.27521.01), X64 RyuJIT
.NET Core 3.1 : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
Job=.NET Core 3.1 Runtime=.NET Core 3.1
| Method | Mean | Error | StdDev | Median |
|------------------- |----------:|---------:|---------:|----------:|
| ReferenceCodeJam | 29.26 ms | 0.306 ms | 0.287 ms | 29.36 ms |
| ReferenceMy | 14.01 ms | 0.214 ms | 0.200 ms | 13.95 ms |
| Referencex5CodeJam | 156.17 ms | 2.488 ms | 2.205 ms | 156.52 ms |
| Referencex5My | 49.88 ms | 0.419 ms | 0.392 ms | 49.97 ms |
| intCodeJam | 29.41 ms | 0.579 ms | 0.594 ms | 29.37 ms |
| intMy | 24.99 ms | 0.499 ms | 0.925 ms | 24.82 ms |
| intx5CodeJam | 118.24 ms | 1.596 ms | 1.414 ms | 118.34 ms |
| intx5My | 86.73 ms | 1.352 ms | 3.240 ms | 85.68 ms |
| longCodeJam | 28.85 ms | 0.236 ms | 0.209 ms | 28.82 ms |
| longMy | 23.51 ms | 0.225 ms | 0.200 ms | 23.51 ms |
| longx5CodeJam | 116.59 ms | 1.507 ms | 1.410 ms | 116.57 ms |
| longx5My | 81.93 ms | 1.235 ms | 1.095 ms | 81.99 ms |
S>Ну, т.е. конкретно этот метод мы втащим, не проблема. А с остальными хэлперами для IEnumerable чего делать?
S>Переписывать всё — не вариант, код итераторов достаточно тяжёлый бывает. Переписать выборочно можно, но кто потом этот код будет поддерживать?
Если бы нашелся человек, который использует CodeJam в продакшене(у меня пока только в коде для тестов) и проверил, есть ли у него заметный выигрыш от применения новых итераторов, то это был бы понятный ориентир.
Может это вообще никому не надо, а если у кого-то будет наблюдаться заметный прирост производительности, то плюс за переписывание
S>Короче, я в раздумьях и не готов топить ни за, ни против.
Здравствуйте, Sinix, Вы писали:
S>с третьей — в core 3.1 разница уже не так впечатляет
S>S>BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
S>Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
S>.NET Core SDK=3.1.300
S> [Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
S> .NET Core 3.1 : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
S>Job=.NET Core 3.1 Runtime=.NET Core 3.1
S>| Method | Mean | Error | StdDev | Median |
S>|------------------- |----------:|----------:|----------:|----------:|
S>| ReferenceCodeJam | 25.34 ms | 0.141 ms | 0.110 ms | 25.36 ms |
S>| ReferenceMy | 12.04 ms | 0.087 ms | 0.082 ms | 12.01 ms |
S>| Referencex5CodeJam | 131.43 ms | 2.199 ms | 3.154 ms | 131.42 ms |
S>| Referencex5My | 42.65 ms | 0.694 ms | 0.580 ms | 42.75 ms |
S>| intCodeJam | 23.51 ms | 0.217 ms | 0.193 ms | 23.50 ms |
S>| intMy | 19.04 ms | 0.115 ms | 0.096 ms | 19.06 ms |
S>| intx5CodeJam | 105.39 ms | 6.257 ms | 16.373 ms | 100.47 ms |
S>| intx5My | 71.85 ms | 1.427 ms | 1.805 ms | 71.79 ms |
S>| longCodeJam | 23.76 ms | 0.231 ms | 0.216 ms | 23.68 ms |
S>| longMy | 20.00 ms | 0.184 ms | 0.172 ms | 19.97 ms |
S>| longx5CodeJam | 135.20 ms | 15.707 ms | 46.314 ms | 110.50 ms |
S>| longx5My | 80.00 ms | 4.103 ms | 10.882 ms | 76.13 ms |
S>
Надо сравнить с .NET 5 и если будет быстрее то однозначно я за .