Анимация
JavaScript
|
Главная Библионтека тую можно сделать гораздо более эффективую его реализацию путем оптимизации алгорит, ма, чем привлечением всех возможных средств оптимизации к никудышнему алгоритму. Затем следует выделить критические участки кода. Зачастую, более чем 99% времени процессора тратится на исполнение внутреннего цикла профаммы. В этом случае нужно провести оптимизацию на ассемблере только этого цикла, а остальную часть профаммы оставить на языке высокого уровня. Некоторые профаммисты фатят офомное количество сил на оптимизацию на языке ассемблер некритичных участков кода своей профаммы, и единственным значительным результатом этого становятся дополнительные фудности в ее дальнейшей разработке и поддержке по причине сложности профаммного кода. Если не совсем понятно, какие части профаммы являются критическими, можно привлечь специальный инсфументарий, способный провести анализ временных затрат на исполнение алгоритма и указать на критичные участки кода (большинство современных сред разработки на языках высокого уровня обладают подобным инсфументарием -так называемыми профайлерами). Если выясняется, что узким местом является доступ к диску, следует модифицировать алгоритм так, чтобы сделать доступ к диску последовательным (для повышения эффективности использования дискового), а не переключаться на ассемблерное профаммирование. Если узким местом является вывод фафики, можно поискать путь к уменьшению числа вызовов фафических процедур. Некоторые современные высокоуровневые компиляторы осуществляют относительно хорошую оптимизацию результирующего кода для конкретных поколений микропроцессоров, но дальнейшая "ручная" оптимизация обычно дает более ощутимые результаты. 6.2. Дополнительные источники Множество полезной литературы и описаний можно бесплатно скачать с сайта Intel htф: www.intel.com или там же заказать их на CD-ROM. Для дальнейшего изучения методов оптимизации желательно познакомиться с этой литературой для получения представления об архитектуре микропроцессоров. Найти нужные документы можно, используя функцию поиска на сайте http: developer.intel.com или перейдя по одной из ссылок, которые находятся на http: www.agner.org/assem . Некоторые документы храняться в формате PDF. Если у вас нет профаммного обеспечения для просмофа и распечатки файлов PDF, можно скачать бесплатный просмотр-щик файлов PDF - Acrobat Reader с www.adobe.com. Утилита VTUNE от Intel предназначена для оптимизации кода. Ее также можно найти в системе поиска на сайтах Intel. Кроме Intel существует множество другой полезной информации. Эти источники перечислены в FAQ фуппы новостей comp.lang.asm.x86. Также следует обратить внимание на ссылки, приведенные на www.agner.org/assem. 53. Вызов ассемблерньк функций из шыков высокого уровня В большинстве современных сред разработки на языках высокого уровня доступно использование встроенного ассемблера или, по крайней мере, линковка с процедурами, не написанными на нем. Также большинство компиляторов обладают способностью промежуточной фансляции с языка высокого уровня в ассемблерный код. Использование этой возможности предоставляет способ получения информации о семантике вызова функций и работе с данными профаммы на языке ассемблер, а также, естественно, путь к ее оптимизации. Методы вызова функций и способы их именования могут быть достаточно сложными. Существует много различных соглашений о вызовах функций, и многие компиляторы не совместимы друг с другом в этом отношении. Если фебуется вызов ассемблерной процедуры, например из C+-i-, самым логичным будет ее объявляление как extern "С" Hcdecl. К имени ассемблерной функции нужно прибавить символ подчеркивания и компилировать ее следует, указав в опциях фебование чувствительности к регисфу в именах функций и переменных. Если нужно использовать перефужаемые функции, перефужаемые операторы, функции-члены классов и другие расширения С++, тогда следует сначала написать код, например на С++, и с помощью компилятора перевести его в ассемблер, чтобы получить верную информацию о линковке и методе вызова. Вот пример перефуженной функции вычисления квадратного корня. ; int square (int х); SQUARE I PROC NEAR квадратного корня @square$qi LABEL NEAR ?square@@YAHH@Z LABEL NEAR Microsoft square Fi LABEL NEAR ; имя, создаваемое компилятором Gnu PUBLIC @square$qi, ?square@@YAHH@Z, square Fi MOV EAX, [ESP+4] IMUL EAX SQUARE I ENDP ; целочисленная функция вычисления ; имя, создаваемое компилятором Borland ; имя, создаваемое компилятором ; double square (double х); SQUARE D PROC NEAR с двойной точностью @square$qd LABEL NEAR функция вычисления квадратного корня ; имя, создаваемое компилятором Borland ; имя, создаваемое компилятором ?square@@YANN@Z LABEL NEAR Microsoft square Fd LABEL NEAR ; имя, создаваемое компилятором Gnu PUBLIC @square$qd, ?square@dYANN@Z, square Fd FLD QWORD PTR [ESP+4] FMUL ST(0), ST(0) SQUARE D ENDP Способ передачи параметров зависит от используемого соглашения о вызове подпрограммы. В табл. 6.2 приводятся наиболее распространенные соглашения о вызовах функций, подробное их обсуждение велось в гл. 5 данной книги: Табл. 6.2. Соглашения о вызовах функций
Кроме соглашения на вызов функции, в каждой ОС существуют правила использования регистров. Приведем, в качестве примера, правила использования регистров процессора при вызове подпрограмм на языке ассемблера в системах семейства ОС Windows. Использование регистров в 16-битном режиме Windows для С или С++: 16-битное значение возвращается в АХ, 32-битное значение - в DX:AX, значение с плавающей запятой - в ST(0). Регистры АХ, ВХ, СХ, DX, ES и арифметические флаги могут быть изменены процедурой; другие регистры должны быть сохранены и восстановлены. Процедура должна полагаться на то, что при вызове другой процедуры значение SI, D1, BP, DS и SS не изменится. Использование регистров в 32-битном режиме Windows, С++ и других языках программи-рования: целочисленное значение возвращается в ЕАХ, значение с плавающей точкой возвращается в ST(0). Регистры ЕАХ, ЕСХ, EDX (но не ЕВХ) могут быть изменены процедурой; все другие регистры должны быть сохранены и восстановлены. Сегментные регистры нельзя изменять даже временно. CS, DS, ES и SS указывают на плоский сегмент. FS используется операционной системой. GS не используется, но зарезервирован на будущее. Флаги могут меняться процедурой, но со следующими офаничениями: флаг направления равен О по умолчанию. Его можно изменить временно, но при выходе из процедуры необходимо очистить. Флаг прерывания не может быть очищен. Стек математического сопроцессора пуст при входе в процедуру и должен быть пустым при выходе, если только ST(0) не используется для возвращения значения. Регисфы ММХ можно изменять, и, если это бьшо сделано, их нужно очистить с помощью инсфукции EMMS перед возвращением или вызовом любой другой процедуры, которая может их использовать. Все ХММ регисфы можно свободно изменять. Правила для передачи парамефов и возвращения значений через регисфЫ ХММ объяснены g документе Intel АР 589. Процедура может полагаться на неизменность ЕВХ, ESI, EDI, ЕВР и всех сегментных регисфов при вызове другой процедуры. 6.4. Отладка Отладка ассемблерного кода - довольно трудоемкий и неприятный процесс. Перед разработкой оптимизированной процедуры на ассемблере, реализующей некоторый алгоритм, желательно сначала написать ее как подпрофамму на языке высокого уровня. Затем с помощью тестовой профаммы опять же на языке высокого уровня производится отладка самого алгоритма на предмет удовлетворения всем условиям ветвления и выравнивания. После этого осуществляется перевод задачи на язык ассемблера. Теперь можно начинать оптимизацию. Каждый раз, когда будут вноситься изменения в разрабатываемый код, можно осуществлять запуск тестовой профаммы, чтобы убедиться, что она работает. Нумеруйте все промежуточные версии и сохраняйте их отдельно, чтобы в случае ошибки можно было вернуться к одной из рабочих версий. Замерьте скорость наиболее критичных участков кода профаммы с помощью метода, изложенного в разд. 6.30. или с помощью тестовой профаммы. Если код значительно медленнее, чем ожидалось, возможны следующие проблемы: неправильно используется кэш (разд. 6.7); невыравненны операнды (разд. 6.6); цена первого запуска (разд. 6.8); неправильное предсказание переходов (разд. 6.22); проблемы зафузки кода (разд. 6.15); потери скорости при чтении регисфа (разд. 6.16); долгая цепь зависимости (разд. 6.20). 6.5. Модель памяти Процессоры семейства Pentium спроектированны в основном для работы в 32-битном режиме, и, соответственно, при использовании 16-битного кода их производительность существенно снижается. Сегментирование кода и данных также значительно офажается на производительности, поэтому по возможности следует использовать 32-битный плоский режим и операционную систему, которая его поддерживает. Примеры, приводимые в данном руководстве, если иное не оговорено, рассчитаны именно на этот режим работы процессоров.
На процессорах Р1 и РММХ при обращении к невыравненным данным теряется, по меньщей мере, 3 такта при пересечении фаницы в 4 байта. Потери, естественнс будут ощутимее при пересечении фаницы кэща (разд. 6.7). На РРго, Р2 и РЗ невыравненные данные будут стоить 6-12 дополнительных тактов в случае выхода за фаница кэща. Невыравненные операнды, размер которых не превы-щает 16 байтов и не перещедщие фаницу в 32 байта, не приводят к потерям. Выравнивание данных на 8- или 16- байтовую фаницу в стеке двойных слов может стать проблемой. Общий метод рещения - установить выравненный указатель на кадр стека. Функция с выравненными локальными данными может выглядеть примерно так: FuncWithAlign PROC NEAR кадр PUSH EBP MOV EBP, ESP AND EBP, -8 FLD DWORD PTR [ESP+8] SUB ESP, LocalSpace + 4 пролог выравнивание указателя на стека на 8 параметр функции резервируем локальные данные ESP, LocalSpace + 4 POP EBP FuncWithAlign ENDP ; эпилог, восстанавливаем ESP ; (потеря скорости AGI на Pl/PMMX) В то время как выравнивание данных ифает ощутимую роль в достижении максимальной производительности, выравнивание кода не является обязательным для Р1 и РММХ. Принципы выравнивания кода на РРго, Р2 и РЗ изложены в разд. 6.15. 6.7. Кэш у Р1 и РРго 8 килобайт кэщ первого уровня дня кода и 8 килобайт для данных. У РММХ, Р2 и РЗ по 16 килобайт для кода и данных соотвественно. Данные в кэще первого уровня можно читать или перезаписывать за один такт, в то время как выход за его фаницы может стоить множества тактов. Поэтому важно понимать, как работает кэщ, чтобы использовать его наиболее эффективно. Кэщ данных состоит из 256 или 512 рядов по 32 байта в каждом. Каждый раз, когда считывается элемент данных из памяти, который еще не находится в кэще, процессор заполняет весь кэщ-ряд. Кэщ-ряды всегда выравниваются по физическому адресу, кратному 32. Когда происходит обращение к байту по адресу, который кратен 32, чтение или запись в следующие за ним 31 байт не будет стоить практически ничего. Это можно выгодно использовать, организовав данные, которые используются вместе, в блоки по 32 байта. Если, например, есть цикл, в котором обрабатываются два массива, можно их пе регруппировать в один массив сфуктур, чтобы данные, которые используются вместе, находились в памяти рядом друг с другом. Если размер массива кратен 32 байтам, следует и выровнять его по фанице 32 байт. Кэщ процессоров семейства Pentium set-ассоциативен. Это означает, что кэщ-ряд нельзя ассоциировать с произвольным адресом памяти. У каждого кэщ-ряда есть 7-битное значение, которое задает биты 5-11 адреса в физической памяти (биты 0-4 определяются положением элемента данных в кэщ-ряду). У Р1 и РРго два кэщ-ряда для каждого из 128 возможных set-значений, поэтому с определенным адресом в памяти можно ассоциироват! только два жестко заданных кэщ-ряда. В РММХ, Р2 и РЗ - четыре. Следствием этого является то, что кэщ может содержать не более двух или четыре> различных блока данных, у которых совпадают биты 5 - И их адресов. Существует воз- 6.6. Выравнивание Для получения максимальной производительности все данные в оперативной памяти должны быть выравненены так, чтобы их адреса были кратны 2, 4, 8 или 16, согласно информации, приведенной в табл. 6.3. Табл. 6.3. Рекомендуемые границы выравнивания данных для различных поколений семейства микропроцессоров Inte FSTP QWORD PTR [EBP-Loca1 Space] ; теперь сохраняем что-нибудь в ; локальной переменной 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 [ 58 ] 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |