[/b/] [/d/] [/tu/] [/a/] [/ph/] [/wa/] [/cg/] [/t/] [/p/]

[Burichan] [Foliant] [Futaba] [Greenhell] [Gurochan] [Photon] - [Home] [Manage] [Archive]

[Return]
Posting mode: Reply
Leave these fields empty (spam trap):
Name
Link
Subject
Comment
File
Verification
Password (for post and file deletion)
  • Supported file types are: GIF, JPG, PDF, PNG
  • Maximum file size allowed is 30720 KB.
  • Images greater than 200x200 pixels will be thumbnailed.

File: 1764610132585.jpg -(115625 B, 1080x1177) Thumbnail displayed, click image for full size.
115625 No.235386  

как в ассамблерном языке NASM можно написать как можно более быстрый остаток от деления, когда деление округляет в ближайшую сторону?
Вот пример кода на NASM:
section .text
global _start
_start:
; золотое сечение
mov rbp, rsp
push qword -1836311903 ; [rbp-8]
push qword -1134903170 ; [rbp-16]

; алгоритм Евклида
; <БЕНЧМАРК>
rdtsc
push rax ; [rbp-24]

; {

; rdx — числ. & rcx — знам.
mov rdx, [rbp-8]
cmp rdx, 0
jns _1
neg rdx
_1:
mov rcx, [rbp-16]
cmp rcx, 0
jns _2andeuclid_loop
neg rcx
; сам алгоритм Евклида
_2andeuclid_loop:
cmp rcx, 0
jz _euclid_loop_end

mov rax, rdx
xor rdx, rdx
div rcx
  ; место, которым отличаются 2 варианта алгоритма.
; либо остаток от деления, округляющего вниз,
; либо остаток от деления, округляющего к ближайшему:
;
; mov rbx, rdx ; rbx — temp
; shl rbx, 1
; cmp rbx, rcx
; js _3
; sub rdx, rcx
; neg rdx
; _3:

xor rdx, rcx
xor rcx, rdx
xor rdx, rcx

jmp _2andeuclid_loop
_euclid_loop_end:
push rdx ; НОД: [rbp-32]

; }

rdtsc
sub rax, [rbp-24]
mov qword [rbp-24], rax
; </БЕНЧМАРК>

mov rax, 60 ; syscall: exit
xor rdi, rdi ; exit code 0
syscall
(см закомментированную часть кода, которым отличаются два алгоритма) — можно ли этот код как-то поправить, чтобы ускорить выполнение этой программы? Или этот код уже является реально самым быстрым для решения алгоритмом Эвклида? Акцент делается на взятии остатка, мы же измеряем скорость для этого остатка.

>> No.235387  

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

>> No.235388  

>>235386

> как можно более быстрый остаток от деления

Прочитать *DX, очевидно же.

>> No.235400  

>>235388
а ещë как? Вроде это мало что даëт.

>> No.235401  

>>235400
Инструкции div/idiv кладут его в эти регистры.

>> No.235446  

Вроде каждый шаг алгоритма зависит от предыдущего: не вижу, как серьёзно ускорить.

> rdx — числетель

Почему не rax? Можно было бы убрать mov rax, rdx из тела цикла.

> xor rdx, rcx
> xor rcx, rdx
> xor rdx, rcx

Вариант с mov или xchg на современных AMD64 с register renaming точно не быстрее?

> cmp rdx, 0
> jns _1
> neg rdx

Вариант с cmovcc не быстрее ли? Also, вроде div это unsigned divide. Зачем rdx = abs(rdx) тогда?
mov r8, rdx
neg r8
cmovns rdx, r8
; И для rcx дважды воспользоваться резульататами neg тогда
mov r8, rcx
neg r8
cmovns rcx, r8
jz _euclid_loop_end
; В теле цикла сократив количество jump'ов
_2andeuclid_loop:
mov rax, rdx
xor rdx, rdx
div rcx
xchg rcx, rdx
test rcx, rcx
jnz _2andeuclid_loop
_euclid_loop_end:
; ...

>> No.235447  

P.S.
xor r8, r8
sub r8, rdx
Возможно, быстрее, чем mov + neg.

>> No.235509  

Из двух ответов выше — некоторое полезно, а некоторое неверно по невнимательности. Но как я понял, ответ на тред уже найден?

>> No.235513  

>>235509

> некоторое неверно по невнимательности

Что именно?

>> No.235538  

Проходил мимо и мне почему-то подумалось, что твою, оп, хуйню, сможет решить нейронка. И я решил проверить это, вот результат. По моему хуйня какая-то. Гемини про
"Для ускорения этого участка кода необходимо избавиться от условных переходов (`js`, `jmp`). В современных процессорах (начиная с Pentium Pro и далее) промах предсказателя переходов (branch misprediction) стоит очень дорого (10-20 тактов), что может свести на нет выгоду от уменьшения количества итераций алгоритма Евклида.

Поскольку инструкция `div` сама по себе очень медленная (от 20 до 80 тактов в зависимости от архитектуры), добавление лишних ветвлений критично.

### Решение: Безветвлевая логика (Branchless programming)

