Минутка WTF-5: реально WTF
От: Sinix  
Дата: 23.04.16 15:49
Оценка: 34 (3)
Обещал позабористей, чем это
Автор: Sinix
Дата: 20.04.16
?

Ну вот держите:
        public struct HeavyStruct
        {
            private readonly decimal _a;
            private readonly decimal _b;
            private readonly decimal _c;
            private readonly decimal _d;
            private readonly decimal _e;
            private readonly decimal _f;
            private readonly decimal _g;
            private readonly decimal _h;

            public decimal Test() => _a;
        }

        public struct HeavyStructWrapper
        {
            private readonly HeavyStruct _h;

            public decimal CallTest() => _h.Test();
        }


Чего не так-то?
Чтоб не посылать в ложном направлении: нет, проблема не в отсутствующих Equals/GetHashCode, не влияют.

Поскольку вопрос действительно заковыристый,
  подсказка:
каким образом нужно поменять HeavyStructWrapper2 (код ниже) так, чтобы получить вот такой вот вывод:
                 wrapper1:  1013ms, ips:          98 634 922,27 | Mem:   8,00 kb, GC 0/1/2: 0/0/0 => 100000000
                 wrapper2:   146ms, ips:         683 613 443,94 | Mem:   3,91 kb, GC 0/1/2: 0/0/0 => 100000000

Done.


менять только код в HeavyStructWrapper2, метод CallTest не трогать

Сам код бенчмарка (запускать без отладчика, Ctrl-F5):
using System;
using System.Diagnostics;

namespace Samples
{
    class Program
    {
#pragma warning disable 169
#pragma warning disable 649
        public struct HeavyStruct
        {
            private readonly decimal _a;
            private readonly decimal _b;
            private readonly decimal _c;
            private readonly decimal _d;
            private readonly decimal _e;
            private readonly decimal _f;
            private readonly decimal _g;
            private readonly decimal _h;

            public decimal Test() => _a;
        }

        public struct HeavyStructWrapper
        {
            private readonly HeavyStruct _h;

            public decimal CallTest() => _h.Test();
        }

        public struct HeavyStructWrapper2
        {
            private readonly HeavyStruct _h;

            public decimal CallTest() => _h.Test();
        }
#pragma warning restore 649
#pragma warning restore 169

        public static void Main(string[] args)
        {
            Console.WindowWidth = 120;

            const int Count = 100 * 1000 * 1000;
            var wrapper1 = new HeavyStructWrapper();
            var wrapper2 = new HeavyStructWrapper2();

            Measure("wrapper1", () =>
            {
                for (int i = 0; i < Count; i++)
                {
                    wrapper1.CallTest();
                }
                return Count;
            });

            Measure("wrapper2", () =>
            {
                for (int i = 0; i < Count; i++)
                {
                    wrapper2.CallTest();
                }
                return Count;
            });

            Console.WriteLine("\r\nDone.");
            Console.ReadKey();
        }

        static void Measure(string name, Func<long> callback)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            var mem = GC.GetTotalMemory(true);
            var gc00 = GC.CollectionCount(0);
            var gc01 = GC.CollectionCount(1);
            var gc02 = GC.CollectionCount(2);

            var sw = Stopwatch.StartNew();
            var result = callback();
            sw.Stop();

            var mem2 = GC.GetTotalMemory(false);
            var gc10 = GC.CollectionCount(0);
            var gc11 = GC.CollectionCount(1);
            var gc12 = GC.CollectionCount(2);

            var memDelta = (mem2 - mem) / 1024.0;
            var gcDelta0 = gc10 - gc00;
            var gcDelta1 = gc11 - gc01;
            var gcDelta2 = gc12 - gc02;

            Console.WriteLine(
                "{0,25}: {1,5}ms, ips: {2,22:N} | Mem: {3,6:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
                name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
        }
    }
}
Отредактировано 07.01.2017 16:47 Sinix . Предыдущая версия .
минутка wtf
Re: Минутка WTF-5: реально WTF
От: hi_octane Беларусь  
Дата: 23.04.16 16:36
Оценка: 85 (3) +1
Спрятал чтобы не сломать случайно интригу тем кто читает в плоском режиме.

  Скрытый текст
