int, короткая, байтовая производительность в петлях for-to-back for - PullRequest
14 голосов
/ 07 апреля 2010

(фон: Почему я должен использовать int вместо байта или short в C # )

Чтобы удовлетворить мое собственное любопытство о плюсах и минусах использования целого числа «соответствующего размера» против «оптимизированного» целого числа, я написал следующий код, который подкрепил то, что я ранее придерживался в отношении производительности int в .Net (и которое объясняется в ссылке выше), которая оптимизирована для производительности int, а не для коротких или байтовых значений.

DateTime t;
long a, b, c;

t = DateTime.Now;
for (int index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}           
a = DateTime.Now.Ticks - t.Ticks;

t = DateTime.Now;
for (short index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}

b=DateTime.Now.Ticks - t.Ticks;

t = DateTime.Now;           
for (byte index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}
c=DateTime.Now.Ticks - t.Ticks;

Console.WriteLine(a.ToString());
Console.WriteLine(b.ToString());
Console.WriteLine(c.ToString());

Это дает примерно одинаковые результаты в области ...

~ 950000

~ 2000000

~ 1700000

Что соответствует тому, что я ожидал бы увидеть.

Однако, когда я пытаюсь повторить циклы для каждого типа данных, как это ...

t = DateTime.Now;
for (int index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}
for (int index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}
for (int index = 0; index < 127; index++)
{
    Console.WriteLine(index.ToString());
}
a = DateTime.Now.Ticks - t.Ticks;

Числа больше похожи на ...

~ 4500000

~ 3100000

~ 300000

Что я нахожу загадочным. Кто-нибудь может предложить объяснение?

Примечание: В целях сравнения подобного с подобным я ограничил циклы 127 из-за диапазона типа значения byte . Также это акт любопытства , а не микрооптимизации производственного кода.

Ответы [ 6 ]

40 голосов
/ 07 апреля 2010

Прежде всего, это не .NET, оптимизированная для int производительности, это машина , которая оптимизирована, потому что 32-битный - это собственный размер слова (если только вы не используете x64, в этом случае это long или 64 бита).

Во-вторых, вы пишете на консоль внутри каждого цикла - это слишком дорого, чем увеличение и тестирование счетчика цикла, поэтому вы не измеряете ничего реалистичного.

В-третьих, byte имеет диапазон до 255, поэтому вы можете выполнить цикл 254 раза (если вы попытаетесь выполнить 255, он переполнится и цикл никогда не закончится - но вам не нужно останавливаться на 128).

В-четвертых, вы нигде не выполняете около достаточно итераций для профилирования. Повторять жесткий цикл 128 или даже 254 раза бессмысленно. То, что вы должны сделать, это поместить цикл byte / short / int в другой цикл, который повторяется гораздо большее число раз, скажем, 10 миллионов, и проверяет результаты этого.

Наконец, использование DateTime.Now в расчетах приведет к некоторому временному «шуму» при профилировании. Вместо этого рекомендуется (и проще) использовать класс Секундомер .

Суть в том, что для этого требуется много изменений, прежде чем он станет действительным перф тестом.


Вот что я считаю более точной тестовой программой:

class Program
{
    const int TestIterations = 5000000;

    static void Main(string[] args)
    {
        RunTest("Byte Loop", TestByteLoop, TestIterations);
        RunTest("Short Loop", TestShortLoop, TestIterations);
        RunTest("Int Loop", TestIntLoop, TestIterations);
        Console.ReadLine();
    }

    static void RunTest(string testName, Action action, int iterations)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();
        Console.WriteLine("{0}: Elapsed Time = {1}", testName, sw.Elapsed);
    }

    static void TestByteLoop()
    {
        int x = 0;
        for (byte b = 0; b < 255; b++)
            ++x;
    }

    static void TestShortLoop()
    {
        int x = 0;
        for (short s = 0; s < 255; s++)
            ++x;
    }

    static void TestIntLoop()
    {
        int x = 0;
        for (int i = 0; i < 255; i++)
            ++x;
    }
}

Это запускает каждый цикл внутри гораздо большего цикла (5 миллионов итераций) и выполняет очень простую операцию внутри цикла (увеличивает переменную). Результаты для меня были:

Байт: истекшее время = 00: 00: 03.8949910
Короткий цикл: истекшее время = 00: 00: 03.9098782
Int Loop: Истекшее время = 00: 00: 03.2986990

Итак, заметной разницы нет.

Кроме того, убедитесь, что вы профилируете в режиме выпуска, многие люди забывают и тестируют в режиме отладки, который будет значительно менее точным.

12 голосов
/ 07 апреля 2010

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

Дополнительно:

  • Использование DateTime.Now - плохой способ измерения времени. Используйте System.Diagnostics.Stopwatch вместо
  • Как только вы избавитесь от вызова Console.WriteLine, цикл из 127 итераций будет слишком коротким для измерения. Вам нужно выполнить цикл лотов раз, чтобы получить разумное измерение.

Вот мой тест:

using System;
using System.Diagnostics;

public static class Test
{    
    const int Iterations = 100000;

    static void Main(string[] args)
    {
        Measure(ByteLoop);
        Measure(ShortLoop);
        Measure(IntLoop);
        Measure(BackToBack);
        Measure(DelegateOverhead);
    }