Ваша задача математически сводится к следующему: получить *минимальный по модулю* остаток.
После `div rcx` у вас в `rdx` лежит классический остаток $R$ ($0 \le R < B$).
Нам нужно выбрать между $R$ и $B - R$, взяв то, что меньше.
То есть: `rdx = min(rdx, rcx - rdx)`.

Вместо сравнения и прыжка используем инструкцию условной пересылки `cmov` (Conditional Move).

#### Оптимизированный фрагмент кода:

Вставьте этот блок вместо вашей закомментированной части:

```nasm
; rdx — остаток после div, rcx — делитель

mov rbx, rcx ; rbx = делитель
sub rbx, rdx ; rbx = делитель - остаток (альтернативный вариант)

cmp rdx, rbx ; Сравниваем: текущий остаток vs (делитель - остаток)

               ; Это математически эквивалентно проверке: rdx > rcx / 2

cmova rdx, rbx ; Если rdx > rbx (выше/больше), то rdx = rbx

               ; cmova (Condition Move if Above) работает без ветвления

```

### Почему это быстрее?

  1. Отсутствие прыжков: Процессор выполняет инструкции линейно. Нет риска сброса конвейера (pipeline flush).
  2. Минимум инструкций: Мы используем регистровую арифметику, которая выполняется за доли такта (благодаря суперскалярной архитектуре), пока процессор "отходит" от тяжелой инструкции `div`.

-----

### Полный улучшенный код для теста

Вот ваш код с интегрированным улучшением и небольшими правками для корректности:

```nasm
section .text
global _start

_start:
mov rbp, rsp
push qword -1836311903
push qword -1134903170

; <БЕНЧМАРК>
rdtsc
shl rdx, 32
or rax, rdx
mov r8, rax ; Сохраняем начальное время в r8 (вместо стека для скорости)

; Загрузка и модуль чисел (выносим из цикла, так как это подготовка)
mov rdx, [rbp-8]
mov rcx, [rbp-16]

; Быстрый abs() без ветвлений для rdx
mov rbx, rdx
sar rbx, 63
xor rdx, rbx
sub rdx, rbx

; Быстрый abs() без ветвлений для rcx
mov rbx, rcx
sar rbx, 63
xor rcx, rbx
sub rcx, rbx

_euclid_loop:
test rcx, rcx ; cmp rcx, 0 быстрее через test
jz _euclid_loop_end

mov rax, rdx
xor rdx, rdx
div rcx ; rdx = rdx % rcx

; --- НАЧАЛО ОПТИМИЗАЦИИ ---
; Реализация округления к ближайшему (Least Absolute Remainder)
mov rbx, rcx ; копия делителя
sub rbx, rdx ; rbx = делитель - остаток
cmp rdx, rbx ; сравниваем остаток и (делитель-остаток)
cmova rdx, rbx ; если остаток больше половины делителя, берем разницу
; --- КОНЕЦ ОПТИМИЗАЦИИ ---

; Swap (обмен)
xchg rdx, rcx ; xchg чуть компактнее, хотя 3 xor часто быстрее на старых CPU.

                   ; На современных Intel/AMD register-to-register xchg оптимизирован (zero latency renaming).

jmp _euclid_loop

_euclid_loop_end:
; Результат в RDX (или RCX в зависимости от шага, но обычно GCD в том, что не 0)
; В конце цикла rcx=0, значит результат в rdx.

rdtsc
shl rdx, 32
or rax, rdx
sub rax, r8 ; Вычитаем начальное время

; </БЕНЧМАРК>

; rax содержит кол-во тактов.
; Для вывода нужно дописать код, но для теста exit code:

mov rdi, rax ; Вернем младшие биты времени как код возврата (для проверки в strace)
mov rax, 60 ; syscall: exit
syscall
```

### Является ли этот алгоритм *самым* быстрым?

Ответ: Для алгоритма, использующего деление (`div`), — да, это предел.

Однако, если ваша цель — абсолютно самый быстрый GCD (НОД), то использование инструкции `div` является ошибкой. Инструкция `div` выполняется 40-80 тактов. Это очень много.

Самым быстрым алгоритмом на "железе" является Бинарный алгоритм Евклида (Binary GCD / Stein's algorithm). Он вообще не использует деление, только сдвиги (`shr`), сравнения и вычитания.

Если хотите экстремальной скорости, алгоритм должен выглядеть так (псевдокод):

  1. `tzcnt` (подсчет нулей в конце) чтобы убрать общие множители 2.
  2. Цикл на одних вычитаниях и сдвигах.

Но если условие задачи требует именно "остаток от деления с округлением" (Centered Euclidean Algorithm), то вариант с `cmova` — это лучший способ реализовать данный математический шаг.

### Рекомендации к следующему шагу

Хотите ли вы увидеть реализацию Бинарного алгоритма Евклида на NASM, чтобы сравнить его по скорости с вашим вариантом (который использует тяжелый `div`)?"

>> No.235658  

Не знаю, создавать ли отдельный тред, так что здесь напишу следующий вопрос:
Как прочитать в NASM файл в область кучи, выделенную с помощью mmap (системный вызов Линукса) как неанонимную область?

>> No.235659  

>>235658
С памятьу от mmap можно работать как с самой обычной памятью.



Delete Post []
Password

[/b/] [/d/] [/tu/] [/a/] [/ph/] [/wa/] [/cg/] [/t/] [/p/]