Логический вывод:
т.к. поле falseRO
private static readonly bool falseRO = false;
— имеет аттрибут readonly, и в конструкторе не изменялось, компилятор соптимизировал
Console.WriteLine(CaseB()); на "Not set"
интересно как оно в msil выглядит.
Підтримати Україну у боротьбі з країною-терористом.
Только джит не смог превратить код в константу, или просто не стал делать этого из принципа
И для CaseA:
return "A: Not set";
-----------------------------------------------------
006404F2 in al,dx
006404F3 mov eax,dword ptr ds:[34722A8h]
006404F9 pop ebp
006404FA ret
Здравствуйте, -n1l-, Вы писали:
N>Про кейс b — var roField = typeof(Program).GetField(nameof(falseRO), bf); N>Почему есть уверенность что roField — поле данного объекта?
А чей еще? Ну и в коде все есть, понятно все без пояснений
Здравствуйте, -n1l-, Вы писали:
N>Здравствуйте, rameel, Вы писали: R>>А чей еще? Ну и в коде все есть, понятно все без пояснений
N>Что есть? сеттим зачение у null'a?
N>roField.SetValue(null, true);
Здравствуйте, -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
S>>Угу. И где оно может выстрелить, есть идеи? R>PS. Упс. Первое сообщение я неправильно понял. Я твой вопрос в первом сообщении прочитал вот так: кто виноват?
Отредактировал на всякий случай второй вопрос. А то я его так сформулировал, что как хочешь отвечай
Имел в виду следующее: когда эта фича оказывается реально полезной?
Для второго варианта (привести пример, когда оно может больно ударить по носу) единственный (надеюсь) вариант есть в стартовом посте
R>Проблема (проблема ли?)
Вот именно.
R>B: Not set <-- здесь видим, что метод никак не отреагировал на изменение поля
А он должен был? Если мы собираемся изменить значение статического поля с маркером readonly без reflection то это почему то не компилитя.(sarcasm)
В чем весь сырбор?
... S>Вопрос 2, посложнее: где эта багофича выстреливает (в смысле, оказывается полезной)?
храним ссылку на синглтон?
ЗЫ: Рихтер у себя писал вроде как что JIT может повторно перекомпилить метод в целях оптимизации. И как пример был код с readonly полем. Другой пример перекомпиляции: реордеринг кода для например if — then — else
Здравствуйте, 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.
PO>ЗЫ: Рихтер у себя писал вроде как что JIT может повторно перекомпилить метод в целях оптимизации. И как пример был код с readonly полем. Другой пример перекомпиляции: реордеринг кода для например if — then — else
…но тут есть один нюанс:
Current versions of the CLR do not do this, but future versions might.
Здравствуйте, Sinix, Вы писали:
S>Ну что нет ответов? Нда, это было (не)ожиданно.
Нее, именно это я давно понял, еще кажись Андрей Акиньшин на конфе об этом рассказывал, если не путаю. Я другого ждал, я ждал чуда Не знаю какого, но чуда
S>Трюк обязателен к применению для выбора алгоритмов, которые зависят от окружения процесса — версии фреймворка, разрядности и тыды. S>В ту же степь — всевозможные appswitch, которые включаются/отключаются через appconfig.