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


79

Чи є приріст / втрата продуктивності при використанні цілих чисел без знака над цілими числами зі знаком?

Якщо так, чи це теж коротко і довго?


9
Мало того, що вам потрібно про це піклуватися.
JeremyP

17
@JeremyP, чи можу я припустити, що ти говорив правду лише для більшості розробників та додатків ....
Бретт,

1
@Brett: Різниця між арифметикою зі знаком та без знака на більшості процесорів дорівнює нулю. Різниця для різних розмірів незначна, якщо ви не робите багато арифметики.
JeremyP

Відповіді:


108

Ділення на потужності 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

11
Це буде працювати лише в тому випадку, коли дільник - це відома константа часу, що дорівнює часу, що має ступінь двох, чи не так?
гострий зуб

1
@sharptooth, для поділу, так. Ймовірно, існують інші хитрощі з маніпуляціями з бітами, які дійсні лише для непідписаних. Або підписали. Я не думаю, що позитивний ефект полягає лише в одному напрямку.
Програміст,

Чому фокус не можна зробити для непостійних дільників? Перший операнд x86 shrlповинен бути буквальним?
Manu343726

@ Manu343726 Що робити, якщо дільник не має степеня 2? (І навіть якби це було так, вам би спочатку довелося обчислити двійковий логарифм числа перед зсувом.)
fredoverflow

1
У цьому масштабі більше інструкцій не завжди означає повільніший час роботи для сучасних конвеєрних архітектур ЦП. Тобто я б все-таки зробив вимірювання, перш ніж робити нестримні висновки.
ulidtko

49

У C ++ (і C) переповнене ціле число зі знаком невизначене, тоді як переповнення беззнакового цілого числа визначено для обгортання. Зверніть увагу, що, наприклад, у gcc, ви можете використовувати прапор -fwrapv, щоб визначити підписане переповнення (для обгортання).

Невизначене підписане ціле число переповнення дозволяє компілятору припустити, що переповнення не відбувається, що може створити можливості оптимізації. Див., Наприклад, цю публікацію в блозі для обговорення.


20

unsignedпризводить до однакових або кращих показників, ніж signed. Кілька прикладів:

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

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

Примітка: деякі ЦСП мають інструкції щодо швидкого множення для signed shortтипу; в цьому конкретному випадку shortшвидше, ніж int.

Щодо різниці між intі long, я можу лише здогадуватися (я не знайомий з 64-розрядними архітектурами). Звичайно, якщо intі longмають однакові розміри (на 32-розрядних платформах), їх продуктивність також однакова.


Дуже важливе доповнення, на яке звернули увагу кілька людей:

Що насправді важливо для більшості програм, це розмір пам'яті та використовувана пропускна здатність. Ви повинні використовувати найменші необхідні цілі числа ( short, можливо, навіть signed/unsigned char) для великих масивів.

Це дасть кращу продуктивність, але коефіцієнт посилення нелінійний (тобто не в 2 чи 4 рази) і дещо непередбачуваний - це залежить від розміру кешу та взаємозв'язку між обчисленнями та передачею пам'яті у вашому додатку.


8
Я був би обережний із твердженням про ефективність шорта порівняно з int. Хоча арифметика "може" бути швидшою, використовуючи int, слід пам'ятати, що цілочисельна арифметика рідко є вузьким місцем (принаймні на сучасному процесорі робочого столу), пропускна здатність пам'яті, навпаки, часто є, тому для великих наборів даних короткі можуть насправді дати значно кращу продуктивність, ніж інт. Крім того, для автовекторизованого коду з використанням менших типів даних часто означає, що більше елементів даних може бути оброблено за один, тому навіть арифметична продуктивність може зрости (хоча малоймовірна, враховуючи поточний стан автовекторизаторів).
Грізлі

1
@Grizzly Я погоджуюсь (мій додаток насправді важкий для обчислень, тому мій досвід роботи shortвідрізняється від вашого / будь-кого іншого)
anatolyg

2
@martinkunev Абсолютно! Це може бути єдиною причиною для використання shortсьогодні (а не кеш-пам’ять фактично нескінченна), і дуже вагомою причиною.
anatolyg