У HeavyStructWrapper2 надо убрать у структуры readonly. Причина тормозов в том что компилятор C# не знает какие методы у структруры мутабельные а какие нет (имхо мог бы вычислять и аттрибутом помечать, либо могли добавить модификатор const/readonly как сделано у C++.
Без этого знания считается что все методы мутабельные, и CLR приходится для сохранения структуры в исходном состоянии вызывать .Test() только у копии. Т.е. перед каждым вызовом .Test() делается копия структуры, и это копирование загоняет всё в тормоза.

P.S. Почему-то у меня ускорение не в 10 а почти в 50 раз. Имхо такой разницы быть не должно, разбираюсь
Nemerle — power of metaprogramming, functional, object-oriented and imperative features in a statically-typed .NET language
Re: Минутка WTF-5: реально WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 23.04.16 17:34
Оценка: 33 (1) +2
Здравствуйте, Sinix, Вы писали:

S>Обещал позабористей, чем это
Автор: Sinix
Дата: 20.04.16
?


  Скрытый текст
Скорее всего нужно убрать readonly в HeavyStructWrapper? И ситуация, я думаю, один в один с Micro-optimization: the surprising inefficiency of readonly fields
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[2]: Минутка WTF-5: реально WTF
От: Sinix  
Дата: 23.04.16 17:43
Оценка: 5 (1)
Здравствуйте, hi_octane, Вы писали:

_>Спрятал чтобы не сломать случайно интригу тем кто читает в плоском режиме.


Спрятал в ответ)
  Скрытый текст
Позор-позор, в управдомы пора переквалифицироваться))
Лаадно, поищем что-нибудь ещё.

По теме:
* объяснение на пальцах:
http://blog.nodatime.org/2014/07/micro-optimization-surprising.html

* намёки на "pure надо бы оптимизировать"
https://github.com/dotnet/roslyn/issues/7626
https://github.com/dotnet/roslyn/issues/7561
https://github.com/dotnet/roslyn/issues/115

Разумеется, разница в перфомансе не значит, что readonly надо немедленно выпиливать, хотя в паре мест я так код у себя ускорял.

Разница в 10 раз у меня на ноуте вылезла. На "взрослых" машинах и больше может быть.
Re[2]: Минутка WTF-5: реально WTF
От: Sinix  
Дата: 23.04.16 17:44
Оценка:
Здравствуйте, rameel, Вы писали:

R> И ситуация, я думаю, один в один с ...

Угу, я на этот пост тоже сослался для объяснений))
Re: Минутка WTF-5: реально WTF
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 25.04.16 04:09
Оценка: 66 (1)
Здравствуйте, Sinix, Вы писали:

S>
S>        public struct HeavyStructWrapper
S>        {
S>            private readonly HeavyStruct _h;

S>            public decimal CallTest() => _h.Test();
S>        }
S>


S>Чего не так-то?


Да, отличный пример! Я именно по этой причине в свой чудо анализатор ErrorProne.NET добавил правило UseReadOnlyAttributeAnalyzer, которое предлагает заменить ключевое слово readonly кастомным атрибутом:

public struct HeavyStructWrapper
{
  [ReadOnly]
  private HeavyStruct _h;
}



Теперь анализатор не позволит изменить _h за пределами конструктора, но при этом доступ к полю не будет приводить к копии.

Я вот подумал, что если бы анализатор детектил проблему по размеру используемой структуры (еще бы знать этот порог?) и вообще было бы хорошо!
Re[2]: Минутка WTF-5: реально WTF
От: Sinix  
Дата: 25.04.16 12:04
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Теперь анализатор не позволит изменить _h за пределами конструктора, но при этом доступ к полю не будет приводить к копии.


