К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 85
Текст из файла (страница 85)
К тому же, техника оптимизации процессоров последнего поколения стала значительно вротде, чем была лет пять тому назад. С другой стороны, ассемблер — это не вольчебная лампа Аладдина; он не способен творить чудеса. Во всяком случае реализация полиномиального алгоритма на ассемблере еше никогда не превращала его в логарифмический. За исключением особо оговариваемых случаев речь может идти лишь о количественном, но отнюль не о качественном выигрыше (пример с "Не((о, Ъог)бг' — как раз и есть олин из таких редких случаев, и ниже он будет рассмотрен во всех подробностях).
К тому же, при неумелом обращении (или применении знаний, почерпнутых из книжек по оптимизации десятилетней давности) "ручная" оптимизация становится просто посмешищем компиляторов! В общем, здесь есть, с чем разбираться. Краткий экскурс в историю или ассемблер — это всегда весна До середины (990-х борьба ассемблера с компиляторами шла с переменным успехом. Верх олерживала то одна, то другая сторона.
После выхода новых, более быстродействующих, микропроцессоров интерес к ассемблеру на какое-то время угасал — всем казалось, что наконец-то настала та счастливая пора, когда для решения подавляющего большинства задач достаточно одних лишь языков высокого уровня и нет нужды "корпеть" над кодом, сражаясь за каждый такт. Впрочем, эйфория никогда не длилась долго — вслед за возросшими процессорными мо!цностями усложнялись и возлагаемые на них задачи.
Возможностей языков высокого уровня вновь катастрофически не хватало, и интерес к ассемблеру незамедлительно вспыхивал с новой силой. Практически ни олин, сколь-нибудь заметный проект тех лет не обошелся без ассемблера. Ассемблерный код в изобилии присутствовал и в МБ-ООВ, и в ЪЧпбоюз, и в Оца(ге, и в М(сгозой О(((се, и во многих других продуктах. Короче, слона шапками не закидать — плохой компилятор остается плохим и на быстрых процессорах, а потому рынок его сбыта будет сильно ограничен. Жесточайшая конкуренция разработчиков компиляторов, в конечном счете, обернулась благом (и в первую очередь для прикладных программистов), ибо привела к интенсивному совершенствованию алгоритмов машинной оптимизации.
Уже к концу (990-х качество оптимизирующих компиляторов 436 Глава 4 практически достигло теоретического идеала, сравнявшись в решении иипагпных задач с программистами средней квалификации. Сегодня, когда редкий программист обходится без визуальных средств разработки и лаже "чистые" высокоуровневые языки выходят из молы, ассемблер и вовсе выглядит "архаичным рулиментом", задвинутым на одну полку с перфоратором и ламповой ЭВМ. Между тем слухи о его скорой "смерти" сильно преувеличены. Ассемблер жив! И лучшее подтверждение этому — тот факт, что основным языком разработки лрайверов М~сговой объявляет именно его, ассемблер, а вовсе не С~С++ или, скажем, Разсай Без ассемблера (или специализированных компиляторов) невозможно использовать преимушества новых мультимелийных команл параллельной обработки данных. Наконец, ассемблер по- прежнему незаменим в создании высокопроизводительных математических и графических библиотек.
(Кстати, загляните в катало~ СКТ~БКС~!п~е! дистрибутива М~сговойг%ьца! С++ — и злесь не обошлось без ассемблера, практически все функции, работаюшие со строками, реализованы именно на нем.) В общем, ситуация с "вживлением" ассемблера стабилизировалась, и на ос- тавшуюся у него вотчину языки высокого уровня более не претендуют. Критерии оценки качеетва машинной оптимизации Основные критерии качества кода это: быстродействие, компактность и время, затраченное на его разработку. Причем, оптимизация программы сразу по всем трем критериям невозможна в пригщипе. В частности, при выравнивании кода и структур данных по кратным адресам производительность программы возрастает, но вместе с нею увеличивается и ее размер. Считается, что скорость — более приоритетная характеристика, нежели объем.
Сегодня, когда количество оперативной памяти измеряется сотнями мегабайт, а емкость диска — сотнями гигабайт, компактность программного кода, действительно, уже не столь критична, однако конечному пользователю отнюдь не все равно: сколько мегабайт занимает программа — один или миллион, Поэтому практически все современные компиляторы поддерживают как минимум два режима оптимизации: гвахппшп вреев и впвппшп врасе. Однако скрупулезное тестирование не входит в наши планы. Оставим это на откуп читателям, а сами ограничимся компромиссным режимом максимальной оптимизации, пытающимся достичь наивысшей скорости при наименьшем объеме. Имешю такой режим и используется в подавляющем большинстве случаев, а потому он наиболее интересен.
437 Машинная оптимизация Отметим так же, что манипулирование ключами тонкой настройки оптимизатора может значительно изменить результаты тестирования, причем, как в худшую, так и в лучшую сторону. Но в этом случае будут уже сравниваться не сами оптимизаторы, а искусство их настройки, но это тема совершенно другого разговора. Важно понять, что точно оцепить общее качество оптимизации нп частных случаях невозмозгсно. Вот„например, М!Сгозой Ч!>ша! С++ умеет подменять константное деление умножением (что в десятки раз быстрее!), а Вог!апй С++ — нет. В зависимости от того, встречается ли константное деление в оптимизируемой программе или нет, соответствующим образом будет варьироваться и разница в быстродействии кода, сгенерированного обоими компиляторами.
Поэтому, в отсутствие конкретного примера, можно говорить лишь о приблизительной, прикидочной оценке качества компиляторов. Выше (см. разд. "Сравнительный пналоз оптимизирующих комто>яторов языка С!С++" этой главы) мы уже рассматривали этот вопрос во всех подробностях, и сейчас нас в первую очередь будет интересовать усредненное качество машинной оптимизации на примере типовых алгоритмов. Конечно, понятие "типовой алгоритм" очень относительное и субъективное. Для одних программистов, например, показательно Фурье-преобразование, другие же в своей практике могут и вовсе не сталкиваться с вещественной арифметикой.
Нижеследующий выбор заведомо нерепрезентативен, но определенную пишу лля размышлений он все-таки дает. Методики оценки качества машинной оптимизации Задача оценки качества кодогенерации намного сложнее, чем может показаться на первый взгляд. Прежде всего, следует разделять собственно сам компилятор и его окружение (среду, библиотеки и т. д.). В частности совершенно не корректно сравнивать размер откомпилированного примера "Неко, %огЫ" с его ассемблерной реализацией. Вызов р и и х" >; компилятор транслирует приблизительнО В следующий кОЛ; риап пггаеп ххх>са>> рптпПГ>рпр еах — КруЧЕ уЖЕ НЕ ОПТИМИЗИруЕШЬ! Обратите внимание на размер объективного файла, сформированного компилятором. Не правда ли, он мало в чем уступает объективному файлу ассемблерной реализации? Конечно, после подключения всех необходимых библиотек размер откомпилированного файла увеличивается в десятки раз, в то время как объем ассемблерного модуля практически не изменяется.
Да, это так, но при чем здесь компилятор?! Ему встретился вызов функции рхзппг — Он и Включил его в объективный файл. Если бы программист захотел вывести строку напрямую, т. е, через соответствующую АР!-функция> 438 Глава 4 операционной системы (как он поступил в ассемблерной реализации). исполняемый файл сразу бы "похудел" на десяток-другой килобайт. Что еше остается? Ах да, среда, называемая так же КТЕ (Кцп Типе Ь!Ьгагу— библиотека времени исполнения), — служебные функции, вызываемые самим компилятором.
Несмотря на то, что библиотеки времени исполнения являются неотъемлемым компонентом компилятора, к качеству кодогенерации они не имеют никакого отношения, т. к. с точки зрения компилятора функции КТЕ ничем не отличаются от обычных библиотечных функций. Избьпочность штатных библиотечных функций и библиотеки времени исполнения на крохотных проектах очевидна — вывод строки "Не!!о, %от!сГ' не использует и сотой доли возможностей функции рг'аег, но в программе, состояшей из нескольких тысяч строк, соотношение между полезными и служебными функциями нормализуется и коэффициент полезного действия библиотек практически вплотную приближается к единице. Таким образом, сравнивать эффективность компилятора и ассемблера на примере библиотечных функций — это вопиюшая некорректность.
Следует рассматривать лишь чистые реализации, не обрашаюшиеся к внешнему колу. В противном случае булет сравниваться не качество машинной и ручной оптимизации, а совершенство библиотек (написанных, кстати, в большинстве своем на ассемблере) с ручной оптимизацией. Совершенно бесполезно сравнивать и размеры объективных файлов — помимо кода они содержат массу посторонней информации, причем в рассматриваемых ниже примерах ее объем превышает размер машинного кода в десятки раз! Истинную картину вещей дает лишь дизассемблер, — загружаем в него объективный или исполняемый — без разницы — файл и от адреса конца функции вычитаем адрес ее начала. Полученная разность и будет подлинным размером исследуемого кода.