[Этюд] Минутка WTF
От: Sinix  
Дата: 27.03.16 11:12
Оценка: 14 (4)
Ну что, продолжаем?

Итак, дано: .Net 4+ / mono / .Net Core и
  вот такой вот код:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ConsoleApplication1
{
    class Program
    {
        private const bool FalseConst = false;
        private static readonly bool falseRO = false;
        private static bool falseSettable = false;

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string CaseA()
        {
            //Debugger.Break();
            return "A: " + (FalseConst ? "Set" : "Not set");
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string CaseB()
        {
            //Debugger.Break();
            return "B: " + (falseRO ? "Set" : "Not set");
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string CaseC()
        {
            //Debugger.Break();
            return "C: " + (falseSettable ? "Set" : "Not set");
        }

        static void Main(string[] args)
        {
            Console.WriteLine(CaseA());
            Console.WriteLine(CaseB());
            Console.WriteLine(CaseC());

            Setup();
            WriteCases();

            Console.WriteLine(CaseA());
            Console.WriteLine(CaseB());
            Console.WriteLine(CaseC());

            Console.WriteLine("Done");
            Console.ReadKey();
        }

        private static void Setup()
        {
            var bf = BindingFlags.Static | BindingFlags.NonPublic;
            var roField = typeof(Program).GetField(nameof(falseRO), bf);
            roField.SetValue(null, true);

            falseSettable = true;
        }

        private static void WriteCases()
        {
            Console.WriteLine();
            Console.WriteLine("A/B/C set? {0}, {1}, {2}", FalseConst, falseRO, falseSettable);
            Console.WriteLine();
        }
    }
}

Вопрос 1, простой: объяснить вывод для CaseB.
  Скрытый текст
A: Not set
B: Not set
C: Not set

A/B/C set? False, True, True

A: Not set
B: Not set
C: Set
Done


Вопрос 2, посложнее: где эта багофича выстреливает (в смысле, оказывается полезной)?
Отредактировано 07.01.2017 16:48 Sinix . Предыдущая версия . Еще …
Отредактировано 27.03.2016 16:38 Sinix . Предыдущая версия .
Отредактировано 27.03.2016 11:15 Sinix . Предыдущая версия .
минутка wtf
Re: [Этюд] Минутка WTF
От: _ichensky Европа https://github.com/ichensky
Дата: 27.03.16 13:15
Оценка: 11 (2) +1
Здравствуйте, Sinix, Вы писали:

Логический вывод:
т.к. поле falseRO
private static readonly bool falseRO = false;
— имеет аттрибут readonly, и в конструкторе не изменялось, компилятор соптимизировал
Console.WriteLine(CaseB()); на "Not set"

интересно как оно в msil выглядит.
Підтримати Україну у боротьбі з країною-терористом.

https://prytulafoundation.org/
https://u24.gov.ua/

Слава Збройним Силам України!!! Героям слава!!!
Re: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 13:41
Оценка: 80 (3) +1
Здравствуйте, Sinix, Вы писали:

S>Вопрос 2, посложнее: где эта багофича выстреливает?


Джит соптимизировал ( ). В MSIL выглядит один в один как исходнике, за исключением константы, конечно.

Вот что генерирует джит для CaseC:
return "C: " + (falseSettable ? "Set" : "Not set");
-----------------------------------------------------
00A80550  push        ebp  
00A80551  mov         ebp,esp  
00A80553  mov         ecx,dword ptr ds:[34022B4h]  
00A80559  cmp         byte ptr ds:[72439Bh],0  
00A80560  jne         00A8056A  
00A80562  mov         edx,dword ptr ds:[34022B0h]  
00A80568  jmp         00A80570  
00A8056A  mov         edx,dword ptr ds:[34022B8h]  
00A80570  call        5B8216B0  
00A80575  pop         ebp  
00A80576  ret


А вот как для CaseB:
return "B: " + (falseRO ? "Set" : "Not set");
-----------------------------------------------------
0073052A  in          al,dx  
0073052B  mov         ecx,dword ptr ds:[35122ACh]  
00730531  mov         edx,dword ptr ds:[35122B0h]  
00730537  call        5B8216B0  
0073053C  pop         ebp  
0073053D  ret


Только джит не смог превратить код в константу, или просто не стал делать этого из принципа

И для CaseA:
return "A: Not set";
-----------------------------------------------------
006404F2  in          al,dx  
006404F3  mov         eax,dword ptr ds:[34722A8h]  
006404F9  pop         ebp  
006404FA  ret
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Отредактировано 27.03.2016 13:50 rameel . Предыдущая версия .
Re: [Этюд] Минутка WTF
От: -n1l-  
Дата: 27.03.16 14:38
Оценка:
Здравствуйте, Sinix, Вы писали:

Я не понимаю чего объяснять, вызвали falseSettable = true; потом вызвали метод который на него смотрит
Re[2]: [Этюд] Минутка WTF
От: -n1l-  
Дата: 27.03.16 14:43
Оценка:
Я почему-то подумал про кейс C.

Про кейс b — var roField = typeof(Program).GetField(nameof(falseRO), bf);
Почему есть уверенность что roField — поле данного объекта?
Re[3]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 14:47
Оценка:
Здравствуйте, -n1l-, Вы писали:

N>Про кейс b — var roField = typeof(Program).GetField(nameof(falseRO), bf);

N>Почему есть уверенность что roField — поле данного объекта?

А чей еще? Ну и в коде все есть, понятно все без пояснений
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[2]: [Этюд] Минутка WTF
От: Sinix  
Дата: 27.03.16 14:49
Оценка:
Здравствуйте, rameel, Вы писали:


R>Джит соптимизировал ( ). В MSIL выглядит один в один как исходнике, за исключением константы, конечно.


Угу. И где оно может выстрелить, есть идеи?
Re[4]: [Этюд] Минутка WTF
От: -n1l-  
Дата: 27.03.16 14:50
Оценка:
Здравствуйте, rameel, Вы писали:
R>А чей еще? Ну и в коде все есть, понятно все без пояснений

Что есть? сеттим зачение у null'a?

roField.SetValue(null, true);

Вот пример с msdn почему то мы туда объект передаем что бы взять значение.
Re[5]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 14:54
Оценка: +1
Здравствуйте, -n1l-, Вы писали:

N>Что есть? сеттим зачение у null'a?

N>Вот пример с msdn почему то мы туда объект передаем что бы взять значение.

Для статических полей как раз null и передается.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[5]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 14:59
Оценка:
Здравствуйте, -n1l-, Вы писали:

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

R>>А чей еще? Ну и в коде все есть, понятно все без пояснений

N>Что есть? сеттим зачение у null'a?


N>roField.SetValue(null, true);


Вот https://msdn.microsoft.com/ru-ru/library/6z33zd7h(v=vs.110).aspx.

FieldInfo.SetValue(Object, Object)

If the field is static, obj will be ignored.

For non-static fields, obj should be an instance of a class that inherits or declares the field.

... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[3]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 15:12
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Угу. И где оно может выстрелить, есть идеи?


В голову пока ничего здравого не лезет, а то что лезет, то ССЗБ

PS. Упс. Первое сообщение я неправильно понял. Я твой вопрос в первом сообщении прочитал вот так: кто виноват?
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Re[4]: [Этюд] Минутка WTF
От: Sinix  
Дата: 27.03.16 15:20
Оценка:
Здравствуйте, rameel, Вы писали:

S>>Угу. И где оно может выстрелить, есть идеи?

R>В голову пока ничего здравого не лезет, а то что лезет, то ССЗБ

Ну тогда подождём до завтра, пусть интрига будет.
Re[6]: [Этюд] Минутка WTF
От: -n1l-  
Дата: 27.03.16 15:31
Оценка:
Ок, круто, но раз поле — readonly, то значит его значение можно задать в статическом конструкторе.

Тут мы видим задается значение не в статическом конструкторе.

Так в чем проблема?
Re[7]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 27.03.16 16:08
Оценка: 31 (2) +1
Здравствуйте, -n1l-, Вы писали:

N>Ок, круто, но раз поле — readonly, то значит его значение можно задать в статическом конструкторе.


Можно.

N>Тут мы видим задается значение не в статическом конструкторе.


И там и там.

N>Так в чем проблема?


Я обескуражен, если честно. Есть код, есть результат работы кода. Неужели не видно?

Проблема (проблема ли?) в том, что джит скомпилировал метод CaseB с ридонли-полем, обращаясь с ней как с константой, используя на момент компиляции значение присвоенное в статическом конструкторе. Соответственно, когда значение поля изменяется, на результат работы метода это уже никак не сказывается.
// Выполнили...
A: Not set
B: Not set
C: Not set

// Поменяли значение
// Проверяем, что, то что нужно изменило свое значение
A/B/C set? False, True, True

// Еще раз вызываем методы...
A: Not set
B: Not set  <-- здесь видим, что метод никак не отреагировал на изменение поля
C: Set      <-- здесь обычное поле


Done
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Отредактировано 27.03.2016 16:10 rameel . Предыдущая версия .
Re[4]: [Этюд] Минутка WTF
От: Sinix  
Дата: 27.03.16 16:44
Оценка:
Здравствуйте, rameel, Вы писали:


S>>Угу. И где оно может выстрелить, есть идеи?

R>PS. Упс. Первое сообщение я неправильно понял. Я твой вопрос в первом сообщении прочитал вот так: кто виноват?
Отредактировал на всякий случай второй вопрос. А то я его так сформулировал, что как хочешь отвечай

Имел в виду следующее: когда эта фича оказывается реально полезной?

Для второго варианта (привести пример, когда оно может больно ударить по носу) единственный (надеюсь) вариант есть в стартовом посте
Re[8]: [Этюд] Минутка WTF
От: -n1l-  
Дата: 27.03.16 17:41
Оценка:
R>Проблема (проблема ли?)
Вот именно.

R>B: Not set <-- здесь видим, что метод никак не отреагировал на изменение поля

А он должен был? Если мы собираемся изменить значение статического поля с маркером readonly без reflection то это почему то не компилитя.(sarcasm)
В чем весь сырбор?
Re: [Этюд] Минутка WTF
От: Pro100Oleh Украина  
Дата: 29.03.16 14:40
Оценка:
Здравствуйте, Sinix, Вы писали:

...
S>Вопрос 2, посложнее: где эта багофича выстреливает (в смысле, оказывается полезной)?

храним ссылку на синглтон?

ЗЫ: Рихтер у себя писал вроде как что JIT может повторно перекомпилить метод в целях оптимизации. И как пример был код с readonly полем. Другой пример перекомпиляции: реордеринг кода для например if — then — else
Pro
Отредактировано 29.03.2016 14:49 Pro100Oleh . Предыдущая версия .
Re[4]: [Этюд] Минутка WTF
От: Sinix  
Дата: 29.03.16 18:25
Оценка: 21 (1)
Здравствуйте, rameel, Вы писали:

R>PS. Упс. Первое сообщение я неправильно понял. Я твой вопрос в первом сообщении прочитал вот так: кто виноват?


Ну что нет ответов? Нда, это было (не)ожиданно.

  разгадка:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace ConsoleApplication1
{
    class Program
    {
        private const MethodImplOptions Options = MethodImplOptions.NoInlining;

        private static int Implementation1(int i) => i * i;
        private static int Implementation2(int i) => i + 1;
        private static int Implementation3(int i) => i / 2;

        private enum ImplementationToUse
        {
            Implementation1,
            Implementation2,
            Implementation3,
        }

        [MethodImpl(Options)]
        private static int DirectCall(int i) => Implementation2(i);

        private static volatile ImplementationToUse _ImplementationToUse1 = ImplementationToUse.Implementation2;
        [MethodImpl(Options)]
        private static int SwitchOverStaticField(int i)
        {
            switch (_ImplementationToUse1)
            {
                case ImplementationToUse.Implementation1:
                    return Implementation1(i);
                case ImplementationToUse.Implementation2:
                    return Implementation2(i);
                case ImplementationToUse.Implementation3:
                    return Implementation3(i);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private static readonly ImplementationToUse _ImplementationToUse2 = ImplementationToUse.Implementation2;
        [MethodImpl(Options)]
        private static int SwitchOverRoField(int i)
        {
            switch (_ImplementationToUse2)
            {
                case ImplementationToUse.Implementation1:
                    return Implementation1(i);
                case ImplementationToUse.Implementation2:
                    return Implementation2(i);
                case ImplementationToUse.Implementation3:
                    return Implementation3(i);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private static volatile bool _ImplementationToUse3 = false;
        [MethodImpl(Options)]
        private static int IfOverStaticField(int i)
        {
            if (_ImplementationToUse3)
            {
                return Implementation3(i);
            }
            return Implementation2(i);
        }

        private static readonly bool _ImplementationToUse4 = false;
        [MethodImpl(Options)]
        private static int IfOverRoField(int i)
        {
            if (_ImplementationToUse4)
            {
                return Implementation3(i);
            }
            return Implementation2(i);
        }

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

            Warmup();

            Run();

            Console.WriteLine("Done");
            Console.ReadKey();
        }

        private static void Warmup()
        {
            DirectCall(0);
            SwitchOverRoField(0);
            SwitchOverStaticField(0);
            IfOverRoField(0);
            IfOverStaticField(0);
        }

        private static void Run()
        {
            const int Count = 10 * 1000 * 1000;
            Measure("DirectCall", () =>
            {
                int sum = 0;
                for (int i = 0; i < Count; i++)
                {
                    sum += DirectCall(i);
                }
                return Count;
            });
            Measure("Switch", () =>
            {
                int sum = 0;
                for (int i = 0; i < Count; i++)
                {
                    sum += SwitchOverStaticField(i);
                }
                return Count;
            });
            Measure("Switch (ro)", () =>
            {
                int sum = 0;
                for (int i = 0; i < Count; i++)
                {
                    sum += SwitchOverRoField(i);
                }
                return Count;
            });
            Measure("If", () =>
            {
                int sum = 0;
                for (int i = 0; i < Count; i++)
                {
                    sum += IfOverStaticField(i);
                }
                return Count;
            });
            Measure("If (ro)", () =>
            {
                int sum = 0;
                for (int i = 0; i < Count; i++)
                {
                    sum += IfOverRoField(i);
                }
                return Count;
            });
        }

        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);
            GC.WaitForFullGCComplete(100);
            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,14}: {1,5}ms, ips: {2,22:N} | Mem: {3,10:N2} kb, GC 0/1/2: {4}/{5}/{6} => {7,6}",
                name, sw.ElapsedMilliseconds, result / sw.Elapsed.TotalSeconds, memDelta, gcDelta0, gcDelta1, gcDelta2, result);
        }
    }
}


Результатов не будет, сами-сами

Трюк обязателен к применению для выбора алгоритмов, которые зависят от окружения процесса — версии фреймворка, разрядности и тыды.
В ту же степь — всевозможные appswitch, которые включаются/отключаются через appconfig.

Ну и в реальном коде надо проверять вариант с
        private const MethodImplOptions Options = MethodImplOptions.AggressiveInlining;

и вариант с Options = 0
может оказаться быстрее, может медленнее.
Re[2]: [Этюд] Минутка WTF
От: Sinix  
Дата: 29.03.16 18:35
Оценка:
Здравствуйте, Pro100Oleh, Вы писали:


PO>ЗЫ: Рихтер у себя писал вроде как что JIT может повторно перекомпилить метод в целях оптимизации. И как пример был код с readonly полем. Другой пример перекомпиляции: реордеринг кода для например if — then — else


…но тут есть один нюанс:

Current versions of the CLR do not do this, but future versions might.

Ну и если Рихтера мало, то вот ещё пруф от Джона Скита.


Не, на самом деле всё как всегда немного сложнее, но к нашему случаю это никак не относится.
Re[5]: [Этюд] Минутка WTF
От: rameel https://github.com/rsdn/CodeJam
Дата: 29.03.16 19:44
Оценка: +1 :)
Здравствуйте, Sinix, Вы писали:

S>Ну что нет ответов? Нда, это было (не)ожиданно.


Нее, именно это я давно понял, еще кажись Андрей Акиньшин на конфе об этом рассказывал, если не путаю. Я другого ждал, я ждал чуда Не знаю какого, но чуда

S>Трюк обязателен к применению для выбора алгоритмов, которые зависят от окружения процесса — версии фреймворка, разрядности и тыды.

S>В ту же степь — всевозможные appswitch, которые включаются/отключаются через appconfig.

+1
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.