Угу. Как пожелание: в UseReadOnlyAttributeAnalyzer добавить учёт размера структуры. Из эмпирики: для < 8 байт можно вообще не показывать, для > 24 — варнинг надо.
Если нужны пруфы, то лучше меня не слушать, а взять бенчмарк (в ветке выше простенький был), запустить и посмотреть, начиная с какого размера падение совсем неприличное получается.

Кстати, а нет желания упросить товарищей из JetBrains включить аналогичные аннотации?

Они в профильном форуме отмечаются регулярно, если заведёшь тему — могу поддержать


UPD. Бгг. Вот это

ST>Я вот подумал, что если бы анализатор детектил проблему по размеру используемой структуры (еще бы знать этот порог?) и вообще было бы хорошо!

не заметил, сорри. В одном направлении думаем.
Re[2]: Минутка WTF-5: реально WTF
От: Sinix  
Дата: 25.04.16 12:07
Оценка:
Здравствуйте, SergeyT., Вы писали:

Upd-2. И в этот список надо добавиться
Re[3]: Минутка WTF-5: реально WTF
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 25.04.16 15:04
Оценка: 36 (1) +1
Здравствуйте, Sinix, Вы писали:


ST>>Теперь анализатор не позволит изменить _h за пределами конструктора, но при этом доступ к полю не будет приводить к копии.


S>Угу. Как пожелание: в UseReadOnlyAttributeAnalyzer добавить учёт размера структуры. Из эмпирики: для < 8 байт можно вообще не показывать, для > 24 — варнинг надо.

S>Если нужны пруфы, то лучше меня не слушать, а взять бенчмарк (в ветке выше простенький был), запустить и посмотреть, начиная с какого размера падение совсем неприличное получается.

Да, у меня были похожие мысли. Только, кажется, нужно еще привязываться к архитектуре, поскольку пороги для x86 и x64 — разные.

S>Кстати, а нет желания упросить товарищей из JetBrains включить аналогичные аннотации?


Я чутка с этим делом еще поиграюсь, и можно будет их попросить это сделать!

S>Они в профильном форуме отмечаются регулярно, если заведёшь тему — могу поддержать


Спасибо!
Re[4]: Минутка WTF-5: реально WTF
От: romangr Россия  
Дата: 25.04.16 18:04
Оценка: 47 (2) +2
Здравствуйте, SergeyT., Вы писали:

ST>Здравствуйте, Sinix, Вы писали:



ST>>>Теперь анализатор не позволит изменить _h за пределами конструктора, но при этом доступ к полю не будет приводить к копии.


S>>Угу. Как пожелание: в UseReadOnlyAttributeAnalyzer добавить учёт размера структуры. Из эмпирики: для < 8 байт можно вообще не показывать, для > 24 — варнинг надо.

S>>Если нужны пруфы, то лучше меня не слушать, а взять бенчмарк (в ветке выше простенький был), запустить и посмотреть, начиная с какого размера падение совсем неприличное получается.

ST>Да, у меня были похожие мысли. Только, кажется, нужно еще привязываться к архитектуре, поскольку пороги для x86 и x64 — разные.


Мне кажется, тут надо исходить из того, влезает ли структура в cache line size, которая обычно 64 байта. Хотя и меньшая по размеру структура может 2 линии занимать.
А влияние x86 и x64 (кроме размера указателей) будет еще и в том, что JIT разный код генерит для копирования. Например, может rep movsd использовать, а может и xmm регистры.
Например, x86:
  Скрытый текст
            var hsw = new HeavyStructWrapper();
00AD34AA  in          al,dx  
00AD34AB  push        edi  
00AD34AC  push        esi  
00AD34AD  push        ebx  
00AD34AE  sub         esp,100h  
00AD34B4  mov         esi,ecx  
00AD34B6  lea         edi,[ebp-10Ch]  
00AD34BC  mov         ecx,40h  
00AD34C1  xor         eax,eax  
00AD34C3  rep stos    dword ptr es:[edi]  
00AD34C5  mov         ecx,esi  
00AD34C7  lea         edi,[ebp-8Ch]  
00AD34CD  xor         eax,eax  
00AD34CF  lea         ecx,[eax+20h]  
00AD34D2  rep stos    dword ptr es:[edi]  
            Console.WriteLine(hsw.CallTest());
