Найшвидший спосіб обчислити порядок величини в складі x86


12

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

  • Порядок величини визначається як log10, ні log2.
  • Діапазон допустимого введення є 0для включно. Поведінка для введення за межі цього діапазону не визначена.1012
  • Значення повинні бути округлені до найближчого цілого числа, за винятком того, що для даного вводу 0має бути вихід 0. (Ви можете вважати це найкращим представленням негативної нескінченності, яка можлива в непідписаних цілих числах).
  • Повинна бути збірка x86.
  • Ціле число повинно бути значенням часу виконання , а не статичним / вбудованим цілим числом. Тож ми не знаємо, що це за час компіляції.
  • Припустимо, що у вашому реєстрі вже завантажено ціле число. (Але у відповідь на ясність включіть встановлення значення в регістрі).
  • Неможливо викликати будь-які зовнішні бібліотеки чи функції.
  • Безкоштовно користуватися будь-якими доступними інструкціями в документах Intel .
  • Ні.
  • Будь-яка з ~ 7 архітектур Intel Core є прийнятною (перелічено на стор. 10 ). В ідеалі Nehalem (Intel Core i7).

Виграшною є відповідь, яка використовує найменше можливих циклів годин. Тобто він може мати найбільше операцій за секунду. Орієнтовні підсумки тактового циклу можна знайти тут: http://www.agner.org/optimize/instruction_tables.pdf . Обчислення тактових циклів може відбутися після опублікування відповіді.


Чи дозволено "FYL2X" та інші інструкції FPU?
Цифрова травма

1
Чи повинен результат бути цілим? Якщо так, то як слід його округляти?
Цифрова травма

3
Отже, вхід і вихід є цілими числами, так? Але вхід може бути таким же, як 10 ^ 12, тому він є занадто великим для 32-бітного int. Тож чи вважаємо ми 64-бітовим цілим числом введення?
Пол Р.

3
Чи базується критерій виграшу на основі максимальної чи середньої кількості циклів? І якщо він середній, то який розподіл входів?
Runer112

2
На який процесор націлено? У зв'язаному документі перелічено понад 20 різних процесів (AMD, Intel та інші), і затримки значно різняться.
Цифрова травма

Відповіді:


8

7 циклів, постійний час

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

    .intel_syntax noprefix
    .globl  main
main:
    mov rdi, 1000000000000              #;your value here
    bsr rax, rdi
    movzx   eax, BYTE PTR maxdigits[1+rax]
    cmp rdi, QWORD PTR powers[0+eax*8]
    sbb al, 0
    ret
maxdigits:
    .byte   0
    .byte   0
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .byte   1
    .byte   2
    .byte   2
    .byte   2
    .byte   3
    .byte   3
    .byte   3
    .byte   3
    .byte   4
    .byte   4
    .byte   4
    .byte   5
    .byte   5
    .byte   5
    .byte   6
    .byte   6
    .byte   6
    .byte   6
    .byte   7
    .byte   7
    .byte   7
    .byte   8
    .byte   8
    .byte   8
    .byte   9
    .byte   9
    .byte   9
    .byte   9
    .byte   10
    .byte   10
    .byte   10
    .byte   11
    .byte   11
    .byte   11
    .byte   12
powers:
    .quad   0
    .quad   10
    .quad   100
    .quad   1000
    .quad   10000
    .quad   100000
    .quad   1000000
    .quad   10000000
    .quad   100000000
    .quad   1000000000
    .quad   10000000000
    .quad   100000000000
    .quad   1000000000000

Компілюється в GCC 4.6.3 для ubuntu і повертає значення у вихідний код.

Я не впевнено інтерпретую цю таблицю циклів для будь-якого сучасного процесора, але, використовуючи метод @ DigitalTrauma, на процесорі Nehalim я отримую 7 ?

ins        uOps Latency
---        -    - 
BSR r,r  : 1    3
MOVZX r,m: 1    -
CMP m,r/i: 1    1 
SBB r,r/i: 2    2

О, побачила DigitalTrauma після того, як я почала писати свою. Подібні ідеї. Використовуючи його методологію підрахунку, я думаю, отримаю 3,1,1,1 = 6 для BSR, MOV, CMP, SBB
AShelly

Так, я думаю, що твоє б'є моє. Просто показуємо: а) я не програміст з монтажу; б) збори найкраще залишати нас самими простими смертними ;-)
Digital Trauma

The integer must be a runtime value, not a static/inline integer. So we don't know what it is at compile time.
кіт

1
правильно, а в наступному рядку написано: "Припустимо, що у вашому реєстрі вже завантажено ціле число. (Але у відповідь на ясність включте встановлення значення в регістрі)." Що я і зробив.
AShelly

замініть movzx eax на mov al. Найпопулярніші 24 біти eax вже будуть нульовими, тому zx є зайвим (і це дорого).
peter ferrie

6

Кращий випадок 8 циклів, найгірший - 12 циклів

Оскільки це не зрозуміло в питанні, я базую це затримки з мосту Плюща.

Підхід тут полягає у використанні інструкції bsr(біт-сканування) як log2 (). Результат використовується як індекс в таблиці стрибків, яка містить записи для бітів від 0 до 42. Я припускаю, що враховуючи, що операція над 64-бітовими даними неявно необхідна, тоді використання bsrінструкції нормально.

У кращому випадку вхідні дані, які можна перейти, можуть відображати bsrрезультат безпосередньо на величину. Наприклад, для входів у діапазоні 32-63 bsrрезультат буде 5, який відображається на величину 1. У цьому випадку шлях інструкції:

