Статические конструкторы приводят к снижению производительности? - PullRequest
15 голосов
/ 27 мая 2010

Недавно прочитал в статье на dotnetpearls.com здесь о том, что статические ядра подвергаются значительному снижению производительности.

Не могу понять, почему?

Ответы [ 4 ]

18 голосов
/ 27 мая 2010

Я думаю, что "существенное" - это преувеличение в большинстве случаев использования.

Наличие статического конструктора (даже если он ничего не делает) влияет на время инициализации типа из-за наличия / отсутствия флага beforefieldinit . Существуют более строгие гарантии относительно времени, когда у вас есть статический конструктор.

Для большинства кода, я бы предположил, что это не имеет большого значения - но если вы работаете в тесном цикле и получаете доступ к статическому члену класса, это может произойти. Лично я не стал бы беспокоиться об этом слишком сильно - если у вас есть подозрение, что оно имеет отношение к вашему реальному приложению, то протестируйте его, а не угадывайте. Микробенчмарки могут сильно преувеличивать эффект.

Стоит отметить, что .NET 4 ведет себя несколько иначе, чем в предыдущих версиях , когда дело доходит до инициализации типа - поэтому любые тесты должны действительно показывать разные версии, чтобы быть актуальными.

11 голосов
/ 27 мая 2010

Ну, я только что повторил его тест.

За 1000000000 итераций с DEBUG build я получаю:

  • 4s для своего статического класса со статическим Конструктор
  • 3.6с того же класса с закомментированным статическим конструктором
  • 2.9 с классом не статичным (и создание экземпляра перед итерация) либо со статическим конструктор или нет

То же самое с RELEASE build не выделяет разницу:

  • Статический класс со статическим конструктором: 4046,875 мс
  • Статический класс без статического конструктора: 484,375 мс
  • Экземпляр со статическим конструктором: 484.375ms
  • Экземпляр без статического конструктора: 484,375 мс
6 голосов
/ 27 мая 2010

CLR предоставляет довольно надежную гарантию выполнения статических конструкторов, он обещает вызывать их только один раз и за до может выполняться любой метод в классе. Эту гарантию довольно сложно реализовать, когда в классе используется несколько потоков.

Взглянув на исходный код CLR для SSCLI20, я вижу довольно большой кусок кода, предназначенный для предоставления этой гарантии. Он поддерживает список запущенных статических конструкторов, защищенных глобальной блокировкой. Как только он получает запись в этом списке, он переключается на специфическую для класса блокировку, которая гарантирует, что никакие два потока не могут запускать конструктор. Дважды проверил блокировку бита состояния, который указывает, что конструктор уже был запущен. Много непостижимого кода, обеспечивающего гарантии исключений.

Ну, этот код не предоставляется бесплатно. Добавьте его к времени выполнения самого cctor, и вы посмотрите на некоторые накладные расходы. Как всегда, не позволяйте этому стеснять ваш стиль, эта гарантия также очень хороша, которую вы не захотите предоставить себе. И измерить, прежде чем исправить.

0 голосов
/ 03 марта 2014

Я только что провел небольшой тест, чтобы проверить влияние добавления статического конструктора в один из моих классов.

У меня есть базовый класс, который выглядит так:

public abstract class Base
{
    public abstract Task DoStuffAsync();
}

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

public sealed class Test1 : Base
{
    readonly Task _emptyTask;

    public Test1()
    {
        TaskCompletionSource<Object> source = new TaskCompletionSource<object>();
        source.SetResult(null);
        _emptyTask = source.Task;
    }

    public override Task DoStuffAsync()
    {
        return _emptyTask;
    }
}

(Другой вариант - вернуть задачу по требованию, но оказывается, что этот метод всегда вызывается)

Объекты этого класса создаются очень очень часто, обычно в циклах. Глядя на это, похоже, что установка _emptyTask в качестве статического поля была бы полезной, поскольку она была бы одинаковой Task для всех методов:

public sealed class Test2 : Base
{
    static readonly Task _emptyTask;

    static Test2()
    {
        TaskCompletionSource<Object> source = new TaskCompletionSource<object>();
        source.SetResult(null);
        _emptyTask = source.Task;
    }

    public override Task DoStuffAsync()
    {
        return _emptyTask;
    }
}

Затем я вспоминаю «проблему» со статическими конструкторами и производительностью, и после небольшого исследования (вот как я здесь оказался), я решил сделать небольшой тест:

Stopwatch sw = new Stopwatch();
List<Int64> test1list = new List<Int64>(), test2list = new List<Int64>();

for (int j = 0; j < 100; j++)
{
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        Test1 t = new Test1();
        if (!t.DoStuffAsync().IsCompleted)
            throw new Exception();
    }
    sw.Stop();
    test1list.Add(sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        Test2 t = new Test2();
        if (!t.DoStuffAsync().IsCompleted)
            throw new Exception();
    }
    sw.Stop();
    test2list.Add(sw.ElapsedMilliseconds);

    sw.Reset();
    GC.Collect();
}

Console.WriteLine("Test 1: " + test1list.Average().ToString() + "ms.");
Console.WriteLine("Test 2: " + test2list.Average().ToString() + "ms.");

И результаты вполне понятны:

 Test 1: 53.07 ms. 
 Test 2: 5.03 ms. 
 end

Таким образом, несмотря на наличие статического конструктора, выгода перевешивает проблему. Поэтому всегда измеряйте.

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