1
Оперативна пам'ять @anatolyg може бути фактично нескінченною, але не забувайте, що 32-розрядні програми все ще значно перевершують 64-розрядні, що означає, що незалежно від того, скільки оперативної пам'яті доступно, ви все ще часто обмежуєтеся 2 ГБ корисної адреси -простір.
bcrist

1
@JoshParnell Думаю, ви маєте на увазі shortшвидше, ніж intколи пам'ять обмежена . З мого досвіду, вони мають однакову продуктивність на x86 і shortповільніше на ARM.
anatolyg

17

Це буде залежати від точної реалізації. У більшості випадків різниці не буде. Якщо вам дійсно цікаво, вам доведеться спробувати всі варіанти, які ви розглядаєте, і виміряти ефективність.


21
+1для "якщо ви хочете знати, вам потрібно виміряти". Дуже прикро, що на це потрібно відповідати майже щотижня.
sbi

9

Це в значній мірі залежить від конкретного процесора.

У більшості процесорів є інструкції як для підписаної, так і безпідписаної арифметики, тож різниця між використанням підписаних та беззнакових цілих чисел зводиться до того, яку використовує компілятор.

Якщо будь-який із двох швидший, це повністю специфічний процесор, і, швидше за все, різниця незначна, якщо вона взагалі існує.


7

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

В кінці своєї посади він включає наступний розділ:

Закономірним є питання, чи може та сама оптимізація покращити підписаний підрозділ; на жаль, здається, що ні, з двох причин:

Приріст дивіденду повинен стати збільшенням величини, тобто збільшенням, якщо n> 0, зменшенням, якщо n <0. Це створює додаткові витрати.

Штраф за дільник, що не співпрацює, становить приблизно приблизно половину менше у підписаному відділі, залишаючи менше вікно для поліпшень.

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


4

Не лише ділення на ступені 2 швидше з типом без знака, ділення на будь-які інші значення також швидше з типом без знака. Якщо ви подивитесь на таблиці інструкцій Агнера Фога то побачите, що непідписані підрозділи мають однакову або кращу продуктивність, ніж підписані версії

Наприклад, з AMD K7

Інструкція Операнди Опс Латентність Взаємна пропускна здатність
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

Інструкція Операнди Цикли годин
DIV r8 / m8 17
DIV r16 / m16 25
DIV r32 / m32 41
IDIV r8 / m8 22
IDIV r16 / m16 30
IDIV r32 / m32 46

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


3

Одним словом, не турбуйтеся перед фактом. Але не турбуйся після цього.

Якщо ви хочете мати продуктивність, вам доведеться використовувати оптимізацію продуктивності компілятора, яка може працювати проти здорового глузду. Запам’ятайте одне, що різні компілятори можуть компілювати код по-різному, і вони самі мають різні види оптимізації. Якщо ми говоримо про g++компілятор і говоримо про максимізацію рівня його оптимізації за допомогою -Ofastабо, принаймні, -O3прапора, на мій досвід він може компілювати longтип у код з навіть кращою продуктивністю, ніж будь-який unsignedтип, або навіть простоint .

Це з мого власного досвіду, і я рекомендую вам спочатку написати свою повну програму і піклуватися про такі речі лише після цього, коли у вас є фактичний код, і ви можете скомпілювати його з оптимізаціями, щоб спробувати вибрати типи, які насправді виконуються найкраще. Це також хороша дуже загальна порада щодо оптимізації коду для продуктивності, спочатку пишіть швидко, спробуйте компілювати з оптимізаціями, налаштуйте речі, щоб побачити, що найкраще працює. Вам також слід спробувати використовувати різні компілятори для компіляції вашої програми та вибрати той, який виводить найбільш ефективний машинний код.

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

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

Ось, наприклад, NVidia, яка заявляє, що для програмування своїх графічних процесорів:

Примітка: Як уже було рекомендовано найкращу практику, підписана арифметика повинна мати перевагу над непідписаною арифметикою, де це можливо, для кращої пропускної здатності на SMM. Стандарт мови C встановлює більше обмежень щодо поведінки переповнення для математики без підпису, обмежуючи можливості оптимізації компілятора.


1