Instruction    Latency

bsrq                 3
jmp                  2
movl                 1
jmp                  2

total                8

У гіршому випадку введення bsrрезультатів буде відображатись на дві можливі величини, тому запис, який можна перейти, робить ще одну додаткову cmpдля перевірки, чи є вхід> 10 н . Наприклад, для входів у діапазоні 64-127, bsrрезультат буде 6. Відповідний запис, який можна перейти, перевіряє, чи є вхід> 100, і відповідно встановлює величину виходу.

На додаток до найгіршого випадку, ми маємо додаткову інструкцію mov для завантаження 64-бітового негайного значення для використання в cmp, тому найгірший шлях інструкції:

Instruction    Latency

bsrq                 3
jmp                  2
movabsq              1
cmpq                 1
ja                   2
movl                 1
jmp                  2

total               12

Ось код:

    /* Input is loaded in %rdi */
    bsrq    %rdi, %rax
    jmp *jumptable(,%rax,8)
.m0:
    movl    $0, %ecx
    jmp .end
.m0_1:
    cmpq    $9, %rdi
    ja  .m1
    movl    $0, %ecx
    jmp .end
.m1:
    movl    $1, %ecx
    jmp .end
.m1_2:
    cmpq    $99, %rdi
    ja  .m2
    movl    $1, %ecx
    jmp .end
.m2:
    movl    $2, %ecx
    jmp .end
.m2_3:
    cmpq    $999, %rdi
    ja  .m3
    movl    $2, %ecx
    jmp .end
.m3:
    movl    $3, %ecx
    jmp .end
.m3_4:
    cmpq    $9999, %rdi
    ja  .m4
    movl    $3, %ecx
    jmp .end
.m4:
    movl    $4, %ecx
    jmp .end
.m4_5:
    cmpq    $99999, %rdi
    ja  .m5
    movl    $4, %ecx
    jmp .end
.m5:
    movl    $5, %ecx
    jmp .end
.m5_6:
    cmpq    $999999, %rdi
    ja  .m6
    movl    $5, %ecx
    jmp .end
.m6:
    movl    $6, %ecx
    jmp .end
.m6_7:
    cmpq    $9999999, %rdi
    ja  .m7
    movl    $6, %ecx
    jmp .end
.m7:
    movl    $7, %ecx
    jmp .end
.m7_8:
    cmpq    $99999999, %rdi
    ja  .m8
    movl    $7, %ecx
    jmp .end
.m8:
    movl    $8, %ecx
    jmp .end
.m8_9:
    cmpq    $999999999, %rdi
    ja  .m9
    movl    $8, %ecx
    jmp .end
.m9:
    movl    $9, %ecx
    jmp .end
.m9_10:
    movabsq $9999999999, %rax
    cmpq    %rax, %rdi
    ja  .m10
    movl    $9, %ecx
    jmp .end
.m10:
    movl    $10, %ecx
    jmp .end
.m10_11:
    movabsq $99999999999, %rax
    cmpq    %rax, %rdi
    ja  .m11
    movl    $10, %ecx
    jmp .end
.m11:
    movl    $11, %ecx
    jmp .end
.m11_12:
    movabsq $999999999999, %rax
    cmpq    %rax, %rdi
    ja  .m12
    movl    $11, %ecx
    jmp .end
.m12:
    movl    $12, %ecx
    jmp .end

jumptable:
    .quad   .m0
    .quad   .m0
    .quad   .m0
    .quad   .m0_1
    .quad   .m1
    .quad   .m1
    .quad   .m1_2
    .quad   .m2
    .quad   .m2
    .quad   .m2_3
    .quad   .m3
    .quad   .m3
    .quad   .m3
    .quad   .m3_4
    .quad   .m4
    .quad   .m4
    .quad   .m4_5
    .quad   .m5
    .quad   .m5
    .quad   .m5_6
    .quad   .m6
    .quad   .m6
    .quad   .m6
    .quad   .m6_7
    .quad   .m7
    .quad   .m7
    .quad   .m7_8
    .quad   .m8
    .quad   .m8
    .quad   .m8_9
    .quad   .m9
    .quad   .m9
    .quad   .m9
    .quad   .m9_10
    .quad   .m10
    .quad   .m10
    .quad   .m10_11
    .quad   .m11
    .quad   .m11
    .quad   .m11_12
    .quad   .m12
    .quad   .m12
    .quad   .m12

.end:
/* output is given in %ecx */

Це здебільшого генерувалося з результатів асемблера gcc для підтвердження коду C, який я написав . Зверніть увагу, що код C використовує обчислювальний goto для реалізації таблиці стрибків. Він також використовує __builtin_clzll()вбудований gcc, який компілюється в bsrінструкцію (плюс an xor).


Я розглядав кілька рішень, перш ніж прийти до цього:

  • FYL2Xобчислити природний журнал, то FMULза допомогою необхідної постійної. Імовірно, це виграє, якби це був конкурс [тег: інструкція: гольф]. Але FYL2Xмає затримку 90-106 для Ivy Bridge.

  • Бінарний пошук із жорстким кодом. Це насправді може бути конкурентоспроможним - це я залишу комусь іншому :)

  • Повна таблиця пошуку результатів. Це, я впевнений, теоретично найшвидший, але знадобиться таблиця пошуку 1 ТБ - ще не практична - можливо, через кілька років, якщо закон Мура буде продовжувати діяти.


У разі необхідності я можу обчислити середню затримку для всіх дозволених даних.
Digital Trauma

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