#assembly #x86
#сборка #x86
Вопрос:
mov eax,0
mov ecx,100
loop2_start: mov eax,ecx
add eax,ecx
imul eax,eax ;To act as n^2
jle loop2_start
loop2_end: call print_int
call print_nl
Я пытаюсь найти сумму всех квадратов от 1 до 100. Когда я печатаю это, это дает мне 40000, когда ответ должен быть 338350. Что я должен изменить, чтобы заставить его работать? Возможно, моя инструкция перехода?
Комментарии:
1. Добавление некоторых комментариев о том, что, по вашему мнению, делает каждая инструкция, было бы очень полезно для тех, кто пытается ответить на этот вопрос. Или даже просто высокоуровневая идея подхода. Я ожидал
inc
dec
где-то или, но я его не вижу, поэтому я понятия не имею об используемом вами алгоритме2. Просто подсказка: 200 * 200 равно 40 000. Это то, что делает ваш код.
3. не вижу
cmp
4. Как бы я использовал
cmp
?5. Напишите логику на C, или псевдокод, или что-то еще, включая логику цикла, и переведите это в asm. (Имея в виду asm, вы можете использовать
do{}while()
структуру цикла). Ваш текущий код междуloop2_start
иjle
полностью сломан, выбросьте его и начните сначала.
Ответ №1:
Давайте начнем с некоторой математики.
Для положительных значений n
, если:
lastSquare = n*n
Затем:
nextSquare = (n 1)*(n 1)
= (n * n) (n * 1) (1 * n) 1
= (n * n) 2 * n 1
= lastSquare 2 * n 1
Другими словами, (используя псевдокод, подобный C), вы можете сделать:
square = 0;
sum = 0;
for(n = 1-1; n < 100-1; n ) {
square = square 2 * n 1;
sum = sum square;
}
Для сборки 80×86 square = square 2 * n 1
это может быть выполнено с помощью одной быстрой инструкции — например lea ebx,[ebx ecx*2 1]
.
Это дает что-то вроде (синтаксис NASM, непроверенный):
; Loop preperation
xor ebx,ebx ;ebx = square = 0
xor eax,eax ;eax = sum = 0
xor ecx,ecx ;ecx = n = 1-1
; Loop
.next:
lea ebx,[ebx ecx*2 1] ;ebx = square 2 * n 1
add eax,ebx ;eax = sum square
; Loop end
inc ecx
cmp ecx,100-1
jb .next
Однако; середина цикла мала по сравнению с накладными расходами цикла, поэтому было бы интересно немного развернуть цикл, чтобы уменьшить накладные расходы цикла. 99 нечетно, но кратно 3, поэтому:
; Loop preperation
xor ebx,ebx ;ebx = square = 0
xor eax,eax ;eax = sum = 0
xor ecx,ecx ;ecx = n = 1-1
; Loop
.next:
lea ebx,[ebx ecx*2 1] ;ebx = square 2 * n 1
add eax,ebx ;eax = sum square
lea ebx,[ebx ecx*2 2 1] ;ebx = square 2 * (n 1) 1
add eax,ebx ;eax = sum square
lea ebx,[ebx ecx*2 4 1] ;ebx = square 2 * (n 2) 1
add eax,ebx ;eax = sum square
; Loop end
add ecx,3
cmp ecx,100-1
jb .next
Это позволяет вам использовать математику для большего мошенничества! В частности:
nextSquare = (n 1)*(n 1)
= (n * n) (n * 1) (1 * n) 1
= (n * n) 2 * n 1
= lastSquare 2 * n 1
nextNextSquare = (n 2)*(n 2)
= (n * n) (n * 2) (2 * n) 2*2
= (n * n) 4 * n 4
= lastSquare 4 * n 4
nextNextNextSquare = (n 3)*(n 3)
= (n * n) (n * 3) (3 * n) 3*3
= (n * n) 6 * n 9
= lastSquare 6 * n 9
nextAddend = nextSquare nextNextSquare nextNextNextSquare
= lastSquare 2 * n 1 lastSquare 4 * n 4 lastSquare 6 * n 9
= 3 * lastSquare 12 * n 14
= 3 * (lastSquare 4 * n) 14
Это приводит к чему-то вроде (синтаксис NASM, непроверенный):
; Loop preperation
xor ebx,ebx ;ebx = square = 0
xor eax,eax ;eax = sum = 0
xor ecx,ecx ;ecx = n = 1-1
; Loop
.next:
lea edx,[ebx ecx*4] ;edx = square 4 * n
lea edx,[edx*2 edx 14] ;edx = 3 * (square 4 * n) 14
add eax,edx ;eax = sum nextSquare nextNextSquare nextNextNextSquare
lea edx,[ecx*2 ecx] ;edx = 2*n
lea ebx,[ebx 2*edx 9] ;ebx = lastSquare 6 * n 9 = nextNextNextSquare
; Loop end
add ecx,3
cmp ecx,100-1
jb .next
Конечно, разворачивая цикл больше, вы больше обманываете; но если вы продолжаете это делать и оптимизируете, вы в конечном итоге «полностью развернуты» (больше нет цикла), где код становится одной mov eax,precalculated_constant
инструкцией «».
Комментарии:
1.
lea ebx,[ebx ecx*2 1]
эффективен для общего количества операций ввода-вывода, но имеет задержку в 3 цикла (на процессорах Intel, потому что имеет 3 «компонента», 2 плюса), поэтому в этом цикле на самом деле было бы быстрее выполнять mov / imul / add, имея только цепочку зависимостей с циклом в 1 цикл. Илиlea edx, [NOSPLIT ecx*2 1]
/add ebx, edx
было бы очень хорошим компромиссом.2. Или мы могли бы сделать
lea ebx,[ebx ecx*2]
внутри цикла иadd ebx, 100
после цикла, но тогда мы теряем итоговый результат. При развертывании вы можете свернуть1 3 5
в один из 3 LEA, чтобы амортизировать эту дополнительную задержку… (lea с[ebx ecx*2]
имеет дополнительную задержку на AMD из-за масштабируемого индекса. И делая это таким образом, это является частью цепочки зависимостей, передаваемой циклом. :/)3. Если вы действительно хотите обмануть, byjus.com/maths/sum-of-squares показывает , что существует закрытая форма
Σn^2 = [n(n 1)(2n 1)]/6
. Для большинстваn
случаев, когда конечный результат не переполняется, промежуточный результат (до деления на 6) также не переполняется.4. @PeterCordes: если вы делаете взвешенную сумму для всех 32-разрядных процессоров 80×86 (от 80386 до настоящего времени, включая AMD, VIA, Vortex, Cryix и т. Д.), 3-компонентный LEA явно лучше, чем только MUL (без добавления) для любого разумного набора весов. Если вы игнорируете все, кроме последних процессоров для настольных компьютеров / серверов от Intel (исключая Atom, Knight’s Landing и т. Д.), MUL все равно никогда не будет лучше (например, задержка в 3 цикла для Coffee Lake), и вы все равно предпочтете 3-компонентный LEA (используя «предполагаемую стоимость нано-джоуля» в качестве тай-брейка). Для разделения (например, LEA затем ADD) набор весов имеет гораздо большее значение (например, bad на Zen2).
5.Взвешивание по текущей установленной базе? Я не совсем уверен; существует множество процессоров семейства Sandybridge, а большая часть остальных — Core2 / Nehalem / Zen: все они имеют пропускную способность в 1 цикл imul. Ключевым моментом является то, что
imul
задержка находится вне критического пути; это независимая работа, которая «разветвляется» на 1 циклinc
илиdec
цепочку, поэтому важна пропускная способность. Вы получаете только длинные зависимости, переносимые циклом, с этим уменьшением прочности, которое вычисляет следующий на основе предыдущего, а не просто полностью умножает.mov edx, ecx
/imul edx,edx
/add eax, edx
только добавление — это критический путь с широтой