Часовой пояс: UTC + 3 часа




Начать новую тему Новая тема / Ответить на тему Ответить  Сообщений: 71 • Страница 1 из 41  2  3  4  >
  Пред. тема | След. тема 
В случае проблем с отображением форума, отключите блокировщик рекламы
Автор Сообщение
 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
Народ где взять информацию по оптимизации программ, которые я пишу,
под P4, k8, k7?



Партнер
 

Advanced member
Статус: Не в сети
Регистрация: 30.08.2003
Откуда: Санкт-Петербург
Locki
где? у интела и амд... где же еще? У них на сайтах лежат Optimization Guides, которые и рекомендуется прочесть... Хотя если пишете на языках высокого уровня, то тогда обычно можно просто положиться на компилятор

_________________
{:€ дед в законе :-) нородный окодемег
почетный пользователь OpenSuSE 11.3
Ремонт и модернизация ноутбуков IBM (Lenovo) ThinkPad


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
Root особенно на компидятор Дельфи :-)
хотелось бы на русском!


 

Advanced member
Статус: Не в сети
Регистрация: 09.06.2003
Откуда: USSR
Locki писал(а):
особенно на компидятор Дельфи

Вот тут уже врядли, тебе придется половину команд вводить в винарном виде :)


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
Ray Adams дык например надо оптимизить:

function Int32_asm :integer;
begin
time1:=gettickcount;
for i:=1 to 9812500 do
begin
asm
prefetchnta [edx+128] //=>типа думал это оптимизация
prefetchnta [eax+128] //=>типа думал это оптимизация

mov eax,i
mov edx,i

add eax, edx
imul eax, edx
sub eax, edx
idiv eax, edx

end;
end;
time2:=gettickcount;
time:=time2-time1;
Int32_asm:=time;
end;

есть еще какойто /align 16/ , но непонятно как его использовать.


 

Advanced member
Статус: Не в сети
Регистрация: 10.04.2003
Откуда: Москва
Locki, напиши весь цикл, тогда поймешь. :)
Код:
begin
asm
mov ecx, 0
align 16 (если поймет)
LoopGetTickCount:
; цикл start
prefetchnta [edx+128] //=>типа думал это оптимизация  ммм ... глупость! А где первая настройка edx? .... тебе дико везет, что prefetch не вызывает GP!
prefetchnta [eax+128] //=>типа думал это оптимизация

mov eax,ecx
mov edx,ecx

add eax, edx
imul eax, edx
sub eax, edx
idiv eax, edx
; цикл end
inc ecx
cmp ecx,9812500
jl LoopGetTickCount
end;


И потом ... зачем тебе prefetch, если ты не работаешь с памятью? Это ошибка или специально?


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
serj_ Я блин второй месяц асм изучаю и ни черта не понял про первую настройку edx, а на align 16 компилятор ругается!

Добавлено спустя 11 минут, 54 секунды:
писал прогу фильтрации нефти, решил ее оптимизировать, начал изучать асм, т. к. на дельфи все что можно выжал, написал прогу CPUTest для этого ( что бы узнать че быстрее) вот и пытаюсь узнать как че оптимизируется.

Добавлено спустя 11 минут, 44 секунды:
serj_ писал(а):
align 16 (если поймет)
- ругается, как сделать что бы работало?


 

Advanced member
Статус: Не в сети
Регистрация: 09.06.2003
Откуда: USSR
Цитата:
т. к. на дельфи все что можно выжал,

Вот тут я не уверен :), оптимизировать можно все что угодно и без асма.

Цитата:
- ругается, как сделать что бы работало?

Никак, или точнее только через db


 

Member
Статус: Не в сети
Регистрация: 14.01.2004
Откуда: Киев, Украина
Locki для ЯВУ существуют библиотеки с оптимизироваными мат функциями. Естественно на сайте производителей.

_________________
Ку ку


 

Member
Статус: Не в сети
Регистрация: 28.02.2005
Откуда: Брянск
Locki Для Интела ftp://download.intel.com/design/Pentium4/manuals/ - там html'ки лежат с описанием что в pdf'ах. Ну ещё Intel C Compiler - страшная вещь (не бесплатная конечно, и крайне редко проги после него ваще не работают (у меня одна такая)), но это С, а не паскаль.
serj_ И префетчи нафиг из цикла, тем более они нафиг тута не упирались.
ALIGN 16 нужен потому что P4 любит читать данные выровненные по 16 байтной границе (а их тут нет).
Ray Adams Конечно можно, иногда довольно простыми и в то же время странными путями (всякие там MSVC6 могут такую ахинею выдавать).


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
блин скачал мануалы, да только про "оптимизацию на дельфях" ничего не сказано.


 

Advanced member
Статус: Не в сети
Регистрация: 09.06.2003
Откуда: USSR
Locki
Цитата:
блин скачал мануалы, да только про "оптимизацию на дельфях" ничего не сказано.

Оптимизации они не под компилятор, надо конкретнее что именно и для чего оптимизировать


 

Advanced member
Статус: Не в сети
Регистрация: 10.04.2003
Откуда: Москва
Короче...
Код:
begin
asm

rdtsc
rdtsc
push edx
push eax
mov ecx, 0
LoopGetTickCount:
// цикл start

mov eax,ecx
mov edx,ecx

add eax, edx
imul eax, edx
sub eax, edx
idiv eax, edx

// цикл end
inc ecx
cmp ecx,9812500
jl LoopGetTickCount
rdtsc
pop ecx
sub eax,ecx
pop ecx
sbb edx,ecx
// в регистрах edx:eax время выполнения цикла в тиках процессора
end;


 

Member
Статус: Не в сети
Регистрация: 28.02.2005
Откуда: Брянск
Locki А что ты решил оптимизтровать, расскажи поподробнее. Вот оптимизация для всего Pentium'оПодобного http://www.agner.org/assem/pentopt.pdf - но опять же всё это на Си, а не на Паскале (Паскаль помоему сделан не для получения супер быстрого кода). Хочешь чего-то реактивного - придется знакомиться с асмом и со всеми загонами современных процессоров.
Вобщем колись!
serj_ А ещё процессоры любят не mov ecx,0 а xor ecx,ecx :).


 

Advanced member
Статус: Не в сети
Регистрация: 10.04.2003
Откуда: Москва
Toad, потерять 2 такта при цикле в 9812500 итераций .... ;)
Да и не процессор это 'не любит' (что неверно), а осталось с вирусописателей.


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
Надыбал тут:
--------------------------------------------------------------------------------

Права на распространение Ангера Фога, (c) 1996
Перевод Дмитрия Померанцева, (c) 1997 FTS Labs.

Содержание:

0. примечание переводчика
1. введение
2. литература
3. отладка и проверка
4. модель памяти
5. выравнивание
6. кеш
7. блокировка генерации адреса (AGI)
8. спаривание инструкций
9. исполнение кода в цикле
10. неполное спаривание
11. замена сложных инструкций на более простые
12. переходы и ветви
13. префиксы
14. уменьшение длины кода
15. планирование операций с плавающей точкой
16. оптимизация цикла
17. обзор специальных инструкций
18. целые числа вместо чисел с плавающей точкой
19. числа с плавающей точкой вместо целых чисел
20. список целочисленных инструкций
21. список инструкций с плавающей точкой
22. скоростные испытания
23. соображения о других микропроцессорах


0. ПРИМЕЧАНИЕ ПЕРЕВОДЧИКА
=========================
Прежде всего я хочу сказать, что я не являюсь профессиональным переводчиком
и ранее не занимался переводами технической документации. Возможно, где то
в тексте будут встречаться литературные огрехи, но в любом случае -
документация на английском языке из любопытной вещи превратилась во вполне
понятное руководство, пригодное к повседневной работе.

И так, прежде всего несколько неточностей - что бы не загромождать текст
фразами типа: "инструкции, оперирующие с числами с плавающей точкой", я
просто называю их "инструкции с плавающей точкой", справедливо пологая, что
от технического руководства не будут требовать литературных изысков. Подобная
ситуация наблюдается и с целыми числами. Надеюсь, что теперь вы правильно меня
поймете и подобные выражения в тексте не будут вызывать праведный гнев.

В остальном же я старался при переводе сохранить первоначальный смысл текста,
передать его вам без искажений. Пользуйтесь на здоровье.

И еще, на этот раз последнее. Я не знаю как Ангер Фог, но я точно имею много
против при использовании данного текста с комерческой целью. Конечно, вы
можете применить знания, полученные из данного текста по вашему усмотрению,
а так же распросронять его на любых носителях, не получая от распростанения
комерческой выгоды. То есть, если вы захотите использовать его при написании
собственной книги, то желательно все-таки спросить у самого Агнера Фога, для
начала, ну а если захотите использовать мой перевод, то и у меня...

Дмитрий Померанцев
03.12.1997
2:5020/1140.26


1. ВВЕДЕНИЕ
===========
Это руководство подробно описывает, как составлять оптимизированный код,
на языке ассемблер, с конкректными примерами для Pentium(r) микропроцессора.

Это руководство более подробно, чем то, которое вы найдете где-нибудь еще,
в большинстве случаев позволит вам вычислить - сколько тактов будет
выполняться конкректная часть кода.

Програмировать на ассемблере гораздо труднее, чем на языке высокого уровня.
Ошибки очень легко допустить , а вот обнаружить их гораздо труднее. И так,
вы предупреждены! Я буду надеяться, что читатель уже имеет опыт
програмирования на ассемблере, а если нет, то я рекомендую сначала изучить
пособия по програмированию на ассемблере и только потом приступать к сложным
оптимизациям.

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

