производительность беззнаковых и знаковых целых чисел

Есть ли прирост / потеря производительности при использовании целых чисел без знака над целыми числами со знаком?

Если да, то распространяется ли это как на краткое, так и на длительное время?


person Flexo    schedule 17.01.2011    source источник
comment
@JeremyP, могу я предположить, что вы говорили правду только о большинстве разработчиков и приложений ....   -  person Brett    schedule 12.11.2012
comment
@Brett: разница между знаковой и беззнаковой арифметикой на большинстве процессоров равна нулю. Разница для разных размеров незначительна, если вы не занимаетесь арифметикой.   -  person JeremyP    schedule 12.11.2012


Ответы (12)


Деление на степени 2 выполняется быстрее с unsigned int, потому что его можно оптимизировать в одну инструкцию сдвига. С signed int обычно требуется больше машинных инструкций, потому что деление округляет до нуля, а смещение вправо округляет вниз. Пример:

int foo(int x, unsigned y)
{
    x /= 8;
    y /= 8;
    return x + y;
}

Вот соответствующая x часть (разделение со знаком):

movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax

А вот соответствующая y часть (беззнаковое деление):

movl 12(%ebp), %edx
shrl $3, %edx
person fredoverflow    schedule 17.01.2011
comment
Это будет работать только в том случае, если делитель - это известная константа времени компиляции, являющаяся степенью двойки, не так ли? - person sharptooth; 17.01.2011
comment
@sharptooth, для деления да. Вероятно, есть и другие приемы битовых манипуляций, которые действительны только для беззнаковых символов. Или подписал. Не думаю, что положительный эффект только в одном направлении. - person AProgrammer; 17.01.2011
comment
Почему трюк нельзя проделать с непостоянными делителями? Первый операнд x86 shrl должен быть литералом? - person Manu343726; 30.12.2013
comment
@ Manu343726 Что делать, если делитель не является степенью двойки? (И даже если бы это было так, вам сначала нужно было бы вычислить двоичный логарифм числа перед сдвигом.) - person fredoverflow; 04.10.2014
comment
В этом масштабе больше инструкций не всегда означает медленное выполнение для современных конвейерных архитектур ЦП. Т.е. Я бы все равно провел измерения, прежде чем делать далеко идущие выводы. - person ulidtko; 13.06.2016

В C ++ (и C) целочисленное переполнение со знаком не определено, тогда как целочисленное переполнение без знака определено для переноса. Обратите внимание, что, например, в gcc вы можете использовать флаг -fwrapv, чтобы определить подписанное переполнение (для переноса).

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

person kbjorklu    schedule 17.01.2011

unsigned приводит к такой же или лучшей производительности, чем signed. Некоторые примеры:

  • Деление на константу, являющуюся степенью двойки (см. Также ответ FredOverflow)
  • Деление на постоянное число (например, мой компилятор реализует деление на 13, используя 2 инструкции asm для беззнаковых и 6 инструкций для подписанных)
  • Проверка четности числа (я понятия не имею, почему мой компилятор MS Visual Studio реализует его с помощью 4 инструкций для signed чисел; gcc делает это с помощью 1 инструкции, как и в случае unsigned)

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

Примечание: некоторые DSP имеют инструкции быстрого умножения для типа signed short; в этом конкретном случае short быстрее, чем int.

Что касается разницы между int и long, я могу только догадываться (я не знаком с 64-битными архитектурами). Конечно, если int и long имеют одинаковый размер (на 32-битных платформах), их производительность также будет одинаковой.


Очень важное дополнение, отмеченное несколькими людьми:

Что действительно важно для большинства приложений, так это объем памяти и используемая пропускная способность. Для больших массивов следует использовать наименьшие необходимые целые числа (short, может быть, даже signed/unsigned char).

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

