К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 77
Текст из файла (страница 77)
К тому же такая схема копирования не портит кэш второго уровня, оставляя скэшированные ранее ланные в целости-сохранности, что немаловажно для большинства алгоритмов. Но, как бы там все ни было хорошо, обязательно помните, что эти команды работают только на Р-П1+, а на более ранних процессорах (в т. ч. и АМР Агй)оп) генерируют исключение "неверный опкод". Поскольку компьютеры на базе процессоров Р-П и Р-ММХ все еше прололжают использоваться (причем, достаточно широко), следует либо создавать отдельные версии программ лля новых и старых процессоров (но это увеличит трудности по их тестированию и сопровождению), либо перехватывать исключение "неверный опкод" и программно эмулировать отсутствуюшие команды (что приведет к очень большим "тормозам"), либо автоматически определять процессор при старте программы и использовать соответствующую функцию копирования.
(Хорошая идея поместить различные версии функций копирования в различные динамические библиотеки (Р1.1.), избавляясь от условных псреходов внутри функции.) Последний способ, похоже, наиболее предпочтителен, хотя, все же и он не лишен недостатков. Новые команды — это, конечно, хорошо, но есть ли от них хоть какая-то польза, пока на рынке не появятся компиляторы, обеспечивающие их под- Кзш держку? Ну, во-первых, такие компиляторы уже есть и распространяются самой !иге!, а, во-вторых, ничто не мешает программисту использовать ассемблерные вставки в своем любимом компиляторе (правда, не все умеют программировать на ассемблере). Будет большим заблуждением считать, что поддержка компилятором новых команд автоматически увеличит производительность приложения и вся оптимизация сведется к тривиальной перекомпиляции.
Новые команды предлагают новые концепции программирования, что требует кардинального пересмотра алгоритмов программы. А решать алгоритмические задачи до сих пор способен лишь человек — компиляторам это не "по зубам". Писать программы на ассемблере — тоже не лучший выход. Решение, предложенное 1пге1, носит компромиссный характер и заключается во введении встроенных операторов Ппггашс), функционально эквивалентных командам процессора, но обладающих высокоуровневым интерфейсом с "человеческим лицом".
Например, конструкция а ргегььсьшььг *а, ь ьы на самом деле является не функцией, а завуалированным вызовом команды р егеьсь . Встретив ее в тексте программы, компилятор отнюдь не станет вызывать подпрограмму, а непосрелственно вставит соответствующую инструкцию в код, минимилизируя накладные расходы. (Некоторые операторы заменяются не на одну, а на целую группу совместно употребляемых машинных команд, но сути дела это не меняет.) Подробное описание ппппгйс-операторов и соответствующих им машинных инструкций можно найти в справочнике !пге! по командам (Ьиггаслоп 5ег йе)егепсе) и справочном руководстве, прилагаемом к компилятору (йге! С/С++ Сотр!!ег Иегу ОиЫе И'7гп 5иррогг)ог гпе уггеатт~ ЯМР Ехгепяопз 2).
Поясняющие примеры, приведенные в руководстве по оптимизации (1пге) АгспйесГаге ОрГГтцаяоп Ке)егепсе Мата!), в подавляющем большинстве написаны на С с использованием !пгг!пз!с-операторов, и для их понимания необходимо знать: какой !пгг)пз!с-оператор какой команде процессора соответствует (их мнемоники очень часто не совпадают). Поэтому обращаться к таблице соответствий придется независимо от того: программируете ли вы на чистом ассемблере или С (Гопгап). Хорошо, 1пге! справилась с проблемой, но как быть пользователям лругих компиляторов? Не отказываться же от своих любимых продуктов в угоду прогрессу (тем более, что 1ше! предоставляет компиляторы всего лишь двух языков — С~С++ и Гопгап, к тому же распространяет их отнюдь не бесплатно)! Выход состоит в использовании ассемблерных вставок, а точнее даже не ассемблерных, а машинно-кодовых, поскольку сомнительно, чтобы ваш любимый транслятор понимал мнемоники, придуманные после его создания.
В ассемблерах МАЯМ и ТАЯМ ручной ввол кода обычно осуществляется директивой ов, а в компиляторах М!сгоьой %ьца! С++ и Вот!апг! С++ для той же зог Глава 3 цели служит директива еигс. К сожалению, синтаксис ее вызова различен лля каждого из компиляторов, что приводит к проблемам переносимости. Так, М!Ртобоу! 'т(!бца! С++ предваряет директиву еитс одиночным символом прочерка и требует обязательного его помещения в ассемблерный блок. Например: :Фййш~иг Ф,'.Зб';!".Ру!пурпур)ФФв,:4))дание:"ййетщфю~йи;файф рвбв:ф1!~бб(~.-',.' 'яР':ю) 'ф.;!й ин1п() азт "рукотворное" создение инструкции 1вт Охбб (опкод — СР 66) е '.
ОхСР ехпв Охбб Компилятор Вот!апг! С++, напротив, предписывает окантовывать директиву е с двойным символом прочерка с обеих сторон и ожидает его появления вне ассемблерного блока. Например: .' Лй~т~ЩГ $, !'~! вити() "рукотворное" создание инструкции 1ДТ Охбб (опкод — СР бб) екпс (ОхСР, Охбб)) Разумеется, ручной ввод машинных кодов команд — утомительное и требующее определенной квалификации занятие. Проблема в том, что 1пге! 1пмгцс(юп $е! КеГегепсе содержит лищь базовые опкоды инструкций, без перечисления всех возможных способов адресации. Например, опкод инст- руКцИИ р обетсьпте ВЫГЛядИт таК: От 1а уо. ЕСЛИ ПОПЫтатЬСя ЗадатЬ ЕГО С ПОМОЩЬЮ днрЕКтИВЫ етгв Ках етбв (Охт, Охтб, Охс), тО НИЧЕГО НЕ получится! !Кстати, это частая ошибка начинающих.) Ведь инструкция рвете сьпса ожидает Операнда-указателя на адрес памяти, по которому следует произвести предвыборку. Где же он тут? А вот где: обратите Кэш внимание, что последний байт опкода инструкции предварен косой чертой— это обозначает, что приведено не все содержимое байта, а лишь те биты, которые хранят опкод инструкции.
Остальные же определяют тип адресации, указывая процессору: где именно следует искать ее операнды (операнд). В рамках данной книги вряд ли было бы целесообразно подробно рассказывать о формате машинных команд, поэтому автору ничего не остается, как отослать всех интересующихся к первым восьми страницам 1п(е[ 1пз(гцс([оп Бе( Ке)егепсе, где это подробно описано.
Во всяком случае реализации двух необходимых лля оптимизации копирования памяти команд приведены в листинге 3.37. // Функция предвыбирает 32-байтовую строку в Ы-кэы на Р-111 // и 128-байтовую строку в Ь2-кэю на Р-( // Аналог юа ргегегсп((сваг*)юею, Ьая язвт МТА) гогсегп1гпе чо1б гаапса11 рге1еСсбпса(сбаг *х) ( аэт ( пюч еах, (х( ргегепсьпга [еах) ею11 Охт еюгп Ох18 еюгп Охб // Функция копирует 128-бит (16-байт) иэ агс в баг. // Сба указателя деланы быть выровнены по 16-байтовой границе // аналог вв велена рв ( [81оае*) бве, юа 1оаг\ ра ( (61оае*) авгс! ) чогб гогсегп11пе гаэгса11 *ггеаю ору(сьаг "бпг, сьаг *агс) ( апю пюч еах, [агс) пюч едх, (бап) юочара хпнпб, оногб ргг (еах) екпс Охг 304 Глава 3 е ое Охза епоп Охв поппПрп охопа реп ~еах~, хпппв еаае Охг епг.е Ох2В еаае Ох2 Лару слов о способах вызова "рукотворных" команд.
Это только кажется, что все просто, а на самом же деле, на этом пути сплошное нагромождение ловушек, трудностей и проблем. Вот только некоторые из них. Достаточно очевидно, что оформлять одиночные процессорные команды в виде овеет- или ппа. ы-функций невыгодно — зачем разбрасываться командами процессора? (Хотя, к чести Р-Л(/Р-4 стоит сказать, что при его скорости накладными расходами на вызовы функций можно безболезненно пренебречь.) Отсюда и появляется квалификатор еопоехоххпе, предписывающий компилятору встраивать вызываемую функцию непосредственно в тело вызывающей.
Правда, компилятор — "животное упрямое" и "душа" его — потемки. Встраиванию функции препятствует целый ряд противопоказаний (см. опи- СаНИЕ КаапнфИКатОРа пт В ПРИЛаГаЕМОй К КОМПИЛЯТОРУ ДОКУМЕНтаЦИИ). Так, например, встраиваемыми не могут быть голые (пп?сед) функции — т. е. функции без "пролога и эпилога", часто используемые программистами, не полагающимися на удаление избыточного кода оптимизатором.
Кстати, об оптимизаторе. Соглашение М(сговой о быстрых вызовах (см. ключевое слово г и 22) предписывает передавать первый аргумент функции в регистре ЕСХ, а второй — в ЕОХ. Так почему же в ранее приведенных примерах автор пересылает содержимое первого регистра в ЕАХ, если можно преспокойно обратиться к регистру ЕСХ? Увы, коварство оптимизатора этого не позволяет. Обнаружив, что на аргументы функции явных ссылок нет (и, конечно же, благополучно "забыв" о регистрах), оптимизатор "думает" — а зачем передавать эти аргументы, если они не используются? И не только "думает", но и не передает! В результате, в регистрах оказывается неинициализированный "мусор", и функция, естественно, не работает! К сожалению, любое обращение к аргументам из ассемблерного блока приводит к автоматическому созданию фрейма, адресуемого через регистр ЕВР, т.
е. аргументы функции передаются не через регистры, а через локальные стековые переменные! Словом, избежать накладных расходов таким путем, увы, не удается. Впрочем, эти расходы не слишком-то велики и ими, скрепя сердце, можно пренебречь. Кэш 305 С алгоритмом оптимизированного копирования тоже не все безоблачно. Пример реализации, приведенный в !пгег ЛгслГгеггиге Орйтггшгоп Яе!егелсе МапнаГ' — руководстве по оптимизации под процессоры Р-П и Р-Ш (Оп)ег пцпзЬег 245!27-001), содержит множество ошибок (вообше такое впечатление, что это руководство готовили в страшной спешке, — вот что значит первая ревизия!).
К тому же, пример, реализующий этот алгоритм, не оптимш1ен под процессор Р-4, а пример, приведенный в руководстве оптимизации по Р-4, не оптимален под Р-Ш! Поэтому просто скопировать код программы в буфер обмена и откомпилировать — не получится. Хочешь — не хочешь, а приходится "дос~авать мозги с полки" и мыслить самостоятельно. Значит, так... На первом месте, конечно же, должен быть цикл предвыборки, дающий процессору задание на загрузку данных из основной памяти в кэш первого уровня (второго уровня для процессора Р-4).
Затем загруженные в кэш данные могут быть мгновенно (в течение одною такта) прочитаны и занесены в 8!М!л-регистр, по эстафете передаваемый инструкции некэшируемой записи для выгрузки в память. (Использование промежуточного регистра объясняется тем, что адресация типа намять > пплягль в микропроцессорах 1пге) семейства х86 "отродясь" отсутствует.) Остается найти оптимальную стратегию предвыборки и записи.