Чи порівняння 1 <10 менш дороге, ніж 1 <1000000?


65

Я щойно використав ~ 1 мільярд як підрахунок для z-indexCSS і думав про порівняння, які повинні тривати. Чи є різниця в продуктивності на рівні АЛУ у порівнянні між дуже великою кількістю та дуже маленькою?

Наприклад, чи буде один з цих двох фрагментів дорожчим за інший?

snippet 1

for (int i = 0; i < 10000000; i++){
    if (i < 10000000000000) {
        //do nothing
    }
}

snippet 2

for (int i = 0; i < 10000000; i++){
    if (i < 1000) {
        //do nothing
    }
}


12
ОП не запитує, скільки часу займе розгалуження. Зрозуміло, що приклад призначений для того, щоб в обох фрагментах проходив рівно однаковий час. Питання полягає в тому, чи CMPбуде інструкція на окремих машинах повільнішою, якщо вона iбуде більшою.
Кіліан Фот

18
Оскільки це робиться в CSS, перетворення рядка в ціле число, ймовірно, буде домінувати над самою операцією порівняння з точки зору витраченого часу на виконання.

58
Якщо вам потрібно було використовувати 1000000000 як z-індекс у файлі CSS, ви зробили щось не так.
Бергі

6
Для CSS накладні витрати з перетворення тексту в ціле число залежатимуть від кількості перетворених цифр (де 6-значний номер типу 1000000 може бути приблизно в 6 разів дорожчий за одноцифрове число, як 1); і цей наклад може бути на порядок більшим, ніж накладні витрати на цілі порівняння.
Брендан

Відповіді:


82

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

Найкращий спосіб відповісти на питання точно - це зібрати свій код на збірку та ознайомитися з документацією цільового процесора щодо створених інструкцій. Для поточних процесорів Intel це Посібник для розробників програмного забезпечення для архітектури Intel 64 та IA-32 .

Опис інструкції CMP("порівняти") є в томі 2A, на сторінці 3-126 або на сторінці 618 PDF, і описує її роботу як:

temp ← SRC1 − SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)

Це означає, що другий операнд при необхідності розширюється знаками, віднімається від першого операнда і результат розміщується у тимчасовій області процесора. Потім прапори статусу встановлюються так само, як і для інструкції SUB("віднімання") (стор. 1492 PDF).

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


5
Що робити, якщо число стає занадто великим для 32-розрядної арифметики? Чи не було б воно тоді розділене на повільніші обчислення?
Falco

3
@Falco Не на процесорі зі 64-бітним ALU (що майже всі вони, за винятком вбудованого простору в ці дні.)
reirab

8
@Falco: Так, але оскільки питання стосується продуктивності ALU, слід зазначити, що значення відповідають розміру слова CPU або можливостям будь-яких інструкцій SIMD, які він може мати. Операція на більшій кількості, ніж це, доведеться реалізовувати за допомогою декількох інструкцій поза процесором. Це було дуже часто 30 років тому, коли ви просто мали 8- або 16-бітні регістри, з якими працювали.
Blrfl

6
@Falco Як це вимагатиме налагодження? Це не помилка; це робити трохи повільніше 64-розрядних операційних систем на процесорі, який не підтримує 64-розрядних операційних систем. Запевняти, що ніколи не слід використовувати число вище 2 ^ 31-1, здається трохи смішним.
reirab

2
@Falco Сказавши це, чи використовують системи візуалізації в браузерах навіть цілі числа для представлення z-індексів? Більшість двигунів рендерингу, які мені знайомі з використанням одноточних плавців для всього (до останнього етапу растеризації), але я не дуже вивчив двигуни рендерингу браузера.
reirab

25

Чи є різниця в продуктивності на рівні АЛУ у порівнянні між дуже великою кількістю та дуже маленькою?

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

Тим не менш, ваш конкретний компілятор, можливо, проводить деякі розумні оптимізації, про які ви не знаєте. Спосіб, який ви дізнаєтесь, - це вимірювання. Запустіть профайлер на свій код; подивіться, які порівняння займають найдовше. Або просто запустити та зупинити таймер.


Слід зазначити, що запропоновані цифри у запитанні мають різний числовий тип у типовому 32-бітному цілому типі ...
Falco

19

Багато процесорів мають "невеликі" інструкції, які можуть виконувати арифметичні операції, включаючи порівняння, на певних одразу визначених операндах. Операнди, відмінні від спеціальних значень, повинні або використовувати більший формат інструкцій, або, в деяких випадках, використовувати інструкцію "значення завантаження з пам'яті". Наприклад, у наборі інструкцій ARM Cortex-M3 існує принаймні п’ять способів порівняння значення з константою:

    cmp r0,#1      ; One-word instruction, limited to values 0-255

    cmp r0,#1000   ; Two-word instruction, limited to values 0-255 times a power of 2

    cmn r0,#1000   ; Equivalent to comparing value with -1000
                   ; Two-word instruction, limited to values 0-255 times a power of 2

    mov r1,#30000  ; Two words; can handle any value 0-65535
    cmp r0,r1      ; Could use cmn to compare to values -1 to -65535

    ldr r1,[constant1000000] ; One or two words, based upon how nearby the constant is
    cmp r0,r1
    ...

constant1000000:
    dd  1000000

Перша форма найменша; друга і третя форма можуть або не можуть виконуватися так швидко, залежно від швидкості пам’яті, з якої отримано код. Форма четвертої форми майже напевно буде повільнішою, ніж перші три, а п'ята - ще повільнішою, але останню можна використовувати з будь-яким 32-бітовим значенням.

