Какова разница в производительности между целыми числами без знака и знака? [закрыто]

Я знаю об ударе производительности при микшировании подписанных ints с помощью float.

Не стоит ли смешивать неподписанные int с поплавками?

Есть ли какой-либо удар при смешении подписи /без знака без поплавков?

Влияют ли различные размеры (u32, u16, u8, i32, i16, i8) на производительность? На каких платформах?

41 голос | спросил Luis 31 Jpm1000000pmMon, 31 Jan 2011 18:10:19 +030011 2011, 18:10:19

7 ответов


36

Большой штраф от микширования ints (любого вида) и floats происходит потому, что они находятся в разных наборах регистров. Чтобы перейти от одного набора регистров к другому, вам нужно записать значение в память и прочитать его обратно, что приведет к aa load-hit-store stall.

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

ответил celion 31 Jpm1000000pmMon, 31 Jan 2011 19:48:00 +030011 2011, 19:48:00
12

Я подозреваю, что информация о Xbox 360 и PS3 специально будет стоять за лицензированными стенками только для разработчиков, как и большинство низкоуровневых деталей. Однако мы можем построить эквивалентную программу x86 и разобрать ее, чтобы получить общую идею.

Во-первых, давайте посмотрим, какие неподписанные затраты на расширение:

unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;

Соответствующая часть разбирает (используя GCC 4.4.5):

    z = x;
  27:   0f b6 45 ff             movzbl -0x1(%ebp),%eax
  2b:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  2e:   8b 45 f8                mov    -0x8(%ebp),%eax
  31:   89 45 f4                mov    %eax,-0xc(%ebp)

Итак, в основном то же самое - в одном случае мы перемещаем байт, а в другом мы перемещаем слово. Далее:

signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;

Входит в:

   z = x;
  11:   0f be 45 ff             movsbl -0x1(%ebp),%eax
  15:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  18:   8b 45 f8                mov    -0x8(%ebp),%eax
  1b:   89 45 f4                mov    %eax,-0xc(%ebp)

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

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

Это не переполнение стека, поэтому я надеюсь, что никто здесь не потребует, чтобы микрооптимизация не имела значения. Игры часто работают с очень крупными и очень числовыми данными, поэтому тщательное внимание к разветвлению, кастам, планированию, выравниванию структуры и т. Д. Может дать очень важные улучшения. Любой, кто потратил много времени на оптимизацию кода PPC, вероятно, имеет, по крайней мере, одну историю ужасов о загрузочных сайтах. Но в этом случае это действительно не имеет значения. Размер хранилища вашего целочисленного типа не влияет на производительность, если он выровнен и помещен в регистр.

ответил celion 31 Jpm1000000pmMon, 31 Jan 2011 19:48:00 +030011 2011, 19:48:00
4

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

unsigned foo(unsigned a) { return a / 1024U; }

будет оптимизирован для:

unsigned foo(unsigned a) { return a >> 10; }

Но ...

int foo(int a) { return a / 1024; }

будет оптимизирован для:

int foo(int a) {
  return (a + 1023 * (a < 0)) >> 10;
}

или в системах, где ветвление дешево,

int foo(int a) {
  if (a >= 0) return a >> 10;
  else return (a + 1023) >> 10;
}

То же самое относится к модулю. Это также справедливо для не-сил-2 (но пример более сложный). Если ваша архитектура не имеет аппаратного разделения (например, большинства ARM), то беззнаковые деления не const const также выполняются быстрее.

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

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

ответил John Ripley 1 FebruaryEurope/MoscowbTue, 01 Feb 2011 06:25:55 +0300000000amTue, 01 Feb 2011 06:25:55 +030011 2011, 06:25:55
3

Операции с подписанным или unsigned int имеют одинаковую стоимость для текущих процессоров (x86_64, x86, powerpc, arm). На процессоре 32 бит, u32, u16, u8 s32, s16, s8 должны быть одинаковыми. У вас может быть штраф с плохим выравниванием.

Но преобразование int в float или float в int является дорогостоящей операцией. Вы можете легко найти оптимизированную реализацию (SSE2, Neon ...).

Наиболее важным моментом является, вероятно, доступ к памяти. Если ваши данные не вписываются в кеш L1 /L2, вы потеряете больше циклов, чем преобразование.

ответил Ellis 31 Jpm1000000pmMon, 31 Jan 2011 20:44:57 +030011 2011, 20:44:57
2

Джон Перди говорит выше (я не могу комментировать), что unsigned может быть медленнее, потому что он не может переполняться. Я не согласен, беззнаковая арифметика - простая арифметика moular по модулю 2 на число бит в слове. Подписанные операции в принципе могут переполняться, но обычно они отключены.

Иногда вы можете делать умные (но не очень читаемые вещи), например, упаковывать два или более элемента данных в int и получать несколько операций на инструкцию (карманная арифметика). Но ты должен понять, что делаешь. Конечно, MMX позволяет вам делать это естественно. Но иногда с использованием наибольшего размера слова с поддержкой HW и ручной упаковки данные дают вам самую быструю реализацию.

Будьте осторожны с выравниванием данных. В большинстве реализаций HW нестационарные нагрузки и магазины работают медленнее. Естественное выравнивание означает, что, скажем, 4-байтное слово, адрес кратен четырем, а восемь байтовых слов должны быть кратными восьми байтам. Это переносится в SSE (128 бит поддерживает 16-битное выравнивание). В ближайшее время AVX расширит размер этих «векторных» регистров до 256 бит, а затем 512 бит. И выровненные нагрузки /хранилища будут быстрее, чем неуравновешенные. Для HW geeks операция с неосновной памятью может охватывать такие вещи, как кешлайн и даже границы страниц, для которых HW должен быть осторожным.

ответил 1 FebruaryEurope/MoscowbTue, 01 Feb 2011 01:25:30 +0300000000amTue, 01 Feb 2011 01:25:30 +030011 2011, 01:25:30
1

Немного лучше использовать знаковые целые числа для индексов цикла, поскольку подписанное переполнение не определено в C, поэтому компилятор предположит, что у таких циклов меньше угловых случаев. Это контролируется gcc -fstrict-overflow (включен по умолчанию), и эффект, вероятно, трудно заметить без чтения сборки.

Кроме того, x86 работает лучше, если вы не смешиваете типы, потому что он может использовать операнды памяти. Если ему нужно преобразовать типы (знаковые или нулевые расширения), что означает явную загрузку и использование регистра.

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

ответил alex strange 1 FebruaryEurope/MoscowbTue, 01 Feb 2011 06:10:09 +0300000000amTue, 01 Feb 2011 06:10:09 +030011 2011, 06:10:09
0

Как указывает Целион, накладные расходы на конвертацию между int и float в значительной степени связаны с копированием и преобразованием значений между регистрами. Единственные издержки неподписанных ints сами по себе исходят из гарантированного поведения wraparound, что требует определенной проверки переполнения в скомпилированном коде.

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

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

ответил Jon Purdy 31 Jpm1000000pmMon, 31 Jan 2011 20:52:15 +030011 2011, 20:52:15

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132