person anatolyg    schedule 18.01.2011
comment
Я был бы осторожен с утверждением о производительности short по сравнению с int. Хотя арифметика может быть быстрее с использованием int, следует помнить, что целочисленная арифметика редко является узким местом (по крайней мере, на современных процессорах настольных компьютеров), с другой стороны, пропускная способность памяти часто бывает, поэтому для больших наборов данных short может действительно дать значительно лучшую производительность, чем int. Более того, для автовекторизованного кода использование меньших типов данных часто означает, что за один может быть обработано больше элементов данных, поэтому даже арифметическая производительность может увеличиться (хотя маловероятно, учитывая текущее состояние автовекторизаторов). - person Grizzly; 19.01.2011
comment
@Grizzly Я согласен (мое приложение на самом деле требует больших вычислений, поэтому мой опыт работы с short отличается от вашего / любого другого) - person anatolyg; 19.01.2011
comment
@Grizzly Означает ли это также, что в случае краткости кэш ЦП можно использовать более оптимально, поскольку он сможет хранить больше данных? - person martinkunev; 30.06.2014
comment
@martinkunev Совершенно верно! Это может быть единственной причиной использовать short сегодня (при том, что ОЗУ без кеширования фактически бесконечно), и очень веская причина. - person anatolyg; 30.06.2014
comment
@anatolyg RAM может быть фактически бесконечным, но не забывайте, что 32-битные программы по-прежнему превосходят 64-битные программы с большим отрывом, что означает, что независимо от того, сколько оперативной памяти доступно, вы по-прежнему часто ограничены 2 ГБ используемого адреса. -Космос. - person bcrist; 07.09.2014
comment
@JoshParnell Я думаю, вы имеете в виду, что short быстрее, чем int, когда ограничена память. По моему опыту, они имеют такую ​​же производительность на x86, а short медленнее на ARM. - person anatolyg; 10.05.2017
comment
@JoshParnell Что касается MMX - я думаю, вы действительно имеете в виду SSE. MMX - очень старая технология. По моему опыту, MS Visual Studio выполняет векторизацию целочисленных типов с помощью SSE, и ее можно использовать (возможно, более новые версии MSVS используют AVX в дополнение к SSE). - person anatolyg; 10.05.2017
comment
@anatolyg Да, извините, я, конечно, имел в виду, когда память ограничена, ха-ха. Я не знаком со сборкой ARM, но это интересно. Но, как я уже сказал, на x86 мы будем говорить о необнаруживаемых различиях в производительности. Эти различия возникнут в основном из-за обычных затрат на управление типом, ширина которого меньше, чем выравнивание стека. - person Josh Parnell; 10.05.2017
comment
@anatolyg Re: векторизация; Я действительно имел в виду MMX ... У меня сложилось (неверное) впечатление, что это единственное расширение, поддерживающее операции с упакованными целыми числами размером с слово; Я думал, что SSE и не только работают только с DW и QW в отношении данных типа int. Просто вытащил ссылку на i-set для проверки и понял, что ошибаюсь. У каждого из SSE есть новые целочисленные операции W и B для увеличения исходного MMX. Мне действительно следовало бы прочитать этот раздел более внимательно, но, в свою очередь, огромного количества новых векторных инструкций достаточно, чтобы у любого человека заболели мозги. Виноват! Комментарий отозван. - person Josh Parnell; 10.05.2017
comment
согласно этой таблице многие неподписанные инструкции выполняются быстрее, чем подписанные. И многие short инструкции медленнее своих int версий. Некоторые из них могут быть быстрее (например, mul / div). Даже если они имеют одинаковую производительность, вы все равно можете быстрее использовать int в коде более высокого уровня, потому что использование short может привести к некоторому расширению нуля / знака и / или усечению - person phuclv; 09.06.2017

Это будет зависеть от точной реализации. Однако в большинстве случаев разницы не будет. Если вам действительно не все равно, вы должны попробовать все варианты, которые вы рассматриваете, и измерить производительность.

person sharptooth    schedule 17.01.2011
comment
+1 потому что, если вы хотите знать, вам нужно измерить. Очень обидно, что на этот вопрос нужно отвечать почти еженедельно. - person sbi; 17.01.2011

Это в значительной степени зависит от конкретного процессора.

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

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

person Sebastian Paaske Tørholm    schedule 17.01.2011

Разница в производительности между целыми числами со знаком и без знака на самом деле более общая, чем предполагает ответ о принятии. Деление целого числа без знака на любую константу может быть выполнено быстрее, чем деление целого числа со знаком на константу, независимо от того, является ли константа степенью двойки. См. http://ridiculousfish.com/blog/posts/labor-of-division-episode-iii.html

В конце своего поста он включает следующий раздел:

Возникает естественный вопрос, может ли такая же оптимизация улучшить знаковое разделение; К сожалению, похоже, что это не так по двум причинам:

Увеличение дивиденда должно стать увеличением величины, т.е. увеличиваться, если n ›0, уменьшаться, если n‹ 0. Это приводит к дополнительным расходам.

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

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

person David Stone    schedule 04.10.2014

Не только деление на степени 2 выполняется быстрее с типом без знака, деление на любые другие значения также быстрее с типом без знака. Если вы посмотрите таблицы инструкций Agner Fog, вы увидите, что неподписанные деления имеют одинаковые или лучшая производительность, чем подписанные версии

Например с AMD K7

Instruction Operands Ops Latency Reciprocal throughput
DIV r8/m8 32 24 23
DIV r16/m16 47 24 23
DIV r32/m32 79 40 40
IDIV r8 41 17 17
IDIV r16 56 25 25
IDIV r32 88 41 41
IDIV m8 42 17 17
IDIV m16 57 25 25
IDIV m32 89 41 41

То же самое и с Intel Pentium.

Instruction Operands Clock cycles
DIV r8/m8 17
DIV r16/m16 25
DIV r32/m32 41
IDIV r8/m8 22
IDIV r16/m16 30
IDIV r32/m32 46

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

person phuclv    schedule 09.06.2017

Короче, не беспокойтесь перед фактом. Но потрудитесь потом.