На старих процесорах x86 інструкції порівняння коротких форм виконувались б швидше, ніж довгі форми, але багато новіших процесорів перетворюють як довгі, так і короткі форми в одне і те ж представлення під час їх першого отримання та зберігають це рівномірне представлення в кеші. Таким чином, хоча вбудовані контролери (наприклад, такі, що знаходяться на багатьох мобільних платформах) матимуть різницю в швидкості, багато комп'ютерів на базі x86 не будуть.

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


На MIPS ви можете мати лише 16-бітні одразу, тому, безумовно, порівняння з 1 буде коротшим і (можливо) швидшим, ніж 1000000. Можливо, те саме, що і для Sparc та PowerPC. І я думаю, що я читав з деяких джерел, що Intel також оптимізує операції над невеликими
прямими

@ LưuVĩnhPhúc: Реєстр можна завантажити перед циклом. Після цього фактичним порівнянням буде однакова кількість інструкцій в будь-якому випадку.
cHao

Оскільки цикл був лише прикладом оперативної програми, і питання, наприклад, z-індекс, якщо у вас 1000 об'єктів, кожен з яких має свій z-індекс, і ви встановили їх на 100000000 ... 1000000999 або 10000 ... 10999, і ви перебираєте їх за сортуванням перед рендерінгом, існує багато порівнянь та багато інструкцій щодо завантаження. Там це могло змінити значення!
Фалько

@Falco: У такому випадку безпосередні фактори навіть не враховують; завантаження та порівняння з реєстром здається майже неминучим.
cHao

@cHao: Якщо порівнювати індекси Z один проти одного, вони будуть в регістрах. Якщо обробляти певні діапазони індексів по-різному, це може спричинити негайне порівняння. Зазвичай константи завантажуються перед початком циклу, але якщо, наприклад, один мав цикл, який потрібно прочитати пари значень з пам'яті та порівняти перше значення кожної пари з п'ятьма різними (нерівномірно розташованими) константами в межах 100000 до 100499, а інше значення з п’ятьма іншими такими константами, можливо, буде набагато швидше відняти 100250 (зберігається в реєстрі), а потім порівняти зі значеннями -250 до 250 ...
supercat

5

Коротка відповідь на це запитання полягає в тому, що ні , немає різниці в часі для порівняння двох чисел, виходячи з величини цих чисел, припускаючи, що вони зберігаються в одному типі даних (наприклад, обидва 32-розрядні вставки або обидва 64-бітні довжини.)

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

Єдині випадки, які я можу придумати, що я стикався, коли порівняння двох чисел не було операцією одноциклу, є наступними:

  • Інструкції, де насправді є затримка пам’яті у витягуванні операндів, але це не має нічого спільного з тим, як працює саме порівняння (і, як правило, це не можливо для архітектур RISC, хоча це зазвичай можливо на проектах CISC, як x86 / x64.)
  • Порівняння з плаваючою комою може бути багатоцикловим, залежно від архітектури.
  • Зазначені числа не відповідають розміру слова ALU, і, таким чином, порівняння повинно бути розбито на кілька інструкцій.

4

@ Відповідь Роберта Гарвея хороша; вважайте цю відповідь доповненням до свого.


Також слід врахувати передбачення галузі :

В архітектурі комп’ютера передбачувач гілки - це цифровий ланцюг, який намагається здогадатися, куди піде гілка (наприклад, структура if-then-else), перш ніж це буде відомо точно. Метою галузевого прогноктора є поліпшення потоку в інструкційному трубопроводі. Провідники галузей відіграють вирішальну роль у досягненні високої ефективності в багатьох сучасних конвеєрних мікропроцесорних архітектурах, таких як x86.

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

Відмінне запитання про переповнення стека з цього питання


Прогнозування гілки впливає на час розгалуження, але не сам час порівняння.
reirab

3

Це залежить від реалізації, але це було б дуже, дуже малоймовірно .

Я визнаю, що я не читав деталі реалізації різних двигунів браузера, і CSS не визначає конкретного типу зберігання для чисел. Але я вважаю, що можна припустити, що всі основні браузери використовують 64-бітні числа з плаваючою комою з подвійною точністю ("подвійні", щоб запозичити термін з C / C ++) для обробки більшості своїх числових потреб у CSS , тому що це те, що JavaScript використовує для чисел, і тому використання одного типу полегшує інтеграцію.

З точки зору комп'ютера, всі парні несуть однаковий об'єм даних: 64 біта, будь-яке значення 1 або -3,14, або 1000000, або 1e100 . Кількість часу, необхідного для здійснення операцій над цими номерами, не залежить від фактичного значення цих чисел, оскільки воно завжди працює на однаковій кількості даних. Існує компроміс у тому, щоб робити речі таким чином, оскільки парні не можуть точно представити всі числа (або навіть усі числа в межах їх діапазону), але вони можуть наблизитися досить для більшості питань, і види речей, які робить CSS, не є чисельними -вимагаючи достатньо, щоб потребувати більшої точності, ніж це. Поєднайте це з перевагами прямої сумісності з JavaScript, і у вас є досить вагомий випадок для пар.

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

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


0

Якщо цей код інтерпретувався кожен раз, коли він запускався, то різниця буде тривати довше, ніж токенізувати та інтерпретувати 10000000000000порівняно з 1000. Однак це очевидна перша оптимізація інтерпретаторів у цьому випадку: токенізувати один раз та інтерпретувати лексеми.

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