В С# есть ли значительная разница в производительности при использовании UInt32 и Int32?

StackOverflow https://stackoverflow.com/questions/306602

  •  08-07-2019
  •  | 
  •  

Вопрос

Я переношу существующее приложение на C# и хочу повысить производительность, где это возможно.Многие существующие счетчики циклов и ссылки на массивы определяются как System.UInt32 вместо Int32, который я бы использовал.

Есть ли значительная разница в производительности при использовании UInt32 и Int32?

Это было полезно?

Решение

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

Большая разница заключается в соответствии CLS, поскольку неподписанные типы не соответствуют CLS, так как не все языки поддерживают их.

Другие советы

Короткий ответ - нет.Любое влияние на производительность будет незначительным».

Правильный ответ: «Это зависит».

Лучше спросить: «Должен ли я использовать uint, если я уверен, что знак мне не нужен?»

Причина, по которой вы не можете однозначно ответить «да» или «нет» в отношении производительности, заключается в том, что целевая платформа в конечном итоге будет определять производительность.То есть производительность определяется процессором, который будет выполнять код, и доступными инструкциями.Ваш код .NET компилируется до Средний язык (IL или байт-код).Эти инструкции затем компилируются на целевую платформу с помощью Вовремя (JIT)-компилятор как часть общеязыковая среда выполнения (КЛР).Вы не можете контролировать или предсказывать, какой код будет создан для каждого пользователя.

Таким образом, зная, что аппаратное обеспечение является окончательным арбитром производительности, возникает вопрос: «Насколько отличается код, генерируемый .NET для знакового и беззнакового целого числа?» и «Влияет ли эта разница на мое приложение и целевые платформы?».

Лучший способ ответить на эти вопросы — провести тест.

class Program
{
  static void Main(string[] args)
  {
    const int iterations = 100;
    Console.WriteLine($"Signed:      {Iterate(TestSigned, iterations)}");
    Console.WriteLine($"Unsigned:    {Iterate(TestUnsigned, iterations)}");
    Console.Read();
  }

  private static void TestUnsigned()
  {
    uint accumulator = 0;
    var max = (uint)Int32.MaxValue;
    for (uint i = 0; i < max; i++) ++accumulator;
  }

  static void TestSigned()
  {
    int accumulator = 0;
    var max = Int32.MaxValue;
    for (int i = 0; i < max; i++) ++accumulator;
  }

  static TimeSpan Iterate(Action action, int count)
  {
    var elapsed = TimeSpan.Zero;
    for (int i = 0; i < count; i++)
      elapsed += Time(action);
    return new TimeSpan(elapsed.Ticks / count);
  }

  static TimeSpan Time(Action action)
  {
    var sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.Elapsed;
  }
}

Два метода испытаний, Тестподписанный и TestUnsigned, каждый выполняет ~2 миллиона итераций простого приращения для знакового и беззнакового целого числа соответственно.Тестовый код выполняет 100 итераций каждого теста и усредняет результаты.Это должно устранить любые потенциальные несоответствия.Результаты на моем i7-5960X, скомпилированном для x64, были:

Signed:      00:00:00.5066966

Unsigned:    00:00:00.5052279

Эти результаты почти идентичны, но чтобы получить окончательный ответ, нам действительно нужно посмотреть на байт-код, сгенерированный для программы.Мы можем использовать ИЛДАСМ как часть .NET SDK для проверки кода в сборке, созданной компилятором.

Bytecode

Здесь мы видим, что компилятор C# отдает предпочтение целым числам со знаком и фактически выполняет большинство операций как целые числа со знаком и только когда-либо обрабатывает значение в памяти как беззнаковое при сравнении для ветки (т.н. прыжок или if).Несмотря на то, что мы используем целое число без знака как для итератора, так и для аккумулятора в TestUnsigned, код практически идентичен коду Тестподписанный метод, за исключением одной инструкции: IL_0016.Быстрый взгляд на ECMA-спецификация описывает разницу:

blt.un.s :Переход к цели, если меньше (беззнаковый или неупорядоченный), короткая форма.

блт.с:Переход к цели, если меньше, короткая форма.

Поскольку инструкция настолько распространена, можно с уверенностью предположить, что большинство современных мощных процессоров будут иметь аппаратные инструкции для обеих операций и, скорее всего, будут выполняться за одинаковое количество циклов, но это не гарантировано.Процессор с низким энергопотреблением может иметь меньше инструкций и не иметь ветки для unsigned int.В этом случае JIT-компилятору, возможно, придется выдать несколько аппаратных инструкций (например, сначала преобразование, затем ветвь), чтобы выполнить blt.un.s Инструкция ИЛ.Даже если это так, эти дополнительные инструкции будут базовыми и, вероятно, не окажут существенного влияния на производительность.

Итак, с точки зрения производительности, развернутый ответ таков: «Маловероятно, что вообще будет разница в производительности между использованием знакового и беззнакового целого числа.Если и есть разница, то она, скорее всего, будет незначительной».

Итак, если производительность идентична, следующий логический вопрос: «Должен ли я использовать беззнаковое значение, если я уверен, что это не так?» нуждаться знак?"

Здесь следует учитывать две вещи:во-первых, беззнаковые целые числа НЕ являются CLS-совместимый, Это означает, что вы можете столкнуться с проблемами, если вы предоставляете беззнаковое целое число как часть API, который будет использовать другая программа (например, если вы распространяете библиотеку многократного использования).Во-вторых, большинство операций в .NET, включая сигнатуры методов, предоставляемые BCL (по вышеуказанной причине), используют целое число со знаком.Так что, если вы планируете фактически использовать беззнаковое целое число, вам, скорее всего, придется его довольно часто приводить.Это окажет очень небольшое влияние на производительность и сделает ваш код немного более запутанным.В конце концов, возможно, оно того не стоит.

TLDR; Когда я работал с C++, я бы сказал: «Используйте то, что наиболее подходит, а все остальное пусть разберется с компилятором». C# не так прост, поэтому я бы сказал для .NET следующее:На самом деле нет никакой разницы в производительности между целым числом со знаком и без знака на x86/x64, но для большинства операций требуется целое число со знаком, поэтому, если вам действительно НЕ НУЖНО ограничивать значения ТОЛЬКО положительными или вам действительно НУЖЕН дополнительный диапазон, который съедает знаковый бит, придерживайтесь со знаком целого числа.В конце концов ваш код станет чище.

Я не проводил каких-либо исследований по этому вопросу в .NET, но в старые времена Win32 / C ++, если вы хотели создать " подписанное int " до «подписанного длинного», процессор должен был запустить операцию, чтобы расширить знак. Чтобы создать "unsigned int" до "unsigned long", он просто содержал ноль в верхних байтах. Экономия была порядка пары тактов (т. Е. Вам пришлось бы делать это миллиарды раз, чтобы иметь даже ощутимую разницу)

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

Эти типы оптимизаций редко стоят усилий. Используйте тип данных, который наиболее подходит для задачи, и оставьте это на этом. Если эта вещь настолько сильно затрагивает базу данных, вы, вероятно, найдете дюжину изменений в дизайне БД, синтаксисе запросов или стратегии индексирования, которые сместят оптимизацию кода в C # на несколько сотен порядков.

Он будет выделять одинаковое количество памяти в любом случае (хотя он может хранить большее значение, так как он не экономит место для знака). Поэтому я сомневаюсь, что вы увидите разницу в «производительности», если только вы не используете большие / отрицательные значения, которые приведут к взрыву одного или другого параметра.

это на самом деле не связано с производительностью, скорее с требованиями к счетчику циклов.

Возможно, было выполнено много итераций

        Console.WriteLine(Int32.MaxValue);      // Max interation 2147483647
        Console.WriteLine(UInt32.MaxValue);     // Max interation 4294967295

Неподписанный int может быть там по причине.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top