Если вы хотите добиться производительности, вам нужно использовать оптимизацию производительности компилятора, что может противоречить здравому смыслу. Следует помнить, что разные компиляторы могут компилировать код по-разному и сами имеют разные виды оптимизации. Если мы говорим о g++ компиляторе и говорим о максимальном уровне его оптимизации с помощью -Ofast или хотя бы флага -O3, по моему опыту, он может компилировать тип long в код с даже лучшей производительностью, чем любой тип unsigned, или даже просто int.

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

Оптимизированная многопоточная программа вычисления линейной алгебры может легко получить> 10-кратную разницу в производительности при точной оптимизации по сравнению с неоптимизированной. Так что это имеет значение.

Вывод оптимизатора во многих случаях противоречит логике. Например, у меня был случай, когда разница между a[x]+=b и a[x]=b изменяла время выполнения программы почти в 2 раза. И нет, a[x]=b не был более быстрым.

Вот, например, NVidia, заявляющая, что для программирования своих графических процессоров :

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

person Íhor Mé    schedule 10.08.2016

IIRC, на x86 подписанный / неподписанный не должен иметь никакого значения. С другой стороны, короткий / длинный - это другая история, поскольку объем данных, которые необходимо переместить в / из ОЗУ, больше для длинных (другие причины могут включать операции приведения, такие как расширение короткого до длинного).

person CAFxX    schedule 17.01.2011
comment
Также имейте в виду, что некоторые компиляторы могут иметь оптимизацию, которая не применяется ко всем целочисленным типам. Например. по крайней мере, старые компиляторы Intel не могли применять автовекторизацию, если счетчик цикла был чем-то другим, кроме подписанного int. - person CAFxX; 17.01.2011
comment
это не имеет значения на уровне инструкции, но на уровне C ++ это имеет значение - person phuclv; 09.06.2017
comment
@ LưuVĩnhPhúc, ты говоришь о подписанном переполнении, являющемся UB? если да, то единственный случай, о котором я знаю и который имеет значение, - это случай, когда оптимизирующим компиляторам труднее рассуждать о беззнаковых промежуточных числах, используемых в качестве счетчиков циклов / переменных индукции (и это было охвачено моим комментарием непосредственно над вашим) - person CAFxX; 11.06.2017
comment
Нет, есть и другие случаи, когда значение имеет значение. Вы читали другие ответы? - person phuclv; 11.06.2017
comment
Я сделал. А ты? Большинство из них говорят, что нет больших различий, за исключением делений с постоянной времени компиляции и переменных индукции цикла (которые я упомянул в своем комментарии). Даже в вашем вы вроде указываете, что в более новых процессорах разница не очень большая (посмотрите, например, таблицы Sandy Bridge) - person CAFxX; 13.06.2017

Целые числа со знаком и без знака всегда будут работать как отдельные тактовые инструкции и иметь одинаковую производительность чтения-записи, но в соответствии с Dr Andrei Alexandrescu беззнаковый предпочтительнее подписанного. Причина этого в том, что вы можете уместить вдвое большее количество чисел в одно и то же количество битов, потому что вы не тратите зря знаковый бит и будете использовать меньше инструкций, проверяющих отрицательные числа, что приведет к увеличению производительности из-за уменьшенного ПЗУ. По моему опыту работы с Kabuki VM, которая отличается сверхвысокой производительностью Script Реализация, редко когда действительно требуется номер со знаком, когда работа с памятью. Я провел несколько лет, выполняя арифметические операции с указателями со знаковыми и беззнаковыми числами, и я не нашел преимуществ для подписанных, когда не нужен знаковый бит.

Знаковые значения могут быть предпочтительнее при использовании сдвига битов для умножения и деления степеней двойки, потому что вы можете выполнять деление на отрицательные степени двойки с целыми числами дополнения до 2 со знаком. Просмотрите несколько других видеороликов YouTube от Андрея, чтобы узнать о дополнительных методах оптимизации. Вы также можете найти полезную информацию в моей статье о самый быстрый в мире алгоритм преобразования целого числа в строку.

person Community    schedule 05.02.2018

Традиционно int - это собственный целочисленный формат целевой аппаратной платформы. Любой другой целочисленный тип может повлечь снижение производительности.

РЕДАКТИРОВАТЬ:

В современных системах все немного иначе:

  • int на самом деле может быть 32-битным в 64-битных системах по соображениям совместимости. Я считаю, что это происходит в системах Windows.

  • Современные компиляторы могут неявно использовать int при выполнении вычислений для более коротких типов в некоторых случаях.

person thkala    schedule 17.01.2011
comment
да, традиционно ;-) в текущих 64-битных системах int по-прежнему имеет ширину 32 бита, но 64-битные типы (long или long long, в зависимости от ОС) должны быть как минимум такими же быстрыми. - person Philipp; 17.01.2011
comment
int всегда имеет ширину 32 бита во всех известных мне системах (Windows, Linux, Mac OS X, независимо от того, является ли процессор 64-битным или нет). Это тип long, который отличается: 32 бита в Windows, но одно слово в Linux и OS X. - person Philipp; 17.01.2011
comment
@Philipp, но int не обязательно должен быть всегда шириной 32 бита. - person mercury0114; 15.11.2020

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

person Dr. Debasish Jana    schedule 04.10.2014