К. Касперски - Техника оптимизации программ, Эффективное использование памяти (1127752), страница 87
Текст из файла (страница 87)
Машинная оптимизация 443 Перейдем теперь к сравнению размера откомпилированного и ассемблерного кода. Первое, что бросается в глаза (рис. 4.5), — это весьма внушительный отрыв М1сгозой Утзца1 С++ от своих конкурентов. Однако и он отстает от "ручного" кода, не дотягивая по меньшей мере 23%. Причем, по мере упрощения задач этот разрыв резко увеличивается, достигая в случае примера с копированием памяти целых 76%! Ух, ты! Ассемблерная реализация оказалась практически вдвое короче! Рис.
4.5. Сравнение качества машинной кодогенерации по размеру У конкурентов же ситуация еще хуже и причем значительно хуже. Грубо говоря, можно утверждать, что перенос программы на ассемблер как минимум сокращает ее размер раза в полтора-два. Тем не менее, два раза — это не триста и с этим вполне можно жить. Наглядная демонстрация качества машинной оптимизации Разговор о качестве машинной оптимизации был бы неполным без конкретных примеров. Показатели производительности — слишком абстракт- 444 Глава 4 .Гехт:00401350 зовеаоттве г:00401350 .Гехс:00401350 Г:00401350 с зогс .Секс:00401350 .Секс:00401350 аго а .Гахт:00401350 0 4 СООЕ ХРЕР: зцЬ 401430+ОХ р ргос пеаг = диого рог В = диогд рог ОСЬ .Секс:00401350 .Гехс:00401350 рцзъ еьх .Гехт:00401350 ; сохраняем регистр ЕВХ, т.к.
функция обязана сохранять .Гехс:00401350 ; модифицируеиые регистры, иначе програмяа рухнет .Ганг:00401350 .Генг:00401351 шоч еьх, [езртагд 4! .Гехю 00401351 ; загружаем в еьх самый правый аргумент — количество элементов .Гехю 00401351 ; точно так поступил бы и человек .Гехс:00401351 ; ага, локальные переменные адресуются непосредственно через ЕОР .Гехт:004013Е1 4 это экономит один регистр ~ЕВР1, высвобождая его лля других нужд .гехт:004013Е1 ; человек так не умеет... вернее, не то, что бы совсем не умеет, .гехГма0401зе1 ; но адресация через езР заставляет заново вычислять .Гехс:00401351 ; местоположение локальных переменных при всяком перемешении .Генг:00401351 ; верхушки стека <в частности при передаче функции аргументов), .Гехс:00401351 с что ну очень утомительно... г:00401351 ные величины.
Они дают пишу для размышлений, но не объясняют: вочему откомпилированный код оказался хуже. Как говорится, пока не увижу своими глазами, пока не пощупаю своими руками — не поверю! Что ж, такая возможность у нас есть! Давайте изучим непосредственно сам ассемблерный код, сгенерированный компилятором. Для экономии бумажного пространства ниже приведен лишь один пример — результат компиляции про1раммы пузырьковой сортировки компилятором М1сгозой ЧЬиа! С++.
Пример довольно показательный, ибо ощутимо улучшить его качество, не "вывихнув" при этом себе мозги, практически невозможно. Да вы смотрите сами. Если не влалеете ассемблером — не расстраивайтесь, в листинге 4.1. присутствуют подробные комментарии (надеюсь, вы понимаете, что их вставил не компилятор?). Машинная оптимизация .СехС:004013Е5 .Сехг:004013Еб рцаЬ еЬР рцаЬ ев1 .Сехб:004013Еб : сохраняем еще два регистра .Сехб:00401ЗЕб ; вообще-то, человек мог воспользоваться и «смаидой РОВНА, .сехс:004013еб ! сохраияющей все регистры общего назначения, что было бы .сехС:004013Еб ; намного короче, ио в то же время увеличило бы потребности .сехс:004013еб 7 программы в стековом простраистве и несколько снизило бы .сехс:004013еб ; ее скорость .Сехл:004013Еб .Сехл;004013Е7 с1ар еЬх, 2 .Сехб:004013Е7 ; сравниваем значение аргумента и с констаитой 2 .Сехг:004013Е7 .С С:00401ЗЕЛ .Сехо:00401ЗЕВ рцзб еб1 31 ядогС 1ос 40141В .СехС:004013ЕВ .СЕхС:004013ЕО щоч еЬр, [евр+ООЬ+асо О) .Сехг:004013ЕП ; загружаем в еВР значение крайнего левого аргумента— .Сехо:00401ЗЕО ; указателя иа сортируеыый массив.
Хак уже сказало, так .Сехб:004013ЕП ; адресовать аргументы умепт только компиляторы, .Сехг:00401ЗЕП 7 человеку это ие под силу .Сехг:004013ЕО .Сехл:004013Г1 .сехс:004013Г1 1ос 4013Г1: ПОПЕ ХВЕГ: С богС+ЗЗ .Сехг:004013Г1 7 цикл начинается с нечетного адреса. плохо.' .СехС:004013Г1 ; это весьма пагубно сказывается иа производительиости .Сехл:004013Г1 .СЕхС:004013Г1 хог езс, евь .Сехг:004013Г1 ; очищаем регистр ЕЯ1, выполняя логический ХОР. иад иим самим.
.Сехг:004013Г1 ; Вот иа это человек — способен! .Сехл:004013Г1 .СЕхС:004013ГЗ .СЕхС:004013Гб слр еЬх, 1 31е впогС 1ос 40141В .Селе:004013Гб ; эти две команды лишиие. .сехс:004013ВВ ; а вот здесь пошла оптиьмзация кода под ранние процессоры Р-Р имх .Сехо:00401ЗЕВ 7 сравнение содержимого ЕВХ и анализ результата сравнения разделехы .сехс:004013еВ 7 командой сохранения регистра е01.
дело в том, что Р-Р мих .Сехв:00401ЗЕВ ; могли спаривать команды, если охи имели зависимость по данным. .Сехе:004013ЕВ ; впрочем, в данном конкретном случае такая оптимизация излишня, .сехс:004013ВВ 7 т. к. процессор пытается предсказать направление перехода еще до .Сехг:004013ЕВ ; его реального исполнения. Впрочем, перестановка команд— .Сехг:004013ЕВ ; карман ие тянет и никому ие мешает Глава 4 .Гехс:004013гб .Гвхс:004013Г8 1еа еах, [еЬр+4) .Гехт:004013г8 ) быстрое сложить кВР с 4 и записать результат в ЕАХ) .Гехс:004013ГВ ; об этом трюке знают далеко не все программисты, .Гехс:004013ГВ ; и обычно выполняют данную задачу в два этапа .ГЕхс:00401ЗГ8 ) Моу ЕАХ, ЕВХ1АОО ЕАХ, 4 .Гехс:004013ГВ .Гехс:004013ГВ 1еа ебк, (еЬх-1) .Гехс:004013ГВ ) быстро вычесть из ЕВХ единицу и записать результат в ЕО1 .Гехс:004013ГВ .Гехд:00401ЗГЕ .Гехт:004013ГЕ ьм ОО4О[ЗГК 1ос 4013ГЕ: ; СООК ХВКГ; С когс+Зз 3 аюч есх, [еах-4) .Гехс:004013ГЕ ) оп-с! Команда, расположенная в начале цикла, пересекает .Гехс:00401ЗГЕ ; границу Сх10 байт, что приводит к ощутимым задержка исполнения .Гехь:00401ЗГЕ г Да, забыл сказать, зта инструкция загруяает ячейку згс[а-1) .Гехс:004013ГЕ ; в регистр КСХ .Гехт:СС4013ГЕ .«Ехс:004О1401 шоч ебх, [еах) .Гехс)00401401 ; загружаем ячейку вгс[а) а регистр ЕОХ .Ганс:00401401 .Г С:00401403 .Гехс:00401403 ь сравниваем ЕСХ (ягс(а-1)) и ЕОХ (зло[а)) .Гехс:00401403 ; вообще-то, это можно было реализовать и короче .Гехс:00401403 ) СМР ЕСХ, [ЕАХ), избавлюсь от ксыанды МСЧ ЕОХ, [КАХ) .Гехс:00401403 ; однако это ни к чему, т, к, значение (ЕАХ) нам потребуется ниже .Гехт:00401403 ю при обмене переменными и эта команда все равно бы вылезла там.
.Гехс:00401403 .ГЕхс:00401405 .Гехс:00401405 11е зпогс 1ос 401411 переход на ветку 1ос 401411, если ксх <= кох, .Гехс:00401405 ; в противном случае — выполнить обмен содержимым ячеек .Гехс:00401405 .Гехт:004013Г8 ; Для человека очевидно, если КВХ >= 2, то он всегда больше одного .Гехт:004013гб ) А вот дпя компилятора это — вовсе не факт> (Ну темный он) .Секс:004013гб ; дело в том, что он превратил возрастааханй цикл Гог .гехс:00401ЗГб ; в убываквмй цикл бо/нЬ11е с постусловием .Гехд:004013Г8 ; (убывакхдие циклы с постусловием реалиэутстся на .Гехс:004013гб ; хоб-процессорах намного более эффективно), .Гехх:004013гб ; но для этого компилятор должен быть уверен, что цикл .Гехт:004013Гб ; исполняется хотя бы раз — вот он и помещает .Генг:004013гб : в код дополнительную и абсолютно лишнюю в данном случае .Гехс:004013Гб ; проверку.
Впрочем, она не отнимает много времени Машинная оптимизация 447 .Сехг:00401407 .Сехг:О04О14ОА шоч [еах-4[, ебх [еах[, есх шоч .Сехг:0040140А ; обмен ячейкаьм. Вообще-то, можно было бы реализовать это и .Сехг:004014ОА : через ХСНО, что было бы на несколько байт короче, но у этой .Сехг:0040140А ; инструкции свои проблемы — не на всех процессорах она работает .Сехг:004014ОА : быстрее... .Сехц:004014ОА .Сехе:004014ОС яюч евс, .Сехг:0040140С ( устанавшяваем ЕВ1 (флаг 1[ в .Сехг:00401400 .Сехг:004014П .Сехг:00401411 1сс 401411: або еах, 4 СОВЕ ХНЕГ: С Воггагб 1 .Сехг:00401411 ; увеличиваем ЕАХ (а[ на 4 (всхеос(спС[[ .Сехь;00401411 .
Сехе: 00401414 Пес еб1 .Сехс:00401414 ; уменьшаем счетчик цикла на елиницу .Сехг:00401414 ; (напоминаю, ксьюилятор превратил наш сос в убывающий цикл) . Сехг: 00 401414 .Сехг:00401415 Опх яногС 1ос 401ЗГЕ .Сехг:00401415 ; переход к началу цикла, пока ЕО1 не равен нулю, .Сехг:00401415 ; некоторые человеки здесь лепят 1.СОР, который хоть и компактнее, .Сехг:00401415 ; но исполняется значительно медленнее ,Сехг:00401415 .СЕхС:00401417 Сеяс ев1, ев1 .Сехг:00401417 ; проверка Флага 1 на равенство нулю .СехС:00401417 .Сехг:00401419 .Сехг:00401419 Опс вдогС 1ос 4013Г1 повторять цикл, пока 1 равен нулю .Сехг:0040141В .Сехг:0040141В 1ос 40141В: рор ебс рор ееа .Сехг:0040141В .Сехе:0040141С .СехС:00401410 рор е(>р С:0040141Е рор ебх .Сехг:0040141Е ; восстанавливаем все измененные регистры .Сехп:0040141Е .Сехг:0040140С ь человек мог бы сократить это на несколько байт, записав это так: .Сехг:00401400 ; ИОУ ЕВ(, ЕСХ. Поскольку ЕСХ > ЕОХ, то ЕСХ ~=О, .сехс:0040140с ; при условии, конечно, что еох >= О.
.СехС:0040140С ; разумеется, компиляторам такое не по зубам, однако это .сехс:0040140с ; алгоритмическая оптимизация и к качеству кодогенерации она ,Сехя:0040140С ; не имеет никакого отношения Глава 4 .Хехю0040141Г хесе .1ехю0040141Г ; Вье!Охи хз ФУнхчхх ееср .Хех1:0040141Г С Яохе . 1ех1: 0040141Г Итак, какие у противников компиляторов есть претензии к качеству кода? Смогли бы они реализовать эту же процедуру хотя бы процентов на десять эфФективнее? Согласитесь, здесь есть чему поучиться даже весьма квалифицированным программистам! Особое замечание о создании защитного кода на ассемблере Зашитные механизмы, бесспорно, предпочтительнее всего реализовывать на "чистом" ассемблере, используя максимум трюков и "изврашений".