Прежде чем вы начали перекладывать вашу программу на ассемблер убедитесь, что
вы уже максимально оптимизировали алгоритм. Гораздо чаще вы можете ускорить
свою программу оптимизируя алгоритм, а не исполняемый код.

Кроме того, многие компиляторы с языков высокого уровня уже весьма неплохо
оптимизируют код под Pentium, и дальнейшая оптимизация имеет смысл только в
критических по скорости участкам программы.

Intel недавно заявил, что скоро они будут производить новые версии Pentium и
PentiumPro процессоров, содержащих 57 новых инструкций для целочисленных
векторных операций. Эта технология будет называться инструкциями расширения
мультимедии (MMX). Поведение чипа Pentuim с MMX будет несколько отличаться
от Pentium без MMX. Эти отличия будут отражены в соответствующей документации.
PentuimPro процессор ведет себя совершенно по другому и будет лишь
упоминаться в этом руководстве.

Информация в этом руководстве основана, главным образом, на моих собственных
экспериментам с Pentium процессором. Информация о PentiumPro и процессорах с
MMX - основана исключительно на документации полученной в Intel. Так-же
выражаю благодарность Карки Дж. Бахадур из Колорадского университета.

Пожалуйста, не присылайте мне вопросов по програмированию. Я не собераюсь
делать вашу домашнюю работу за вас.

Удачи в охоте за наносекундами!


2. ЛИТЕРАТУРА
=============
Много полезной информации вы можете свободно(читайте бесплатно) скачать с
Intel веб сервера:
http://www.intel.com/

Вы, так-же, можете найти требующиеся вам документы на поисковых системах:
http://www-cs.intel.com/search.htm
и:
http://www-techdoc.intel.com/cgi-bin/taos.pl

Документы могут быть в различных файловых форматах. Если требуемый документ
не поддерживается вашими средствами обработки текстов, то это-же документ,
но в подходящем формате вы сможете найти где-нибудь в Internet. Многие
компании, выпускающие програмное обеспечение предлагают свободно(читайте
бесплатно) просмоторщики файлов, для поддержания их файлового формата.

Наиболее полезный и чаще используемый документ Intel:
"AP-526 Optimizations for Intel's 32-bit Processors"( AP-526 Оптимизация кода
для 32 битных процессоров Intel), который может быть скачан со следующего URL:
http://www-techdoc.intel.com/cgi-bin/sy ... spangle.pl?\docs\microproc\general\app_n\242819+0

Весьма оригинальное руководство, названное: "Optimizing Applications for the
Pentium Processor"(Оптимизация приложений под Pentium процессор) доступна на:
http://www.intel.com/ial/processr/cbt.htm

Подробную информацию о MMX процессорах вы можете найти в документах:
"Intel architecture MMX technology developer's manual"
("Руководство разработчика под Intel MMX архитектуру")
"Intel architecture MMX technology programmers reference manual"
("Справочник програмиста под Intel MMX архитектуру"),
обе документации доступны на:
http://www.intel.com/pc-supp/multimed/MMX

Есть немало и других, помимо Intel, источников, содержащих полезную
информацию. Ее можно найти в comp.lang.asm.x86 FAQ. Также я настоятельно
рекомендую посетить:
http://www.x86.org/
Есть не плохой ShareWare-редактор ASMEDIT, с online-справочной системой и
кодами инструкций. Доступен на:
http://www.inf.tu-dresden.de/~ok3/asmedit.html

(Прим переводчика) От себя могу порекомендовать очень неплохой
FreeWare-редактор TasmEd (by Eugeny Nonko), доступный на:
ftp://bbs.biserv.altai.su/incoming/RoS/tasmed.rar


3. ОТЛАДКА И ПРОВЕРКА
=====================
Отладка ассемблерного кода может быть весьма тяжелой и досаждающей, как
вы, наверное, уже сами убедились. Я рекомендую начинать писать ту часть
кода, которую вы хотите оптимизировать, как подпрограмму на языке высокого
уровня. Затем напишите напишите программу тестирования, которая тщательно
проверит вашу подпрограмму. Убедитесь, что программа тестирования прошлась
по всем ветвям вашей подпрограммы и все особые случаи выполнились.

После того, как вы убедились, что ваша подпрограмма, написанная на языке
высокого уровня работает, вы можете перевести ее на ассемблер (некоторые
языки высокого уровня способны сделать и эту работу) и продолжить
тестирование.

И только после этого вы можете приступать к оптимизации. После каждой
модификации желательно снова запустить программу тестирования, что бы
убедиться, что все работает правильно.

Пронумеруйте различные версии вашей программы, что бы вы могли возвратиться
к предыдущей модификации/иям, если обнаружите ошибку, незамеченную программой
тестирования. (Например использование неверного адреса).


4. МОДЕЛЬ ПАМЯТИ
================
Pentium изначально разрабатывался для 32 битного кода и 16 битный код
исполняется на нем медленнее. Тот же эффект получается при сегментировании
кода и данных, желательно использовать flat-модель памяти, современные
операционные системы поддерживают этот режим (т.н. Windows'95, OS/2, или
32-битный DOS-extender). По этому в данном руководстве, если не оговорено
особо, все примеры расчитаны на 32 битную flat-модель памяти.


5. ВЫРАВНИВАНИЕ
===============
Все данные в RAM должны быть выравнены на адрес, делящийся на 2, 4, или 8,
согласно следующей схеме:

