Итераторы
От: StatujaLeha на правах ИМХО
Дата: 16.06.20 06:49
Оценка: 63 (1)
Приветствую!

Хотел использовать в проекте расширение 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 |
Отредактировано 16.06.2020 9:48 StatujaLeha . Предыдущая версия . Еще …
Отредактировано 16.06.2020 7:14 StatujaLeha . Предыдущая версия .
Re: Итераторы
От: Sinix  
Дата: 16.06.20 07:49
Оценка: +2
Здравствуйте, 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 чего делать?
Переписывать всё — не вариант, код итераторов достаточно тяжёлый бывает. Переписать выборочно можно, но кто потом этот код будет поддерживать?

Короче, я в раздумьях и не готов топить ни за, ни против.
Re[2]: Итераторы
От: StatujaLeha на правах ИМХО
Дата: 16.06.20 19:13
Оценка: +1
Здравствуйте, 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>Короче, я в раздумьях и не готов топить ни за, ни против.
Отредактировано 17.06.2020 6:27 StatujaLeha . Предыдущая версия . Еще …
Отредактировано 17.06.2020 6:27 StatujaLeha . Предыдущая версия .
Re[2]: Итераторы
От: _NN_  
Дата: 30.08.20 16:38
Оценка:
Здравствуйте, 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 и если будет быстрее то однозначно я за .
http://rsdn.nemerleweb.com
http://nemerleweb.com
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.