Использование ThreadStatic для замены дорогих местных жителей - хорошая идея? - PullRequest
15 голосов
/ 01 февраля 2011

Обновление : как и следовало ожидать, здравый совет сообщества в ответ на этот вопрос заключался в том, чтобы «измерить и увидеть». chibacity опубликовал ответ с несколькими действительно хорошими тестами, которые сделали это для меня;тем временем я написал собственный тест;и разница в производительности, которую я увидел, была на самом деле настолько огромной, что я чувствовал себя обязанным написать пост в блоге об этом.

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

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


У меня есть метод, который (среди многих других вещей) создает несколько средних массивов (~ 50 элементов) для нескольких локальных переменных.

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

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

(Метод инициализирует каждый элемент в самом массиве, поэтому наличие «устаревших» элементов в массиве не должнобыть проблемой.)

Мой вопрос прост: кажется ли это хорошей идеей?Есть ли подводные камни в использовании атрибута ThreadStatic таким образом (т. Е. В качестве оптимизации производительности для снижения затрат на создание новых объектов для локальных переменных), о которых мне следует знать?Возможно, производительность самого поля ThreadStatic невелика;например, есть ли много дополнительных «вещей», происходящих в фоновом режиме, с собственным набором затрат, чтобы сделать эту функцию возможной?

Мне также вполне вероятно, что я даже не пытаюсь пытатьсяоптимизировать что-то столь же дешевое (?), как массив из 50 элементов - и если это так, определенно дайте мне знать - но вопрос general все еще остается в силе.

Ответы [ 3 ]

9 голосов
/ 01 февраля 2011

[ThreadStatic] - нет бесплатный обед.Каждый доступ к переменной должен проходить через вспомогательную функцию в CLR (JIT_GetThreadFieldAddr_Primitive / Objref), а не компилироваться встроенным джиттером.Это также не является реальной заменой локальной переменной, рекурсия будет байтовой.Вы действительно должны профилировать это самостоятельно, угадать, насколько много CLR-кода в цикле не представляется возможным.

5 голосов
/ 01 февраля 2011

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

Как и во многих алгоритмах, которые имеют большое количество итераций, я подозреваю, что это прямой случайнакладные расходы GC убивают его для версии, которая выделяет новые массивы:

Обновление

С тестами, которые включают добавленную итерацию массива для моделирования минимального использования ссылки на массив плюсThreadStatic Использование ссылки на массив в дополнение к предыдущему тесту, где ссылка была скопирована локально:

Iterations : 10,000,000

Local ArrayRef          (- array iteration) : 330.17ms
Local ArrayRef          (- array iteration) : 327.03ms
Local ArrayRef          (- array iteration) : 1382.86ms
Local ArrayRef          (- array iteration) : 1425.45ms
Local ArrayRef          (- array iteration) : 1434.22ms
TS    CopyArrayRefLocal (- array iteration) : 107.64ms
TS    CopyArrayRefLocal (- array iteration) : 92.17ms
TS    CopyArrayRefLocal (- array iteration) : 92.42ms
TS    CopyArrayRefLocal (- array iteration) : 92.07ms
TS    CopyArrayRefLocal (- array iteration) : 92.10ms
Local ArrayRef          (+ array iteration) : 1740.51ms
Local ArrayRef          (+ array iteration) : 1647.26ms
Local ArrayRef          (+ array iteration) : 1639.80ms
Local ArrayRef          (+ array iteration) : 1639.10ms
Local ArrayRef          (+ array iteration) : 1646.56ms
TS    CopyArrayRefLocal (+ array iteration) : 368.03ms
TS    CopyArrayRefLocal (+ array iteration) : 367.19ms
TS    CopyArrayRefLocal (+ array iteration) : 367.22ms
TS    CopyArrayRefLocal (+ array iteration) : 368.20ms
TS    CopyArrayRefLocal (+ array iteration) : 367.37ms
TS    TSArrayRef        (+ array iteration) : 360.45ms
TS    TSArrayRef        (+ array iteration) : 359.97ms
TS    TSArrayRef        (+ array iteration) : 360.48ms
TS    TSArrayRef        (+ array iteration) : 360.03ms
TS    TSArrayRef        (+ array iteration) : 359.99ms

Код:

[ThreadStatic]
private static int[] _array;

[Test]
public object measure_thread_static_performance()
{
    const int TestIterations = 5;
    const int Iterations = (10 * 1000 * 1000);
    const int ArraySize = 50;

    Action<string, Action> time = (name, test) =>
    {
        for (int i = 0; i < TestIterations; i++)
        {
            TimeSpan elapsed = TimeTest(test, Iterations);
            Console.WriteLine("{0} : {1:F2}ms", name, elapsed.TotalMilliseconds);
        }
    };

    int[] array = null;
    int j = 0;

    Action test1 = () =>
    {
        array = new int[ArraySize];
    };

    Action test2 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);
    };

    Action test3 = () =>
    {
        array = new int[ArraySize];

        for (int i = 0; i < ArraySize; i++)
        {
            j = array[i];
        }
    };

    Action test4 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);

        for (int i = 0; i < ArraySize; i++)
        {
            j = array[i];
        }
    };

    Action test5 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);

        for (int i = 0; i < ArraySize; i++)
        {
            j = _array[i];
        }
    };

    Console.WriteLine("Iterations : {0:0,0}\r\n", Iterations);
    time("Local ArrayRef          (- array iteration)", test1);
    time("TS    CopyArrayRefLocal (- array iteration)", test2);
    time("Local ArrayRef          (+ array iteration)", test3);
    time("TS    CopyArrayRefLocal (+ array iteration)", test4);
    time("TS    TSArrayRef        (+ array iteration)", test5);

    Console.WriteLine(j);

    return array;
}

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static TimeSpan TimeTest(Action action, int iterations)
{
    Action gc = () =>
    {
        GC.Collect();
        GC.WaitForFullGCComplete();
    };

    Action empty = () => { };

    Stopwatch stopwatch1 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++)
    {
        empty();
    }

    TimeSpan loopElapsed = stopwatch1.Elapsed;

    gc();
    action(); //JIT
    action(); //Optimize

    Stopwatch stopwatch2 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++) action();

    gc();

    TimeSpan testElapsed = stopwatch2.Elapsed;

    return (testElapsed - loopElapsed);
}
2 голосов
/ 01 февраля 2011

По результатам, подобным this , ThreadStatic выглядит довольно быстро.Я не уверен, что у кого-то есть конкретный ответ, если это быстрее, чем перераспределение массива из 50 элементов.Это то, что вам нужно сравнить себя.:)

Я немного разозлился, если это "хорошая идея" или нет.Пока все детали реализации хранятся внутри класса, это не обязательно плохая идея (вы на самом деле не хотите, чтобы вызывающая сторона беспокоилась об этом), но если тесты производительности не показали выигрыша в производительности от этого метода, я бы просто придерживалсяВыделение массива каждый раз, потому что это делает код проще и легче для чтения.Как более сложное из двух решений, мне нужно увидеть некоторые преимущества от сложности, прежде чем выбрать это.

...