IIRC, на x86 підписаний / непідписаний не повинен мати жодної різниці. З іншого боку, короткий / довгий - це інша історія, оскільки обсяг даних, який потрібно переміщати в / з оперативної пам’яті, на тривалий час більший (інші причини можуть включати операції приведення, такі як розширення короткого до довгого).


1
Також майте на увазі, що певні компілятори можуть мати оптимізації, які не стосуються всіх цілих типів. Наприклад, принаймні старі компілятори Intel не могли застосувати автовекторизацію, якщо лічильник циклу for був чимсь іншим, ніж підписаним int.
CAFxX,

це не має значення на рівні інструкцій, але з рівня С ++ це має значення
phuclv

@ LưuVĩnhPhúc Ви говорите про те, що переписаний переповнення є UB? якщо так, то єдиний випадок, який мені відомий, - це той випадок, коли оптимізаторам компіляторів важче міркувати про беззнакові інтергери, що використовуються як лічильники циклів / індукційні змінні (і це було висвітлено в моєму коментарі безпосередньо над вашим)
CAFxX

Ні, є різні інші випадки, коли вивіска має значення. Чи читали ви інші відповіді?
phuclv

Я зробила. Ти що? Більшість з них кажуть, що великих відмінностей не існує, якщо не робити постійних поділів на час компіляції та змінних індукції циклу (про що я згадав у своєму коментарі). Навіть у вашій справі ви зазначаєте , що в новіших процесорах різниця не дуже велика (перевірте, наприклад, таблиці Sandy Bridge)
CAFxX,

1

Підписані та беззнакові цілі числа завжди працюватимуть як інструкції з одним годинником і матимуть однакову продуктивність читання-запису, але, за словами д-ра Андрія Александреску, непідписаним надається перевага перед підписаним. Причиною цього є те, що ви можете вмістити подвійну кількість чисел в одній і тій самій кількості бітів, оскільки ви не витрачаєте знаковий біт, і ви будете використовувати менше інструкцій, перевіряючи наявність від’ємних чисел, що призводить до збільшення продуктивності від зменшеного ПЗУ. На моєму досвіді з VM Kabuki , який має надвисокопродуктивний сценарійВпровадження, рідко коли вам дійсно потрібен номер із підписом при роботі з пам'яттю. Я витрачаю майські роки на арифметику покажчиків із підписаними та беззнаковими числами, і я не знайшов вигоди для підписаного, коли не потрібен біт знаку.

Де підписом може бути кращим, коли використовується зсув бітів для виконання множення та ділення степенів 2, оскільки ви можете виконувати негативні степені 2 ділення із підписаними цілими числами доповнення 2. Будь ласка, перегляньте ще кілька відеороликів від Андрія для отримання додаткових методів оптимізації. Ви також можете знайти хорошу інформацію в моїй статті про найшвидший у світі алгоритм перетворення цілого числа в рядок .


0

Традиційно intце власний цілочисельний формат цільової апаратної платформи. Будь-який інший цілий тип може спричинити штрафні санкції.

РЕДАГУВАТИ:

У сучасних системах справа дещо інша:

  • intнасправді може бути 32-розрядною в 64-розрядних системах з міркувань сумісності. Я вважаю, що це відбувається в системах Windows.

  • Сучасні компілятори можуть неявно використовувати intпід час обчислення для коротших типів в деяких випадках.


так, традиційно ;-) у сучасних 64-розрядних системах intвсе ще має ширину 32 біти, але 64-бітні типи ( longабо long long, залежно від ОС) повинні бути принаймні такими ж швидкими.
Філіпп

1
intзавжди має ширину 32 біти у всіх відомих мені системах (Windows, Linux, Mac OS X, незалежно від того, чи є процесор 64-розрядним чи ні). Це longтип, який відрізняється: 32 біти в Windows, але одне слово в Linux та OS X.
Філіп

@Philipp , але intне повинен бути завжди 32 біта.
mercury0114

0

Непідписане ціле число вигідне тим, що ви зберігаєте і обробляєте як потік бітів, я маю на увазі просто дані без знака, тому множення, розробка стає простішим (швидшим) за допомогою операцій зсуву бітів

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.