00AD34D4  lea         edi,[ebp-10Ch]  
00AD34DA  lea         ecx,[eax+20h]  
00AD34DD  rep stos    dword ptr es:[edi]  
00AD34DF  lea         edi,[ebp-10Ch]  
00AD34E5  lea         esi,[ebp-8Ch]  
00AD34EB  lea         ecx,[eax+20h]  
00AD34EE  rep movs    dword ptr es:[edi],dword ptr [esi]  
00AD34F0  lea         edx,[ebp-10Ch]  
00AD34F6  mov         ebx,dword ptr [edx]  
00AD34F8  mov         edi,dword ptr [edx+4]  
00AD34FB  mov         esi,dword ptr [edx+8]  
00AD34FE  mov         eax,dword ptr [edx+0Ch]  
00AD3501  push        eax  
00AD3502  push        esi  
00AD3503  push        edi  
00AD3504  push        ebx  
00AD3505  call        71F10A74  
00AD350A  lea         esp,[ebp-0Ch]  
00AD350D  pop         ebx  
00AD350E  pop         esi  
00AD350F  pop         edi  
00AD3510  pop         ebp  
00AD3511  ret

x64:
  Скрытый текст
            var hsw = new HeavyStructWrapper();
00007FFD900B48F2  sub         rsp,148h  
00007FFD900B48F9  mov         rsi,rcx  
00007FFD900B48FC  lea         rdi,[rsp+38h]  
00007FFD900B4901  mov         ecx,44h  
00007FFD900B4906  xor         eax,eax  
00007FFD900B4908  rep stos    dword ptr [rdi]  
00007FFD900B490A  mov         rcx,rsi  
00007FFD900B490D  xor         edx,edx  
00007FFD900B490F  lea         rcx,[rsp+0C8h]  
00007FFD900B4917  xorpd       xmm0,xmm0  
00007FFD900B491B  movdqu      xmmword ptr [rcx],xmm0  
00007FFD900B491F  movdqu      xmmword ptr [rcx+10h],xmm0  
00007FFD900B4924  movdqu      xmmword ptr [rcx+20h],xmm0  
00007FFD900B4929  movdqu      xmmword ptr [rcx+30h],xmm0  
00007FFD900B492E  movdqu      xmmword ptr [rcx+40h],xmm0  
00007FFD900B4933  movdqu      xmmword ptr [rcx+50h],xmm0  
00007FFD900B4938  movdqu      xmmword ptr [rcx+60h],xmm0  
00007FFD900B493D  movdqu      xmmword ptr [rcx+70h],xmm0  
            Console.WriteLine(hsw.CallTest());
00007FFD900B4942  lea         rdx,[rsp+0C8h]  
00007FFD900B494A  lea         rcx,[rsp+38h]  
00007FFD900B494F  mov         r8d,80h  
00007FFD900B4955  call        00007FFDEF7019C0  
00007FFD900B495A  movdqu      xmm0,xmmword ptr [rsp+38h]  
00007FFD900B4960  movdqu      xmmword ptr [rsp+0B8h],xmm0  
00007FFD900B4969  movdqu      xmm0,xmmword ptr [rsp+0B8h]  
00007FFD900B4972  movdqu      xmmword ptr [rsp+28h],xmm0  
00007FFD900B4978  lea         rcx,[rsp+28h]  
00007FFD900B497D  call        00007FFDEEE0F620  
00007FFD900B4982  nop  
00007FFD900B4983  add         rsp,148h  
00007FFD900B498A  pop         rsi  
00007FFD900B498B  pop         rdi  
00007FFD900B498C  ret
... << RSDN@Home (RF) 1.2.0 alpha 5 rev. 67>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.