интервал, короткий, производительность байта в последовательном для циклов

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

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

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 из-за диапазона типа значения байта. Также это - действие любопытства не производственная микрооптимизация кода.

14
задан Community 23 May 2017 в 12:02
поделиться

5 ответов

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

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

В-третьих, байт имеет диапазон до 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 миллионов итераций) и выполняет очень простую операцию внутри цикла (увеличивает переменную на единицу). Результаты для меня были такими:

Byte Loop: Elapsed Time = 00: 00: 03.8949910
Short Loop: Elapsed Time = 00: 00: 03.9098782
Int Loop: Истекшее время = 00: 00: 03.2986990

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

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

41
ответ дан 1 December 2019 в 05:59
поделиться

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

0
ответ дан 1 December 2019 в 05:59
поделиться

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

Вывод из тестовой среды Ааронотса

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

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

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

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

ничего в нем

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

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

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

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

Сборка отладки Использование настроек отладки в VS дает стабильные результаты, такие как у Aaronaught.

2
ответ дан 1 December 2019 в 05:59
поделиться

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

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

0
ответ дан 1 December 2019 в 05:59
поделиться

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

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

  • Использование DateTime.Now - плохой способ измерения времени. Используйте System.Diagnostics.Stopwatch вместо
  • После того, как вы избавитесь от консоли .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

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

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

12
ответ дан 1 December 2019 в 05:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: