Своеобразный результат, касающийся размера структуры и производительности - PullRequest
6 голосов
/ 27 сентября 2010

Мне было любопытно, что такое большая структура по сравнению с небольшой структурой при использовании операторов + и * для математики.Итак, я создал две структуры: одну Small с 1 двойным полем (8 байт) и одну Big с 10 двойными (80 байт).Во всех моих операциях я манипулирую только одним полем с именем x.

Сначала я определил в обеих структурах математические операторы, такие как

public static Small operator +(Small a, Small b)
{
    return new Small(a.x + b.x);
}
public static Small operator *(double x, Small a)
{
    return new Small(x * a.x);
}

, которые, как и ожидалось, занимают много памяти в стеке.для копирования полей вокруг.Я выполнил 5 000 000 итераций математической операции и получил то, что подозревал (замедление в 3 раза).

public double TestSmall()
{
    pt.Start(); // pt = performance timing object
    Small r = new Small(rnd.NextDouble()); //rnd = Random number generator
    for (int i = 0; i < N; i++)
    {
        a = 0.6 * a + 0.4 * r;   // a is a local field of type Small
    }
    pt.Stop();
    return pt.ElapsedSeconds;
}

результаты из кода выпуска (в секундах)

Small=0.33940 Big=0.98909     Big is Slower by x2.91

Теперь для интересной части,Я определяю те же операции со статическими методами с ref аргументами

public static void Add(ref Small a, ref Small b, ref Small res)
{
    res.x = a.x + b.x;
}
public static void Scale(double x, ref Small a, ref Small res)
{
    res.x = x * a.x;
}

и выполняю такое же количество итераций для этого кода теста:

public double TestSmall2()
{
    pt.Start(); // pt = performance timing object
    Small a1 = new Small(); // local
    Small a2 = new Small(); // local
    Small r = new Small(rnd.NextDouble()); //rdn = Random number generator
    for (int i = 0; i < N; i++)
    {
        Small.Scale(0.6, ref a, ref a1);
        Small.Scale(0.4, ref r, ref a2);
        Small.Add(ref a1, ref a2, ref a);
    }
    pt.Stop();
    return pt.ElapsedSeconds;
}

И результаты показывают (в секундах))

Small=0.11765 Big=0.07130     Big is Slower by x0.61

Таким образом, по сравнению с операторами интенсивного копирования в память, я получаю ускорение x3 и x14, что замечательно, но сравните времена структуры Small с Big, и вы увидите, что Small - 60% медленнее чем Большой.

Может кто-нибудь объяснить это?Имеет ли это отношение к конвейеру ЦП, а разделение операций в (пространственно) памяти обеспечивает более эффективную предварительную выборку данных?

Если вы хотите попробовать это сами, возьмите код из моего выпадающего списка http://dl.dropbox.com/u/11487099/SmallBigCompare.zip

Ответы [ 5 ]

3 голосов
/ 27 сентября 2010

Кажется, в вашем тесте есть несколько недостатков.

  1. Используйте Stopwatch вместо типа PerformanceTimer. Я не знаком с последним, и это, кажется, сторонний компонент. Особенно беспокоит то, что он измеряет время в EllapsedSeconds вместо EllapsedMilliseconds.
  2. Следует выполнить каждый тест дважды и считать только секунду, чтобы исключить потенциальные затраты JIT
  3. Marshal.SizeOf - это не фактический размер структуры, а только его упорядоченный размер.

После переключения на Stopwatch я вижу, что эталонный тест работает как ожидалось, производя почти одинаковое время для обоих типов в случае статической ссылки.

1 голос
/ 27 сентября 2010

Я не могу воспроизвести ваши результаты.На моем компьютере «ref» версия имеет в основном одинаковую производительность для Big и Small, в пределах допуска.

(режим Running Release без отладчика, с 10 или 100-кратным числом итерацийчтобы попытаться получить хороший долгосрочный прогон.)

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

0 голосов
/ 28 сентября 2010

Спасибо всем за их вклад.Вот некоторые заключительные мысли.

PerformanceCounter дает те же результаты, что и секундомер, так что это не проблема.

Окончательные результаты:

1. Длямалая структура с использованием оператора или by-ref дает ту же производительность
2. Для большой структуры использование by-ref похоже на 14x быстрее
3 .Большая структура в x20 медленнее, чем маленькая структура для операторов (как и ожидалось)
4. Большая структура примерно на 50% медленнее, чем маленькая структура с by-ref (все еще интересно)

Такпоследний вопрос: какой механизм замедляет Big-структуру с помощью by-ref, поскольку копирование стека не должно выполняться?


Результаты выпуска исполняемого файла, внешнего по отношению к Visual Studio.

Size of Small is 8 bytes
Size of Big is 80 bytes
5,000,000.00 Iterations
Warming up the CPU's

Using QueryPerformanceCounter
Operator Results
  Small=0.03545 Big=0.71519     Slower=x20.18

StaticRef Results
  Small=0.03526 Big=0.05194     Slower=x1.47
  Small=x1.01   Big=x13.77
0 голосов
/ 27 сентября 2010

У меня есть пара предложений.

  • Используйте класс Stopwatch. Он использует те же API-интерфейсы Win32, но уже написан для вас.
  • Увеличьте число итераций, чтобы ваши тесты выполнялись не менее 1 с (или более), иначе аномалии могут возникнуть и доминировать во времени.
  • Рассмотрим влияние процесса vshost.exe. Вы получите разные результаты для сборок Debug и Release в зависимости от того, запускаете ли вы приложение автономно или через хост-процесс Visual Studio.

Когда я запускал ваш код, во всех тестовых сценариях я видел похожие результаты для проходного теста. Что меня действительно поразило, так это то, насколько быстрее меньшая структура была в автономной сборке Release (т.е. не через vshost.exe).

Релиз сборки автономно:

Size of Small is 8 bytes
Size of Big is 80 bytes
50,000,000.00 Iterations
Operator Results
  Small=0.57173 Big=25.58988    Slower=x44.76

StaticRef Results
  Small=26.06602        Big=26.68569    Slower=x1.02
  Small=x0.02   Big=x0.96

Выпуск сборки через vshost:

Size of Small is 8 bytes
Size of Big is 80 bytes
50,000,000.00 Iterations
Operator Results
  Small=4.56601 Big=35.33387    Slower=x7.74

StaticRef Results
  Small=37.94317        Big=39.64959    Slower=x1.04
  Small=x0.12   Big=x0.89
0 голосов
/ 27 сентября 2010

Согласен с Джаредом, это ошибка бенчмаркинга.

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

    Size of Small is 8 bytes
    Size of Big is 80 bytes

    5,000,000.00 Iterations
    Operator Results
      Small=523.00000       Big=1953.00000  Slower=x3.73
    StaticRef Results
      Small=2042.00000      Big=2125.00000  Slower=x1.04
      Small=x0.26   Big=x0.92

    5,000,000.00 Iterations
    Operator Results
      Small=2464.00000      Big=3510.00000  Slower=x1.42
    StaticRef Results
      Small=3578.00000      Big=3647.00000  Slower=x1.02
      Small=x0.69   Big=x0.96

    5,000,000.00 Iterations
    Operator Results
      Small=3921.00000      Big=4817.00000  Slower=x1.23
    StaticRef Results
      Small=4880.00000      Big=4944.00000  Slower=x1.01
      Small=x0.80   Big=x0.97
...