Производительность статического конструктора и почему мы не можем указать beforefieldinit - PullRequest
7 голосов
/ 16 ноября 2011

Я столкнулся с разницей в скорости, используя следующие две структуры:

public struct NoStaticCtor
{
    private static int _myValue = 3;
    public static int GetMyValue() { return _myValue; }
}

public struct StaticCtor
{
    private static int _myValue;
    public static int GetMyValue() { return _myValue; }
    static StaticCtor()
    {
        _myValue = 3;
    }
}

class Program
{
    static void Main(string[] args)
    {
        long numTimes = 5000000000; // yup, 5 billion
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (long i = 0; i < numTimes; i++)
        {
            NoStaticCtor.GetMyValue();
        }
        sw.Stop();
        Console.WriteLine("No static ctor: {0}", sw.Elapsed);

        sw.Restart();
        for (long i = 0; i < numTimes; i++)
        {
            StaticCtor.GetMyValue();
        }
        sw.Stop();
        Console.WriteLine("with static ctor: {0}", sw.Elapsed);
    }
}

Который дает результаты:

Release (x86), no debugger attached:
No static ctor: 00:00:05.1111786
with static ctor: 00:00:09.9502592

Release (x64), no debugger attached:
No static ctor: 00:00:03.2595979
with static ctor: 00:00:14.5922220

Компилятор создает статический конструктор для NoStaticCtor, идентичный тому, который явно объявлен в StaticCtor. Я понимаю, что компилятор будет выдавать beforefieldinit только тогда, когда статический конструктор не определен явно.

Они генерируют почти идентичный код il, за исключением одного различия, объявляя структуру с beforefieldinit, в которой я чувствую разницу, поскольку знаю, что она определяет, когда вызывается конструктор типа, хотя Я не могу понять, почему есть такая разница. Предполагается, что он не вызывает конструктор типа на каждой итерации, поскольку конструктор типа может быть вызван только один раз. 1

Итак,

1) Почему разница во времени между структурой с beforefieldinit и без нее? (Я полагаю, что JITer делает что-то дополнительное в цикле for, однако я понятия не имею, как просмотреть выходные данные JITer, чтобы увидеть, что.

2) Почему разработчики компилятора а) не сделали все структуры beforefieldinit стандартными и б) не дали разработчикам возможность явно указать такое поведение? Конечно, это означает , если вы не можете, поскольку я не смог найти способ.


Edit:

1 . Я изменил код , фактически выполняя каждый цикл во второй раз, ожидая улучшения, но это было немного:

No static ctor: 00:00:03.3342359
with static ctor: 00:00:14.6139917
No static ctor: 00:00:03.2229995
with static ctor: 00:00:12.9524860
Press any key to continue . . .

Я сделал это, потому что я, ну, в общем, может быть , хотя маловероятно, что JITer фактически вызывал конструктор типов каждую итерацию. Мне кажется, JITer знал бы, что конструктор типа уже был вызван, и не генерировал бы код, чтобы сделать это при компиляции второго цикла.

В дополнение к ответу Мотти: Этот код дает лучшие результаты из-за разницы в JITing, JITing DoSecondLoop не генерирует статическую проверку ctor, потому что он обнаружил, что это было сделано ранее в DoFirstLoop, заставляя каждый цикл выполнять с той же скоростью. (~ 3 секунды)

1 Ответ

11 голосов
/ 16 ноября 2011

При первом обращении к вашему типу должен выполняться статический ctor (генерируемый явно или неявно).

Когда JIT-компилятор компилирует IL с нативными инструкциями, он проверяет, был ли выполнен статический ctor для этого типа, и, если нет, испускает нативный код, который проверяет (снова), был ли выполнен статический ctor и, если нет, выполняет его.

Объединенный код кэшируется для будущих вызовов того же метода (и именно поэтому код должен был еще раз проверить, был ли выполнен статический ctor).

Дело в том, что если объединенный код, который проверяет статический ctor, находится в цикле, этот тест будет выполняться на каждой итерации.

Когда присутствует beforefieldinit, JIT-компилятору разрешено оптимизировать код, проверяя статический вызов ctor ДО входа в цикл. Если его нет, эта оптимизация не разрешена.

Компилятор C # автоматически решает нам, когда испускать этот атрибут. В настоящее время он (C # 4) выдает его, только если код НЕ явно определяет статический конструктор. Идея заключается в том, что если вы сами определили статический ctor, время может быть для вас более важным, и ststic ctor не должен выполняться раньше времени. Это может или не может быть правдой, но вы не можете изменить это поведение.

Вот ссылка на часть моего интерактивного учебника по .NET, в которой это подробно объясняется: http://motti.me/c1L

Кстати, я не рекомендую использовать статические ctors для структур, потому что, верьте или нет, они не гарантированно будут выполняться! Это не было частью вопроса, поэтому я не буду вдаваться в подробности, но посмотрим на это подробнее, если вам интересно: http://motti.me/c1I (я касаюсь предмета примерно в 2:30 в видео).

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...