размер операнда выравнивание
----------------------------------
1 (byte) 1
2 (word) 2 (или адрес MOD 4 >< 3. другие процед. треб. выр. на 2)
4 (dword 4
6 (fword) 4 (Pentium Pro требует выравнивания на
8 (qword) 8
10 (tbyte) 8 (Pentium Pro требует выравнивания на 16)

На не выравненные данные потребуется, по крайней мере, на 3 такта больше для
доступа.

В выравнивании кода нет необходимости, когда вы работаете на Pentium, но для
оптимальной производительности на других процессорах вы можете выравнять
точки входа в подпрограммы и циклы на 8 или 16.


6. КЕШ
======
Чип Pentium имеет 8k кеша (L1) для кода и 8k для данных. Данные на L1 могут
быть прочитаны или записаны за 1 такт, а в случае промаха кеша это может
стоить многих тактов. По этому очень важно, что бы вы поняли как
работает кеш и могли использовать его с максимальной эффективностью. К
сожалению в большинстве других документов кеш описывается либо недостаточно,
либо слишком непонятно. Я надеюсь, что вы сочтете это описание одним их
лучших.
Кеш данных состоит из 256 строк, по 32 байта в каждой. Всякий раз, когда вы
пытаетесь прочесть данные, которых нет в кеше - процессор прочтет из памяти
целую строку. Строки кеша всегда выравнены по физическому адресу, делимому на
32. Значит всякий раз, когда вы прочитали байт по адресу делимому на 32,
следующие 31 байт могут быть прочитаны практически мгновенно. Вы можете
воспользоваться этим преимуществом, размещая часто используемые данные в
блоках, выравненных по 32 байта. К примеру, если у вас есть цикл, который
работает с двумя массивами, тогда вы можете скомбинировать эти два массива в
одну структуру, что бы данные, которые обрабатываются совместно и загружались
совместно.
Если размер массива кратен 32 байтам, то было бы неплохо выровнять его на 32
байта.
Строка кеша не может быть связана с произвольным адресом памяти. У каждой
строки кеша есть 7-битное установочное значение, соответствующее битам с 5-го
по 11-ый физичекого адреса памяти (биты 0..4 соответствуют 32 байтам внутри
строки кеша). Таким образом у нас есть два блока по 128 строк (всего 256
строк), в каждом из которых могут существовать две строки, ссылающиеся на
один и тот-же адрес памяти. Следствием этого является то, что мы не можем
иметь в кеше более двух строк, указывающих на физические адреса памяти,
имеющие одинаковые биты 5-11. Мы можем определить, имеют-ли два адреса
одинаковое установочное значение следующим образом: Сбросить младшие 5 бит,
каждого адреса, что бы получить значение делимое на 32. Если два адреса,
со сброшенными младшими 5 битам кратны 4096 (=1000h), то эти адреса имеют
одинаковое установочное значение.

Позвольте мне проиллюстрировать это следующим кодом, где адрес в ESI делим
на 32:

AGAIN: MOV EAX, [ESI]
MOV EBX, [ESI + 13*4096 + 4]
MOV ECX, [ESI + 20*4096 + 28]
DEC EDX
JNZ AGAIN

Все три адреса имеют одинаковую установочную величину, поскольку в усеченном
виде они кратны 4096. Этот цикл будет исполняться крайне медленно. Как только
мы попытеамся прочесть данные в ECX процессор обнаружит, что нет возможных
строк, для кеширования данных, следовательно он удалит из кеша наиболее давно
используемые данные, т.е. строку кеширующую данные, которые мы читали в EAX
и заполнит ее данными с [ESI + 20*4096] по [ESI +20*4096 + 31], а затем
прочтет данные в ECX. Затем, когда мы попытаемся прочесть данные в EAX,
процессор снова обнаружит, что в кеше нет строки для кеширования EAX и нет
места, что бы ее добавить, значит ему придется снова отвергнуть наиболее
старую строку (для EBX), затем проведет кеширование для EAX и, наконец,
прочтет данные. Теперь тоже самое произойдет с EBX, и т.д... Таким образом
мы имеем постоянные промахи кеша, и на каждый проход по циклу тратиться около
60 тактов. Но если мы заменим третью строку на:

MOV ECX, [ESI + 20*4096 + 32]

Все! Мы пересекли 32 байтную границу, значит третья строка будет иметь другую
установочную величину, все строки будут постоянно находиться в кеше, не будет
промахов. Время затрачиваемое на один проход по циклу сократится до 3 тактов
(за исключением первого прохода, конечно) - очень значительное улучшение!

Конечно, определить это весьма не просто, особенно если данные разбросаны по
разным сегментам. Лучший способ, которым вы можете решить эту проблему -
хранить данные, использующиеся в критической части вашей программы, в пределах
одного блока длиной 8Кб максимум, или двух блоков, по 4Кб максимум (например
один блок для статических переменных, а другой для стека). Это гарантирует,
что все линии кеша будут использованы оптимально.

Если критическая часть вашей программы работает с большими структурами
данных или работает с произвольными адресами, то не плохим решением будет
хранить все часто используемые переменные (счетчики, указатели, контрольные
переменные, и т.д.) в одном непрерывном блоке, до 4Кб длиной, тогда у вас
останется полный набор строк кеша, для доступа к произвольным данным.
Поскольку вам скорее всего потребуется память в стеке, для параметров
подпрограмм или адресов возврата, то лучше будет скопировать все часто
используемые статические данные в динамические переменные, в стеке, а затем,
если необходимо, копировать их обратно, за пределами критической части
программы.

Во время чтения данных, не находящихся в L1, строка кеша сначала заполняется
из L2, скорость доступа которой примерно 200 ns (что требует 20 тактов в
100 MHz системе), но первые байты из запрашиваемой строки обычно доступны
уже через 50-100 ns. Если запрашиваемые данные не в L2, то задержка составит
порядка 200-300 ns. Эта задержка может быть и большей, если вы пересечете
границу страницы DRAM. (Размер страницы DRAM 1Кб, для 4Мб 72 пиновых модулей
и 2Кб для 16Мб модулей).

Когда вы записываете данные, по адресу, не находящемуся в L1, данные будут
помещены в L2. Это займет примерно 100 ns. Значит если вы пишете 8 или более
байт в 32 байтовый блок, не читая ничего от туда, то лучше всего будет просто
прочитать от туда что-нибудь, что бы загрузить блок в L1. Все последующие
записи в тот-же блок будут записываться во внутренний кеш, и каждая запись
займет 1 такт. (Это не нужно на Pentium Pro, где всегда загружается строка
при промахе записи). Иногда возникает замедление при повторяющихся записях,
без промежуточного чтения.

Иногда можно уменьшить количество промахов при записи, записывая по 8 байт,
используя FILD / FISTP с DWORD операндом, вместо использования целочисленных
регистров. Более медленное исполнение инструкций FILD и FISTP компенсируется
тем, что вы должны сделать только половину необходимых циклов чтения-записи.
Тем не менее этот метод эффективен только на Pentium и только если адрес
записи не находиться в кеше. Более подробно этот метод будет рассмотрен в
разделе 19.

Временные данные вы можете помещать в стек, поскольку стек скорее всего
попадет в кеш. Знаете вы или нет, но тем не менее, если вы сохраняете QWORD
данные в DOWRD стеке, или DWORD данные в WORD стеке, то у вас может быть
проблема с выравниванием.

Если рабочее время двух структур не пересекается, то они могут использовать
одну и ту же область памяти, для повышения эффективности кеша. Это
соответствует общим правилам распределения памяти временным переменным в
стеке.

Конечно, хранение данных в регистрах более эффективно, но поскольку регистров
не много, то возможно вы захотите использовать [ESP], а не [EBP] для
адресации данных в стеке, тем самым освобождая EBP для других целей. Просто не
забывайте, что значение ESP изменяется каждый раз, когда вы используете PUSH
или POP. (Вы не сможете использовать ESP под 16 битной Windows поскольку
таймер непредсказуемо изменяет старшее слово в ESP).

Pentium имеет 8Кб кеша кода, имеющих структуру, похожую на кеш данных. Значит
важно, что бы критическая часть вашего кода (внутренние циклы) полностью
помещалась в кеш. Часто используемые части кода или структуры, которые
используются вместе, было бы предпочтительно и хранить вместе. Редко
используемые ветви или подпрограммы должны храниться дальше.


7. БЛОКИРОВКА ГЕНЕРАЦИИ АДРЕСА (AGI).
========================================
Требуется один такт, что бы рассчитать адрес, требующийся инструкции для
доступа к памяти. Обычно это делается отдельно, в конвеере, пока выполнятеся
предыдущая инструкция или пара инструкций. Но если адрес зависит от
результата инструкции, выполняющейся в предыдущем такте, то потребуется еще
один такт, дополнительно, для расчета адреса. Это было названо остановка AGI.

Пример:
ADD EBX,4 / MOV EAX,[EBX] ; остановка AGI
В данном примере остановка AGI может быть легко удалена, путем добавления
других инструкций между ADD EBX,4 и MOV EAX,[EBX] или путем обмена их
местами:
MOV EAX,[EBX+4] / ADD EBX,4

Также может получиться остановка AGI при применении инструкций использующих
ESP для адресации, т.н. PUSH, POP, CALL и RET, если ESP была изменена в
предыдущем такте инструкцией типа MOV, ADD или SUB. Однако у Pentium есть
специальная схема, которая позволяет предсказать значение ESP после стековых
операций, таким образом вы не получите остановку AGI после использования PUSH,
POP и CALL. Однако вы можете получить остановку AGI после RET, если у него
есть операнд для добавления к ESP.

Примеры:
ADD ESP,4 / POP ESI ; остановка AGI
POP EAX / POP ESI ; нет остановки, спаривание
MOV ESP,EBP / RET ; остановка AGI
CALL L1 / L1: MOV EAX,[ESP+8] ; нет остановки
RET / POP EAX ; нет остановки
RET 8 / POP EAX ; остановка AGI

Инструкция LEA также приводит к остановке AGI, если она использует базовый или
индексный регистр, который был изменен в предыдущем такте.
INC ESI / LEA EAX,[EBX+4*ESI] ; остановка AGI


8. СПАРИВАНИЕ ИНСТРУКЦИЙ
========================
Pentuim снабжен двумя конвеерами для исполнения инструкций, называющиеся
U-труба и V-труба. при определенных условиях можно выполнить две инструкции
одновременно - одну в U-трубе, а другую в V-трубе. Это может практически
удвоить скорость. Следовательно стоит потратить время на оптимизацию, что бы
ваши инструкции спаривались.

Следующие инструкции спариваются в обоих трубах:
MOV регистр, память, или значение в регистре или памяти
PUSH регистр или значение
POP регистр
LEA, NOP
INC, DEC, ADD, SUB, CMP, AND, OR, XOR,
и некоторые виды TEST (смотри раздел 17.2)

Следующие инструкции спариваются только в U-трубе:
ADC, SBB
SHR, SAR, SHL, SAL со значением счетчиком
ROR, ROL, RCR, RCL со значением счетчиком 1

Следующие инструкции могут исполняться в обоих трубах, но спариваются только в
V-трубе: ближние вызовы, короткие и ближние переходы, короткие и ближние
условные переходы.

Все остальные инструкции могут исполняться только в U-трубе и не спариваются.

Две последовательные инструкции спарятся если будут выполнены следующие
условия:

8.1 Первая инструкция спаривается в U-трубе, а вторая в V-трубе.

8.2 Вторая инструкция не должна читать или писать в регистр, в который пишет
первая. Например:
MOV EAX, EBX / MOV ECX, EAX ; чтение после записи, не спариваются
MOV EAX, 1 / MOV EAX, 2 ; запись после записи, не спариваются
MOV EBX, EAX / MOV EAX, 2 ; запись после чтения, спариваются
MOV EBX, EAX / MOV ECX, EAX ; чтение после чтения, спариваются
MOV EBX, EAX / INC EAX ; чтение и запись после чтения, спариваются

8.3 В правилах 8.2 мы рассматривали части регистров, как полные регистры,
например:
MOV AL, BL / MOV AH, 0 ; запись в разные части одного регистра,
; не спариваются

8.4 Две инструкции, пишущие в разные части регистра флагов могут спариваться,
не смотря на правила 8.2 и 8.3 Например:
SHR EAX,4 / INC EBX ; спариваются

8.5 Инструкция, пишущая в флаги может спариваться с условным переходом, не
смотря на правило 8.2 Например:
CMP EAX, 2 / JA LabelBigger ; спаривается

8.6 Следующие инструкции могут спариваться, не смотря на то, что они
модифицируют указатель стека. Например:
PUSH + PUSH, PUSH + CALL, POP + POP

8.7 Есть различия в спаривании инструкций с префиксом.
Вот различные типы префиксов:
- инструкции, адресующие не типичный сегмент имеют префикс.
- инструкции, использующие 16 битные данные в 32 битном режиме, равно как
инструкции, использующие 32 битные данные в 16 битном режиме имеют
префикс размера операнда.
- инструкции, использующие 32 битные базовые или индексные регистры в
16 битном режиме имеют префикс размера операнда.
- повторяющиеся строковые инструкции имеют префикс повторения.
- заблокированные инструкции имеют префикс блокировки.
- многие двухбайтные инструкции, не присутствующие в процессоре 80086
имеют двухбайтный опкод, где первый байт - 0Fh. Байт 0Fh ведет себя
как префикс на процессоре Pentium без MMX, но не на процессоре Pentium
MMX. Наиболее общие инструкции с префиксом 0Fh: MOVZX, MOVSX, PUSH FS,
POP FS, PUSH GS, POP GS, LFS, LGS, LSS, SETcc, BT, BTC, BTR, BTS, BSF,
BSR, SHLD, SHRD и IMUL с двумя не численными операндами.

На Pentium без MMX, инструкции с префиксами могут исполняться только в
U-трубе, за исключением ближних условных переходов.
На Pentium MMX инструкции с размером операнда, размером адреса, или
префиксом 0Fh могут выполниться в каждой трубе, а инструкции с префиксом
сегмента, повторения и блокировки по-прежнему могут выполняться только в
U-трубе.

8.8 Инструкция, которая имеет как смещение, так и непосредственные данные не
спариваются на Pentium без MMX, а на Pentium MMX спариваются только в
U-трубе:
MOV DWORD PTR DS:[1000], 0 ; не спаривается, или только в U-трубе
CMP BYTE PTR [EBX+8], 1 ; не спаривается, или только в U-трубе
CMP BYTE PTR [EBX], 1 ; спаривается
CMP BYTE PTR [EBX+8], AL ; спаривается

8.9 Инструкции должны предзагружаться и декодироваться. Это объяснятеся в
разделе 9.


9. ИСПОЛНЕНИЕ КОДА В ЦИКЛЕ
==========================
Обычно при первом исполнении некоторой части кода времени тратиться больше,
чем при ее повторном исполнении. И причины этого следующие:

9.1 При загрузке кода из RAM в кеш требуется больше времени, чем на
исполнение этого кода.

9.2 Декодирование кода - тонкий момент. Если требуется 1 такт на декодирование
инструкции, то не возможно за этот-же так декодировать вторую инструкцию,
т.к. процессор еще не знает длину первой инструкции. Pentium решает эту
проблему запоминая длину любой, только что выполнившейся инструкции.
Следствием этого является то, что при первом исполнении инструкции не
спариваются, за исключением случая, когда длины обеих команд - один байт.

9.3 При первом проходе ветви еще не в целевом буффере, и, следовательно,
вероятность правильного предсказания перехода меньше.

9.4 Все данные, используемые кодом должны быть в кеше данных, что может занять
больше времени, чем исполнение инструкций. При повторном исполнении
возможно, что данные уже в кеше.

Четырех этих причин более чем достаточно, что бы код цикла при повторном
исполнении требовал меньше времени, чем при первом.

Если ваш цикл слишком большой и не помещается в кеш, то против вас сработают
9.1 и 9.2. Вы должны попытаться реорганизовать цикл, что бы он целиком
помещался в кеш. Если у вас более 256 переходов и ветви в цикле, то против
вас сработает 9.3 по полной программе.

Равно как, если ваш цикл часто обращается к слишком большим структурам данных,
то замедление скорости будет связано с многочисленными промахами кеша данных.


10. НЕПОЛНОЕ СПАРИВАНИЕ
=======================
Есть ситуации, когда две спаривающиеся инструкции не выполняются одновременно,
а с небольшим перекрытием. Однако они все еще составляют пару, поскольку одна
инструкция выполнятеся в U-трубе, а вторая в V-трубе. Ни одна инструкция не
может начать исполняться, пока не завершаться обе недоспарившиеся инструкции.

Неполное спаривание произойдет в следующих случаях:

10.1 Если у вторых инструкций остановка AGI

10.2 Две инструкции не могут получить доступ к одному и тому-же двойному слову
одновременно.
Следующий пример подоразумевает, что ESI делиться на 4:
MOV AL, [ESI] / MOV BL, [ESI+1]
Два операнда в пределах одного и того-же DWORD, они не могут выполниться
одновременно. На пару уходит 2 такта.
MOV AL, [ESI+3] / MOV BL, [ESI+4]
Здесь два операнда находятся по разные границы DWORD, они спариваются и
на выполнение пары требуется один такт.

10.3 Правило 10.2 распространяется на те данные у которых одинаковые 2-4 бит
в адресах. (конфликт банков кеша) Для DWORD адресов это означает, что
два адреса не должны делиться на 32.
Примеры:
MOV [ESI], EAX / MOV [ESI+32000], EBX ; неполное спаривание
MOV [ESI], EAX / MOV [ESI+32004], EBX ; полное спаривание

Спаренные инструкции, не обращающиеся к памяти выполняются за один такт. MOV
инструкции, передающие данные в или из памяти, так же исполняются за такт,
если данные находятся в кеше и правильно выравнены. Нет замедления при
использовании комплексов режимов адресации, таких как например масштабирование
индексных регистров.

Спаренные инструкции, которые читают из памяти, делают расчеты и сохраняют
результат в регистре флагов, будут исполняться 2 такта.(инструкции
чтения/модифицирования).

Спаренные инструкции, которые читают из памяти, делают расчеты и сохраняют
результат в памяти, будут исполняться 3 такта. (инструкции
чтения/модифицирования/записи).

10.4 Если инструкции чтения/модифицирования спаривается с инструкцией
чтения/модифицирования или чтения/модифицирования/записи, то спаривание
не полное.

Количество используемых тактов приведено в этой таблице:

| Первая инструкция
| MOV или чтение/ чтение/модификация/
Вторая инструкция | межрегистровая модификация запись
----------------------|----------------------------------------------
MOV или межрегистровая| 1 2 3
чтение/модификация | 2 2 4
чтение/мод./запись | 3 3 5
----------------------|-----------------------------------------------

Примеры:
ADD [mem1], EAX / ADD EBX, [mem2] ; 4 такта
ADD EBX, [mem2] / ADD [mem1], EAX ; 3 такта

10.5 Когда спаренные инструкции используют дополнительные такты, из-за
промахов кеша, рассогласования, или не правильного предсказания перехода,
они будут выполняться больше времени, чем каждая инструкция в
отдельности, но потрачено тактов все равно будет меньше, чем если бы
они исполнялись отдельно.

Что бы избежать не полного спаривания, вы должны следить какие инструкции
попадают в U-трубу, а какие в V-трубу. Вы можете просмотреть ваш код, что бы
обнаружить неспариваемые инструкции, инструкции спариваемые только в одной
трубе, или инструкции, которые не могут спариться по правилам, определенным в
разделе 8.

От неполного спаривание можно избавиться просто поменяв инструкции местами.
Например:

L1: MOV EAX,[ESI]
MOV EBX,[ESI]
INC ECX

Здесь инструкции MOV образуют не полную пару, поскольку получают доступ к
одной и той же позиции памяти, последовательность исполнится за 3 такта. Мы
может улучшить код простым переставлением инструкции INC ECX, что бы она
составила пару с одной из инструкций MOV.

L2: MOV EAX,OFFSET [A]
XOR EBX,EBX
INC EBX
MOV ECX,[EAX]
JMP L1

Инструкции INC EBX / MOV ECX,[EAX] спариваются не полно, потому что у
последней - остановка AGI. Последовательность исполниться за 4 такта. Но если
мы вставим инструкцию NOP, или любую подобную, то MOV ECX,[EAX] спариться с
JMP L1, и последовательность выполниться за 3 такта.

Следующий пример - 16 битный, допускается, что SP делиться на 4:

L3: PUSH AX
PUSH BX
PUSH CX
PUSH DX
CALL FUNC

Здесь инструкции PUSH образуют две не полные пары, т.к. оба операнда находятся
по одну границу DWORD. PUSH BX мог бы спариться с PUSH CX, потому что они по
разные границы DWORD, но не делает этого, потому что уже спарился с PUSH AX.
Последовательность исполняется 5 тактов. Но если мы вставим NOP, или
аналогичную инструкцию, то PUSH BX спариться с PUSH CX, а PUSH DX с CALL FUNC.
И тогда последовательность будет выполняться за 3 такта. Другим решением этой
проблемы является - не допуск SP быть делимым на 4. Однако эту проблему трудно
решить в 16 битном режиме, по этому этот способ больше подходит для 32 битного
режима.


11. ЗАМЕНА СЛОЖНЫХ ИНСТРУКЦИЙ НА БОЛЕЕ ПРОСТЫЕ
==============================================
Вы можете заменять инструкции чтения/модифицирования и
чтения/модифицирования/записи, что бы достигнуть спаривания. Пример:
ADD [mem1],EAX / ADD [mem2],EBX ; 5 тактов
Этот код можно заменить на последовательность, выполняющуюся за 3 такта:
MOV ECX,[mem1] / MOV EDX,[mem2] / ADD ECX,EAX / ADD EDX,EBX
MOV [mem1],ECX / MOV [mem2],EDX

Подобным образом вы можете заменять не спаривающиеся инструкции:
PUSH [mem1] / PUSH [mem2] ; не спаривающиеся
Заменяем на:
MOV EAX,[mem1] / MOV EBX,[mem2] / PUSH EAX / PUSH EBX ; всегда спариваются

Другие примеры неспариваемых инструкций, которые можно заменить более
простыми:
CDQ разбивается на: MOV EDX,EAX / SAR EDX,31
NOT EAX заменяется на XOR EAX,-1
NEG EAX разбивается на XOR EAX,-1 / INC EAX
MOVZX EAX,BYTE PTR [mem] разбивается на XOR EAX,EAX / MOV AL,BYTE PTR [mem]
JECXZ разбивается на TEST ECX,ECX / JZ
LOOP разбивается на DEC ECX / JNZ
XLAT заменяется на MOV AL,[EBX+EAX]

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


12. ПЕРЕХОДЫ И ВЕТВИ
====================
Pentium пытается предсказать - произойдет ли условный переход, или нет. Для
этого у него есть "буффер предсказания переходов" (BTB) в котором храниться
история 256 последних переходов.

Pentium без MMX, делает предсказания на основе двух последних событий.
Предполагается, что условный переход произойдет, если он произошел в прошлый
или в позопрошлый раз. Соответственно, если условный преход не произошел в
последние два раза, то предполагается, что он не произойдет и сейчас. Если
условный переход,не встречался ранее (или отсутствует в BTB), то считается что
он не произойдет.

Pentium MMX (или Pentium Pro) делают свой анализ на основе последних четырех
событий, т.е. может предсказать простой повторяющийся участок. Условный
переход, который не встречался ранее (или отсутствует в BTB), будет
считаться происходящим, если он направлен назад (т.н. цикл) и не происходящем,
если он направлен вперед.

Если условный переход предсказан правильно,(т.е.если догадка была правильная),
то он будет исполнен за 1 такт. Непредсказанный переход исполниться за 4
такта, если инструкция в U-трубе и за 5 тактов, если в V-трубе.

Задержка из-за непредсказанного перехода будет намного большей, если сразу
за переходом будет инструкция другого перехода или вызова подпрограммы.
Pentium ведет себя очень странно в данной ситуации. Механизм предсказания
переходов полностью дезориентируется: второй переход может не предсказаться,
даже если он должен предсказаться и наоборот. И это проблема остается, в
следующий раз переход снова не будет предсказан. Более того, любой
безусловный переход или вызов подпрограммы потребуют дополнительных тактов,
если будут стоять первой инструкцией после перехода. Для того что бы избегать
этого вы должны избегать использовать инструкции переходов или вызовов
подпрограмм сразу в начале новой ветви. Пример:

DEC EAX
JNZ L1
CMP EBX,ECX
NOP
JB L2
...
L1: NOP
NOP
CALL P
...
L2: NOP
RET

Pentium MMX так же может потребоваться дополнительные такты, но только в том
случае если две инструкции ветвления находятся в одном, выровненном, блоке
DWORD. Эту проблему можно решить использовав переход near вместо short во
второй инструкции ветвления, что бы сделать ее длиннее, но этот метод не
поможет вам на Pentium без MMX, так что вам придется использовать инструкции
типа NOP, что бы решить эту проблему на обоих процессорах.

Алгоритм предсказания переходов наиболее оптимален для цикла, где проверка
расположена в конце, как в этом примере:

MOV ECX, [N]
L: MOV [EDI],EAX
ADD EDI,4
DEC ECX
JNZ L

Поскольку алгоритм предсказания переходов на Pentium без MMX несимметричный,
то могут встретиться участки, где можно добиться ускорения путем
преобразования кода. Рассмотрим следующую конструкцию:

TEST EAX,EAX
JNZ SHORT A1
CALL F0
JMP SHORT E
A1: CALL F1
E:

Если F0 вызывается более часто, чем F1, а F1 редко вызывается дважды, то вы
можете улучшить предсказание переходов поменяв местами обе ветви. Тем не менее
это будет немножко неоптимально для Pentium MMX и Pentium Pro, которые могут
не предсказывать переход, если его не в буффере предсказания переходов. Другой
причиной целесообразности обмена может служить то, что кеш кода используется
не эффективно, когда реже используемый переход исполняется раньше. Вы можете
вставить два NOP перед каждым CALL, что бы предотвратить замедление в случае
неправильного предсказания перехода.

Многократные переходы (структуры case) лучше всего реализуются на Pentium без
MMX списком адресов переходов. при этом адреса переходов или вызовов
подпрограмм хранятся в сегменте данных, а не кода. На Pentium MMX и Pentium
PRO косвенные переходы должны быть предсказуемы, для максимальной
эффективности, таким образом на этих процессорах лучше использовать
множественные двунаправленные ветвления.

Все вызовы подпрограмм должны быть снабжены инструкциями возврата, т.к. эти
инструкции правильно предсказываются на Pentium MMX и Pentium Pro.

Избеганее ветвления
-------------------
Иногда можно получить тот же эффект, не используя ветвления, всего лишь
удачной манипуляцией битов и флагов. Например мы можем вычислить абсолютное
значение числа со знаком, не используя ветвления:
MOV EDX,EAX
SAR EDX,31
XOR EAX,EDX
SUB EAX,EDX

Флаг переноса очень полезен, для такого рода трюков.
Устанавливается если величина ноль: CMP [значение], 1
Устанавливается, если величина не ноль: XOR EAX, EAX / CMP EAX, [значение]
Увеличение счетчика, если перенос: ADC EAX, 0
Установка бита каждый раз, когда перенос: RCL EAX, 1
Создает битовую маску, если перенос: SBB, EAX, EAX

Этот пример находит минимум из двух без значных чисел: если (b<a), то a=b;
SUB EBX,EAX
SBB ECX,ECX
AND ECX,EBX
ADD EAX,ECX

Этот пример выбирает между двумя числами: если (a не 0), то a=b иначе a = c;
CMP EAX,1
SBB EAX,EAX
AND ECX,EAX
XOR EAX,-1
AND EAX,EBX
OR EAX,ECX

В любом случае, подобные трюки применяются для того что бы уменьшить
количество условных переходов и избавиться от не правильных предсказаний
переходов, кроме того, появляются возможности для спаривания освобожденного
от ветвления кода.

На Pentium Pro вы можете использовать инструкции условного перемещения для
того, что бы избавиться от лишнего ветвления.


13. ПРЕФИКСЫ
============
Инструкция с одним или более префиксами не сможет выполниться в V-трубе
(смотри раздел 8.7), кроме того требуется один такт, для декодирования
каждого префикса, за исключением 0Fh на Pentium MMX и Pentium Pro.

Префиксы адреса, могут быть анулированны использованием 32 битного режима.
Сегментные префиксы могут быть анулированны в 32 битном режиме, использованием
модели памяти FLAT.
Префиксы размера операнда могут быть анулированны в 32 битном режиме, если вы
будете пользоваться только 8 или 32 битными числами.

Там, где без префиксов не обойтись, их декодирование может быть замаскированно
если предыдущая инструкция исполнялась более одного такта. Для Pentium без MMX
существует правило - любая инструкция, исполняющаяся N тактов (не
декодирующаяся) может "затмевать" N-1 префикс для следующих двух (иногда трех)
инструкций. Другими словами, каждый такт (кроме первого), который использует
инструкция, что бы выполниться может быть использован для декодирования
префикса последующей инструкции. Этот эффект может распространяться на
предсказанную ветвь. Любая инструкция, которая использует дополнительные такты
из-за остановки AGI, промаха кеша, рассогласования, или любой друго причины,
кроме задержки декодирования и не правильного предсказания перехода может
"затмевать" декодировку префиксов последующих команд. На Pentium MMX
неспаренные команды так же могут создавать эффект "затемнения".

Примеры:

CLD / REP MOVSD
Инструкция CLD исполняется два такта, по этому второй такт будет использован
для того, что бы "затемнить" декодирование префикса REP. Код использует
два такта, если CLD будет стоять "далеко" от REP MOVSD.

CMP DWORD PTR [EBX],0 / MOV EAX,0 / SETNZ AL
Инструкция CMP, так же, будет исполняться два такта, потому что это инструкция
чтения/модифицирования. Префикс 0Fh инструкции SETNZ будет декодирован во
время второго такта инструкции CMP, таким образом процесс декодирования будет
скрыт.


14. УМЕНЬШЕНИЕ ДЛИНЫ КОДА
=========================
Как объяснялось в разделе 6, размер кеша кода - 8Кб. Если у вас проблемы с
размещением критической части вашего кода в кеше, то вы можете рассмотреть
возможность уменьшения вашего кода.

32 битный код, обычно, длиннее 16 битного, поскольку адресные константы
занимают по 4 байта, а не по 2, как в 16 битном. Тем не менее 16 битный код
имеет другие недостатки, т.н. префиксы (напоминаю, что мы рассматриваем модель
FLAT. - прим. переводчика), или проблемы со смежными словами (см. раздел 10).
Однако далее мы обсудим некоторые другие способы уменьшения кода.

Многие адреса переходов, адреса данных и константы займут меньше места, если
они могут быть представлены как байт со знаком, т.е. они находятся в пределах
от -128 до +127.

Для переходов это будет означать, что инструкция займет на 2 байта меньше, а
ведь инструкции перехода за пределы 127 байт займут 5 байт для условного
перехода и 6 для безусловного.

Подобным образом, адреса данных займут меньший объем, если их можно выразить
как указатель и смещение в пределах -128..+127 байт.
Пример:
MOV EBX,DS:[100000] / ADD EBX,DS:[100004] ; 12 байт
Уменьшено до:
MOV EAX,100000 / MOV EBX,[EAX] / ADD EBX,[EAX+4] ; 10 байт

Преимущество использования указателей становиться очевидней, если вы
используете их много раз. Храня данные в стеке и используя EBP или ESP в
качестве указателя, вы сможете во много раз уменьшить размер вашего кода,
чем когда вы использовали статические позиции памяти и абсолютные адреса,
оговаривая конечно, что ваши данные должны быть в пределах 128 байт.
Использование PUSH и POP, для чтения временных данных всегда предпочтительней.

Константы данных, так же займут меньше места, если значение в пределах -128 и
+127 байт. Многие инструкции, с непосредственным значением операнда могут
быть короче, если операнд в пределах 127 байт. Например:
PUSH 200 ; 5 байт
PUSH 100 ; 2 байта

ADD EBX,128 ; 6 байт
SUB EBX,-128 ; 3 байта

Однако наиболее важная инструкция, с непосредственным значением операнда не
имеет такой формы. Пример:
MOV EAX, 1 ; 5 байт
XOR EAX,EAX / INC EAX ; 3 байта
PUSH 1 / POP EAX ; 3 байта

Если константа используется много раз, то, конечно, ее лучше загрузить в
регистр. Пример:
MOV DWORD PTR [EBX],0 / MOV DWORD PTR [EBX+4],0 ; 13 байт
XOR EAX,EAX / MOV [EBX],EAX / MOV [EBX+4],EAX ; 7 байт

Есть и другие инструкции, с разными длинами. Следующие инструкции имеют длину -
1 байт, чем очень привлекательны: PUSH reg, POP reg, INC reg32, DEC reg32.
INC и DEC с 8 битным регистром занимают 2 байта, таким образом INC EAX будет
короче INC AL.

XCHG EAX,reg также однобайтная инструкция, таким образом она займет меньше
места, чем MOV EAX,reg однако она медленная и неспариваемая.

Некоторые инструкции меньше, в том случае, когда они используют регистр
аккумулятор, а не любой другой регистр. Например:
MOV EAX,DS:[100000] короче чем MOV EBX,DS:[100000]
ADD EAX,1000 короче чем ADD EBX,1000

Инструкции с указателем займут на байт меньше, если у них только базовый
регистр (не ESP) и смещение, чем когда у них есть масштабирование индексного
регистра, или указатель и индексный регистр, или ESP в качестве базового
регистра. Пример:
MOV EAX,[array][EBX] короче чем MOV EAX,[array][EBX*4]
MOV EAX,[EBP+12] короче чем MOV EAX,[ESP+12]

Инструкции с EBP, в качестве базового регистра, без индекса, будут на один
байт больше, чем при использовании других регистров. Пример:
MOV EAX,[EBX] короче чем MOV EAX,[EBP], но
MOV EAX,[EBX+4] столько же MOV EAX,[EBP+4].


15. ПЛАНИРОВАНИЕ ОПЕРАЦИЙ С ПЛАВАЮЩЕЙ ТОЧКОЙ
============================================
Инструкции, оперирующие с плавающей точкой не могут спариваться, так же, как
другие инструкции, за исключением одного особого случая, определяемого
следующими правилами:
- первая инструкция (исполняющаяся в U-трубе) должна быть FLD, FADD, FSUB,
FMUL, FDIV, FCOM, FCHS, or FABS

- вторая инструкция (в V-трубе), должна быть FXCH

- инструкция следующая за FXCH, должна быть инструкцией, оперирующей с
плавающей точкой, в противном случае FXCH - возьмет дополнительный такт.

Этот особый случай спаривания очень важен, я кратко объясню почему.

Хотя инструкции с плавающей точкой не могут спариваться, многие могут
параллелится, т.е. новая инструкция начинается, пока не завершилась старая.
Например:
FADD ST(1),ST(0) ; такты 1-3
FADD ST(2),ST(0) ; такты 2-4
FADD ST(3),ST(0) ; такты 3-5
FADD ST(4),ST(0) ; такты 4-6

Очевидно, что две инструкции не могут перекрываться, если второй инструкции
требуется результат первой. Поскольку почти все инструкции с плавающей точкой
используют верхний регистр стека, ST(0), то на первый взгляд не много
возможностей получить независимые инструкции. Решение этой проблемы -
переименование регистра. Инструкция FXCH на самом деле не обменивает значения
регистров, а только меняет местами их имена. Инструкции вталкивающие или
выталкивающие из стека, так же работают переименованием. Процесс
переименования регистров очень хорошо оптимизирован на Pentium, по этому
процесс переименования может происходить прямо во время доступа к регистру.
Переименование регистра никогда не вызывает остановки AGI - возможна, даже,
неоднократное переименование регистров в одном такте, например когда вы
спариваете FLD или FCOMPP с FXCH.

Правильным использованием FXCH вы можете добиться множественного перекрывание
в вашем коде с плавающей точкой. Пример:

FLD [a1] ; такт 1
FADD [a2] ; такты 2-4
FLD [b1] ; такт 3
FADD [b2] ; такты 4-6
FLD [c1] ; такт 5
FADD [c2] ; такты 6-8
FXCH ST(2) ; такт 6
FADD [a3] ; такты 7-9
FXCH ST(1) ; такт 7
FADD [b3] ; такты 8-10
FXCH ST(2) ; такт 8
FADD [c3] ; такты 9-11
FXCH ST(1) ; такт 9
FADD [a4] ; такты 10-12
FXCH ST(2) ; такт 10
FADD [b4] ; такты 11-13
FXCH ST(1) ; такт 11
FADD [c4] ; такты 12-14
FXCH ST(2) ; такт 12

В вышеуказанном примере, мы используем три независимых потока. Каждый FADD
исполняется 3 такта, так что у нас есть время, чтобы запустить другие FADD.
Когда мы запускаем FADD в потоке "a" у нас есть время, чтобы запустить два
новых FADD в потоке "b" и с "c", до того как мы вернемся к потоку "a", таким
образом все три FADD принадлежат одному потоку. Мы используем инструкцию FXCH,
каждый раз, когда нам надо получить регистр, принадлежащий к желаемому потоку
ST(0). Как вы можете увидеть в вышеуказанном примере, мы создали неплохой
блок, однако не очень хорошо, что FXCH периодически повторяется. Вам придется
основательно "поиграться" с компьютером, чтобы всегда знать какой регистр
используется.

Все виды инструкций FADD, FSUB, FMUL, и FILD исполняются по 3 такта, и могут
перекрываться, таким образом вы можете спаривать их, используя выше описанный
алгоритм. Операнд адреса не занимает больше времени, чем операнд регистра,
если он находиться в кеше L1 и правильно выравнен.

Теперь пришло время познакомиться с правилами исключений, препядствующих
перекрытиям: Вы не можете запустить инструкцию FMUL, такт спустя после другой
инструкции FMUL, поскольку инструкция FMUL не очень хорошо реализована в
конвеере. Рекомендуется ставить другую инструкцию между двумя FMUL. Например:

FLD [a1] ; clock cycle 1
FLD [b1] ; clock cycle 2
FLD [c1] ; clock cycle 3
FXCH ST(2) ; clock cycle 3
FMUL [a2] ; clock cycle 4-6
FXCH ; clock cycle 4
FMUL [b2] ; clock cycle 5-7 (остановка AGI)
FXCH ST(2) ; clock cycle 5
FMUL [c2] ; clock cycle 7-9 (остановка AGI)
FXCH ; clock cycle 7
FSTP [a3] ; clock cycle 8-9
FXCH ; clock cycle 10 (неспарено)
FSTP [b3] ; clock cycle 11-12
FSTP [c3] ; clock cycle 13-14

У нас получилась остановка AGI после FMUL [b2] и перед FMUL [c2], потому что
предыдущий FMUL был запущен в предыдущем такте. Однако, вы можете легко
улучшить этот код, просто вставив инструкцию FLD, между другими FMULами:

FLD [a1] ; такт 1
FMUL [a2] ; такты 2-4
FLD [b1] ; такт 3
FMUL [b2] ; такты 4-6
FLD [c1] ; такт 5
FMUL [c2] ; такты 6-8
FXCH ST(2) ; такт 6
FSTP [a3] ; такты 7-8
FSTP [b3] ; такты 9-10
FSTP [c3] ; такты 11-12

В других случаях вы можете установить FADD, FSUB или что-нибудь еще между
FMUL, чтобы избежать остановки AGI.

Перекрытие инструкций с плавающей точкой предполагает, конечно, что у вас есть
несколько потоков расчетов, которые вы можете чередовать. Если у вас есть
только одна большая формула, то вы можете считать разные ее части параллельно,
для того чтобы достичь перекрытия. Если вы хотите сложить, например, 6 чисел,
то вы можете разделить этот процесс на две части, по 3 числа, а результаты
сложить в конце:

FLD [a] ; clock cycle 1
FADD [b] ; clock cycle 2-4
FLD [c] ; clock cycle 3
FADD [d] ; clock cycle 4-6
FXCH ; clock cycle 4
FADD [e] ; clock cycle 5-7
FXCH ; clock cycle 5
FADD [f] ; clock cycle 7-9 (остановка AGI)
FADD ; clock cycle 10-12 (остановка AGI)

Здесь у нас есть остановка AGI, на один такт, до FADD [f], потому что она
ждет результата FADD [d] и два такта остановки AGI перед последним FADD,
потому что идет ожидание результата FADD [f]. Последнюю остановку AGI можно
замаскировать, вставив несколько целочисленных инструкций, но с первой такой
фокус не пройдет, потому что в таком случае не спариться FXCH.

Первую остановку AGI можно анулировать, если иметь не две, а три потока, но
это будет стоить дополнительного FLD, таким образом мы не выиграем времени и
в этом нет необходимости до тех пор пока мы не складываем по крайней мере
восемь чисел.

Не все инструкции с плавающей точкой могут перекрываться. Но некоторые
инструкции с плавающей точкой могут перекрывать последующие инструкции с
целым операндом. К примеру инструкция FDIV исполняется 39 тактов. Все первые
такты могут перекрываться с целочисленными инструкциями, но только последние
два такта могут перекрыть инструкции с плавающей точкой. Пример:

FDIV ; такты 1-39
FXCH ; такты 1-2
CMC ; такты 3-4
RCR EAX,1 ; такт 5
INC EBX ; такт 5
FADD [x] ; такты 38-40
FXCH ; такты 38
FMUL [y] ; такты 40-42

Сначала FXCH спаривается с FDIV, но использует дополнительный такт из=за того,
что не сопровождает инструкцию с плавающей точкой. CMC начала бы исполняться
сразу после FDIV, но вынуждена ждать окончания FXCH. Инструкции RCR и INC
спариваются. Инструкция FADD начинает исполняться в 38 такте, т.к. инструкции
с плавающей точкой могут начать перекрытие только в двух последних тактах
инструкции FDIV. Следующая инструкция FXCH спаривается с FADD. Инструкция FMUL
ждет окончания FDIV, т.к. использует результат ее деления.

Если у вас нет выбора и приходиться использовать инструкцию с плавающей точкой
после долго исполняемой инструкции типа FDIV или SQRT, то вы можете подставить
адрес из памяти и убедиться, что все значения находятся на уровне L1 кеша.
Например:
FDIV QWORD PTR [EBX]
CMP [ESI],EAX
FMUL QWORD PTR [ESI]
Здесь мы пользуемся перекрытием целочисленной инструкции, предзагружая
значение ESI в кеш(нам абсолютно не важен результат сравнения).

В разделе 21 приведен список полный инструкций с плавающей точкой, а так же
отражены их способности спариваться и перекрываться.

Особого упоминания требует инструкция FST или FSTP с операндом памяти. Эта
инструкция исполняется два такта, но в процессе старта, похоже, начинает
конвертировать значение в ST(0), так что один такт конвеер занят и не готов к
декодированию. Это полностью аналогично остановке AGI. Пример:

FLD [a1] ; такт 1
FADD [a2] ; такты 2-4
FLD [b1] ; такт 3
FADD [b2] ; такты 4-6
FXCH ; такт 4
FSTP [a3] ; такты 6-7
FSTP [b3] ; такты 8-9

FSTP [a3] ждет один такт, поскольку результат FADD [a2] не готов в предыдущем
такте. Во многих случаях этого нельзя замаскировать не разбив вычисление на
четыре пути или не вставив несколько инструкций после. Никакие другие инструкции не имеют этой
странной особенности. Все два такта инструкция FST(P) не может спариваться
или перекрываться с другими инструкциями.

Инструкции с целочисленными операндами, как например FIADD, FISUB, FIMUL,
FIDIV, FICOM могут быть разбиты на более простые операции, и чуть улучшить
перекрытие.
Пример:

FILD [a] ; такты 1-3
FIMUL [b] ; такты 4-9

Разбивается на:

FILD [a] ; такты 1-3
FILD [b] ; такты 2-4
FMUL ; такты 5-7

В данном примере, вы выигрываете два такта, вызывая перекрывание двух
инструкций FILD.


16. ОПТИМИЗАЦИЯ ЦИКЛА
======================
Анализируя программу, вы можете заметить, что нередко до 99% времени программа
проводит во внутренних циклах. Путем к увеличению скорости может стать
использование в наиболее критических частях цикла вставок ассемблерного кода.
Остальная часть программы может быть составлена на языке высокого уровня.

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

Исходный код этой процедуры на языке C будет выглядеть следующим образом:

void ChangeSign (int * A, int * B, int N) {
int i;
for (i=0; i<N; i++) B[i] = -A[i];}

Переведя ее на ассемблер, мы получим что-то вроде этого:

Пример 1:

_ChangeSign PROCEDURE NEAR
PUSH ESI
PUSH EDI
A EQU DWORD PTR [ESP+12]
B EQU DWORD PTR [ESP+16]
N EQU DWORD PTR [ESP+20]

MOV ECX, [N]
JECXZ L2
MOV ESI, [A]
MOV EDI, [B]
CLD
L1: LODSD
NEG EAX
STOSD
LOOP L1
L2: POP EDI
POP ESI
RET ; (ни каких других pop если мы используем соглашения C)
_ChangeSign ENDP

Это похоже на хорошее решение, но оно не оптимально, потому что в цикле
используются медленные не спаривающиеся инструкции. Цикл исполниться за
11 тактов, если все данные на уровне L1 кеша.

Используем только спариваемые инструкции
----------------------------------------

Пример 2:

MOV ECX, [N]
MOV ESI, [A]
TEST ECX, ECX
JZ SHORT L2
MOV EDI, [B]
L1: MOV EAX, [ESI] ; u
XOR EBX, EBX ; v (спаривание)
ADD ESI, 4 ; u
SUB EBX, EAX ; v (спаривание)
MOV [EDI], EBX ; u
ADD EDI, 4 ; v (спаривание)
DEC ECX ; u
JNZ L1 ; v (спаривание)
L2:

Здесь мы использовали только спариваемые инструкции, и спланировали цикл так,
что все инструкции спарились. Теперь одна итерация исполняется за 4 такта. Мы
могли бы получить ту же скорость не разбивая инструкцию NEG, но тогда нам
придется разбить другие не спаривающиеся инструкции.

Использование одного регистра для счетчика и индекса
----------------------------------------------------

Пример 3:

MOV ESI, [A]
MOV EDI, [B]
MOV ECX, [N]
XOR EDX, EDX
TEST ECX, ECX
JZ SHORT L2
L1: MOV EAX, [ESI+4*EDX] ; u
NEG EAX ; u
MOV [EDI+4*EDX], EAX ; u
INC EDX ; v (спаривание)
CMP EDX, ECX ; u
JB L1 ; v (спаривание)
L2:

Используя один регистр как счетчик и как индекс мы задействуем меньше
инструкций в теле цикла, но он все еще исполняется за 4 такта, поскольку у нас
есть две неспаренные инструкции.

Счетчик, стремящийся к нулю
---------------------------
Желая избавиться от инструкции CMP, из примера 3, мы могли бы обозначить конец
цикла нулевым значением счетчика и использовать флаг нуля (ZF) для обнаружения
конца цикла, как это мы делали в примере 2. Единственный путь сделать это -
исполнить цикл наоборот, читая последние элементы первыми. Правда, кеш данных
оптимизировался для доступа к поступающим подряд данным, а не наоборот, так
что постоянные промахи кеша весьма вероятны, что бы избегать этого мы должны
запускать счетчик от -N и увеличивать до нуля. А регистр базы должен указывать
на конец массива, а не на начало:

Пример 4:

MOV ESI, [A]
MOV EAX, [N]
MOV EDI, [B]
XOR ECX, ECX
LEA ESI, [ESI+4*EAX] ; указатель на конец массива A
SUB ECX, EAX ; -N
LEA EDI, [EDI+4*EAX] ; указатель на конец массива B
JZ SHORT L2
L1: MOV EAX, [ESI+4*ECX] ; u
NEG EAX ; u
MOV [EDI+4*ECX], EAX ; u
INC ECX ; v (спаривание)
JNZ L1 ; u
L2:

Теперь у нас пять инструкций в теле цикла, но на исполнение по прежнему
тратиться 4 такта из-за плохого спаривания. (Если адреса и длины массивов
постоянны, то мы можем сохранить два регистра заменяя A+SIZE для ESI и B+SIZE
для EDI). А теперь давайте посмотрим как можно улучшить спаривание.

Спаривание расчета с управлением цикла
--------------------------------------
Мы можем захотеть ускорить цикл, спаривая инструкции его тела с инструкциями,
контролирующими цикл. Если мы установим что-нибудь между INC ECX и JNZ L1, то
это что-нибудь не должно влиять на флаг нуля (ZF). Инструкция
MOV [EDI+4*ECX], EBX после INC ECX создаст остановку AGI, таким образом нам
нужен более удачный код:

Пример 5:

MOV EAX, [N]
XOR ECX, ECX
SHL EAX, 2 ; 4 * N
JZ SHORT L3
MOV ESI, [A]
MOV EDI, [B]
SUB ECX, EAX ; - 4 * N
ADD ESI, EAX ; указатель на конец массива A
ADD EDI, EAX ; указатель на конец массива B
JMP SHORT L2
L1: MOV [EDI+ECX-4], EAX ; u
L2: MOV EAX, [ESI+ECX] ; v (спаривание)
XOR EAX, -1 ; u
ADD ECX, 4 ; v (спаривание)
INC EAX ; u
JNC L1 ; v (спаривание)
MOV [EDI+ECX-4], EAX
L3:

Здесь я использовал другой способ смены знака у EAX: инвертирование всех бит,
с последующим увеличением на 1. Причина, по которой я использовал этот метод
в том, что я могу использовать один грязный трюк инструкции INC: инструкция
INC не изменяет флаг переноса (CF), тогда как ADD изменяет. А используя ADD
вместо INC для увеличения счетчика моего цикла, я могу использовать как
признак окончания цикла флаг переноса (CF), а не флаг нуля (ZF). Таким образом
становиться возможным вставить INC EAX не меняя флага переноса. Вы можете
подумать, что можно бы было использовать LEA EAX, [EAX+1] вместо INC EAX,
которая тоже не изменить флаг, но инструкция LEA может вызвать остановку AGI,
так что это не лучший вариант.

Здесь я получил лучшее спаривание и теперь цикл исполняется за 3 такта. Хотите
ли вы увеличивать значение цикла на 1 (как в примере 4) или на 4 (как в
примере 5) - дело вкуса, для цикла это не принципиально.

Перекрытие операций
-------------------
Метод, используемый в примере 5 не очень удобный, потому что мы можем
использовать другие способы для улучшения спаривания. Один из способов
реорганизовать цикл - совместить конец одной операции с началом следующей. Я
буду называть это - свернутый цикл. Свернутый цикл оставляет незаконченную
операцию в каждой итерации, эта операция будет завершена в следующей итерации.
Например в примере 5 последний MOV одной итерации спаривается с первым MOV
следующей, но давайте рассмотрим его:

Пример 6:

MOV ESI, [A]
MOV EAX, [N]
MOV EDI, [B]
XOR ECX, ECX
LEA ESI, [ESI+4*EAX] ; указатель на конец массива A
SUB ECX, EAX ; -N
LEA EDI, [EDI+4*EAX] ; указатель на конец массива B
JZ SHORT L3
XOR EBX, EBX
MOV EAX, [ESI+4*ECX]
INC ECX
JZ SHORT L2
L1: SUB EBX, EAX ; u
MOV EAX, [ESI+4*ECX] ; v (спаривание)
MOV [EDI+4*ECX-4], EBX ; u
INC ECX ; v (спаривание)
MOV EBX, 0 ; u
JNZ L1 ; v (спаривание)
L2: SUB EBX, EAX
MOV [EDI+4*ECX-4], EBX
L3:

Здесь мы начинаем считывать вторую величину до того как сохранили первую, что
дает возможность улучшить спаривание. Инструкция MOV EBX, 0 вставлена между
INC ECX и JNZ L1 не для улучшения спаривания, а для того что бы избежать
остановки AGI.

Развертывание цикла
-------------------
Наиболее часто предлагаемая схема улучшения цикла - это исполнение двух
операций вместо одной за каждую итерацию, таким образом вдвое снижая
необходимое количество повторений цикла. Это называется развертыванием
цикла.

Пример 7:

MOV ESI, [A]
MOV EAX, [N]
MOV EDI, [B]
XOR ECX, ECX
LEA ESI, [ESI+4*EAX] ; указатель на конец массива A
SUB ECX, EAX ; -N
LEA EDI, [EDI+4*EAX] ; указатель на конец массива B
JZ SHORT L2
TEST AL,1 ; тест на нечетность N
JZ SHORT L1
MOV EAX, [ESI+4*ECX] ; N - нечетное. делаем четным
NEG EAX
MOV [EDI+4*ECX], EAX
INC ECX ; выравниваем счетчик
JZ SHORT L2 ; N = 1
L1: MOV EAX, [ESI+4*ECX] ; u
MOV EBX, [ESI+4*ECX+4] ; v (спаривание)
NEG EAX ; u
NEG EBX ; u
MOV [EDI+4*ECX], EAX ; u
MOV [EDI+4*ECX+4], EBX ; v (спаривание)
ADD ECX, 2 ; u
JNZ L1 ; v (спаривание)
L2:

Мы делаем две операции параллельно, достигая лучшего спаривания. Мы проверяем
N на нечетность и если это так, то выполняем одну операцию за пределами цикла,
поскольку цикл теперь может выполнить только четное количество операций.

У цикла есть остановка AGI в первой инструкции MOV, поскольку ECX был изменен
в предыдущем такте цикла. У цикла уходит 6 тактов на две операции.

Реорганизация цикла, для удаления остановки AGI
-----------------------------------------------
Пример 8:

MOV ESI, [A]
MOV EAX, [N]
MOV EDI, [B]
XOR ECX, ECX
LEA ESI, [ESI+4*EAX] ; указатель на конец массива A
SUB ECX, EAX ; -N
LEA EDI, [EDI+4*EAX] ; указатель на конец массива B
JZ SHORT L3
TEST AL,1 ; тест на нечетность N
JZ L2
MOV EAX, [ESI+4*ECX] ; N - нечетное. делаем четным
NEG EAX ; нет возможности спариться
MOV [EDI+4*ECX-4], EAX
INC ECX ; выравниваем счетчик
JNZ L2
NOP ; добавляем NOPы если JNZ L2 не
NOP ; предсказан.
JMP L3 ; N = 1
L1: NEG EAX ; u
NEG EBX ; u
MOV [EDI+4*ECX-8], EAX ; u
MOV [EDI+4*ECX-4], EBX ; v (спаривание)
L2: MOV EAX, [ESI+4*ECX] ; u
MOV EBX, [ESI+4*ECX+4] ; v (спаривание)
ADD ECX, 2 ; u
JNZ L1 ; v (спаривание)
NEG EAX
NEG EBX
MOV [EDI+4*ECX-8], EAX
MOV [EDI+4*ECX-4], EBX
L3:

Хитрость в том, что бы найти спаривающиеся инструкции, таким образом, что бы
не использовать счетчик цикла как индекс и реорганизовать цикл так, что бы
счетчик цикла увеличивался в предыдущем такте. Таким образом мы достигли
5 тактов для двух операций, приблизившись к лучшему из возможных решению.

Если кеширование данных является критическим, то можно увеличить скорость
объединив массивы A и B в одну структуру что бы каждое B[i] находилось после
соответствующего A[i]. Если структура массива выравнена, то по крайней мере
на 8, то B[i] будет всегда находиться в той же строке, что и A[i] и у вас
никогда не будет промахов кеша при записи B[i]. Конечно это приведет к
усложнению других частей программы, так что вы должны взвесить все
преимущества и недостатки.

Развертывание более чем в 2 раза
--------------------------------
Вы можете подумать, что исполнение более двух операций за одну итерацию
приведет к еще большей производительности цикла. Правда потери цикла не всегда
могут сводиться к одному такту за итерацию, так что развертывая цикл в 4 раза,
вместо 2 вы сохраните только 1/4 такта за итерацию, эффект за который вряд ли
стоит бороться. Только если потери не сводятся к одному такту и N велико
стоит думать о развертывании в 4 раза.

Недостатки через мерного развертывания цикла:
1. Вам необходимо вычислить остаток от деления N на R, где R - коофициент
развертки и выполнять этот остаток до или после основного цикла, что бы
сделать кол-во операций делимым на R. Это потребует дополнительного кода с
плохо предсказуемым ветвлением. И, разумеется, увеличиться тело цикла.
2. Код, обычно, требует больше времени при первом исполнении, чем при повторах
и большое тело цикла будет выполняться дольше первый раз, что актуально, если
N невелико.
3. Через мерный размер кода уменьшает эффективность кеша кода.

Обработка команд с 8 или 16 битными оперндами - частями 32 битных регистров
---------------------------------------------------------------------------
Если вам надо манипулировать




Powered by phpBB 2.0.6 © 2001-2004 phpBB Group
Theme created by Vjacheslav Trushkin


 

Member
Статус: Не в сети
Регистрация: 28.02.2005
Откуда: Брянск
serj_ Всеравно xor reg,reg, а у нас mov reg,imm - то есть загрузка из памяти - но ты прав, это фигня в таком цикле :). Я естественно могу быть не прав, так что не злись :wink:.
Locki Вот ты в асм и скатился... А со времен 586'ых прошло уже почти 10 лет - товариши из Интел и АМД придумали всякие SSE и 3DNow, процессоры поумнели и нарастили жирок из кэша, так что некоторые вещи могут наоборот стопорнуть (например чтение 32битн. регистра после модиф. его 8/16битн. его части вызывает большие задержки).


 

Member
Статус: Не в сети
Регистрация: 31.01.2004
Откуда: moskow
serj_ Как самому сделать это выравнивание (align 16)
например в:
=>начало цикла
asm
mov eax,i
mov edx,i
add eax, edx
add eax, edx
add eax, edx
add eax, edx
...
end;
=>конец цикла
Сколько раз нужно повторить "add" для выравнивания? Или я че то неправильно опять понимаю?


 

Member
Статус: Не в сети
Регистрация: 28.02.2005
Откуда: Брянск
Locki Надо не add, а nop вставлять.

Добавлено спустя 2 минуты, 8 секунд:
Кстати Дельфи выравниваает по 8 байтам (по крайней мере 5, в настройках где-то было)


 

Advanced member
Статус: Не в сети
Регистрация: 10.04.2003
Откуда: Москва
Locki, сделай проще.
Напиши первую инструкцию цикла в таком виде:
Код:
cycle:
mov eax,12345678h
...

Потом открой полученный exe с помощью hiew (или другого) и поищи число 12345678h.
Посмотри, выровнена инструкция на границу 16 или нет. Если выровнена, то что беспокоиться? :)
Это для примера, потом этот оператор выкини из цикла.
NOP вставлять плохо - он чертовски долго выполняется на P4 (а на К7/К8 просто выкидывается на ранней стадии декомпиляции команд).
При выравнивании компилятор вставляет другие 'бессмысленные' команды. Такк что ... не удивляйся, если перед mov eax,12345678h будут какие-то бредовые команды. :)


Показать сообщения за:  Поле сортировки  
Начать новую тему Новая тема / Ответить на тему Ответить  Сообщений: 71 • Страница 1 из 41  2  3  4  >
-

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Перейти:  
Создано на основе phpBB® Forum Software © phpBB Group
Русская поддержка phpBB | Kolobok smiles © Aiwan