    static void Measure(Action action)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            action();
        }
        sw.Stop();
        Console.WriteLine("{0}: {1}ms", action.Method.Name,
                          sw.ElapsedMilliseconds);
    }

    static void ByteLoop()
    {
        for (byte index = 0; index < 127; index++)
        {
            index.ToString();
        }
    }

    static void ShortLoop()
    {
        for (short index = 0; index < 127; index++)
        {
            index.ToString();
        }
    }

    static void IntLoop()
    {
        for (int index = 0; index < 127; index++)
        {
            index.ToString();
        }
    }

    static void BackToBack()
    {
        for (byte index = 0; index < 127; index++)
        {
            index.ToString();
        }
        for (short index = 0; index < 127; index++)
        {
            index.ToString();
        }
        for (int index = 0; index < 127; index++)
        {
            index.ToString();
        }
    }

    static void DelegateOverhead()
    {
        // Nothing. Let's see how much
        // overhead there is just for calling
        // this repeatedly...
    }
}

И результаты:

ByteLoop: 6585ms
ShortLoop: 6342ms
IntLoop: 6404ms
BackToBack: 19757ms
DelegateOverhead: 1ms

(Это на нетбуке - корректируйте количество итераций, пока не получите что-то толковое:)

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

4 голосов
/ 23 мая 2012

Просто из любопытства я изменил программу Aaronaught и скомпилировал ее в режимах x86 и x64.Странно, Int работает намного быстрее в x64:

x86

Байт Loop: Elapsed Time = 00: 00: 00.8636454
Short Loop: Elapsed Time = 00: 00: 00.8795518
UShort Loop: истекшее время = 00: 00: 00.8630357
Int Loop: истекшее время = 00: 00: 00.5184154
UInt Loop: истекшее время = 00: 00: 00.4950156
длинный цикл: истекшее время= 00: 00: 01.2941183
Цикл ULong: истекшее время = 00: 00: 01.3023409

x64

Цикл байта: истекшее время = 00: 00: 01.0646588
Короткий цикл: истекшее время = 00: 00: 01.0719330
UShort Loop: Истекшее время = 00: 00: 01.0711545
Внутренний цикл: истекшее время = 00: 00: 00.2462848
UInt Loop: Истекшее время= 00: 00: 00.4708777
Длинный цикл: истекшее время = 00: 00: 00.5242272
ULong Loop: Истекшее время = 00: 00: 00.5144035

2 голосов
/ 07 апреля 2010

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

Выходы из тестовой системы Aaronaughts

Short Loop: Elapsed Time = 00:00:00.8299340
Byte Loop: Elapsed Time = 00:00:00.8398556
Int Loop: Elapsed Time = 00:00:00.3217386
Long Loop: Elapsed Time = 00:00:00.7816368

намного быстрее

Выходы от Джона

ByteLoop: 1126ms
ShortLoop: 1115ms
IntLoop: 1096ms
BackToBack: 3283ms
DelegateOverhead: 0ms

ничего в этом нет

У Джона есть большая фиксированная константа вызова tostring в результатах, которая может скрывать возможные выгоды, которые могут возникнуть, если работа, выполненная в цикле, была меньше. Aaronaught использует 32-битную ОС, которая, кажется, не выиграет от использования целых чисел так же, как используемая мною x64.

Аппаратное / программное обеспечение Результаты были получены на Core i7 975 с тактовой частотой 3,33 ГГц с отключенным турбонаддувом и установленным сродством ядра, чтобы уменьшить влияние других задач. Все настройки производительности установлены на максимум, а антивирусные программы / ненужные фоновые задачи приостановлены. Windows 7 x64 Ultimate с 11 ГБ свободного ОЗУ и очень небольшая активность ввода-вывода. Запустите конфигурацию выпуска, встроенную в версию 2008, без отладчика или профилировщика.

Повторяемость Первоначально повторяется 10 раз, меняя порядок выполнения для каждого теста. Вариация была незначительной, поэтому я опубликовал только свой первый результат. При максимальной загрузке процессора соотношение времени выполнения оставалось неизменным. Повторные прогоны на нескольких x64 xp xeon-блейдах дают примерно одинаковые результаты после учета процессорной мощности и Ghz

Профилирование Профилировщик Redgate / Jetbrains / Slimtune / CLR и мой собственный профилировщик показывают, что результаты верны.

Отладочная сборка Использование параметров отладки в VS дает согласованные результаты, как у Аарона.

0 голосов
/ 08 апреля 2010

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

Предложения: битовые сдвиги, умножения, манипуляции с массивами, сложение, многие другие ...

0 голосов
/ 07 апреля 2010

Профилирование .Net-кода очень сложно, потому что среда выполнения, в которой работает скомпилированный байт-код, может выполнять оптимизацию времени выполнения для байт-кода. Во втором примере JIT-компилятор, вероятно, обнаружил повторяющийся код и создал более оптимизированную версию. Но без какого-либо действительно подробного описания того, как работает система времени выполнения, невозможно знать, что произойдет с вашим кодом. И было бы глупо пытаться угадать на основе экспериментов, так как Microsoft полностью в своих правах может изменить дизайн движка JIT в любое время, если они не нарушают какую-либо функциональность.

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