Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #71, ОКТЯБРЬ 2006 г.

война миров

КРИС КАСПЕРСКИ АКА МЫЩЪХ

Спецвыпуск: Хакер, номер #071, стр. 071-024-4


Причем цикл выровнен по адресам, кратным 10h (команды «LEA ESI,[ESI+0]» и «LEA EDI,[EDI+0]» используются для выравнивания), что, с одной стороны, хорошо, а с другой — не очень. Начиная с процессоров поколения P6 (к которым, в частности, принадлежит Pentium Pro и Penium-II) и AMD K5, выравнивание циклов требуется только тогда, когда целевой переход попадает на команду, расщепленную двумя линейками кэш-памяти первого уровня, длина которых, в зависимости от процессора, составляет 32, 64 или даже 256 байт. В противном случае, наблюдается существенное снижение быстродействия.

Компилятор MS VC вообще не выравнивает циклы, поэтому их быстродействие зависит от воли случая — попадет ли первая инструкция цикла на «расщепляющий» адрес или не попадет. Компилятор GCC выравнивает циклы, но по слишком ретивой стратегии, всегда подтягивая их к адресам, кратным 10h. Это хорошо работает на «первопнях», но вот на более современных процессорах команды, расходующиеся на выравнивание, занимают лишнее место и впустую съедают производительность (особенно на вложенных циклах, где они выполняются многократно).

Зато, в отличие от своего конкурента, GCC «догадался» использовать команду «MOVZX EAX, AL», только вот зачем она ему понадобилась — непонятно. Команда «MOVZX» пересылает значение из источника в приемник, дополняя его нулями до 16- или 32-бит (в зависимости от разрядности). Но в нашем случае старшие биты регистра EAX уже равны нулю, поскольку компилятор сам обнулил их инструкцией «XOR EAX,EAX». Следовательно, команда «MOVZX EAX,AL» совершенно не нужна и избыточна.

Интересно, изменилось ли что-нибудь в новых версиях? Компилируем программу с помощью GCC 3.4.2 и смотрим полученный результат на листинге 4.

Ага! Программа сократилась до 20h байт, что всего на 8 байт длиннее ассемблерной программы, но цикл практически не изменился. По-прежнему используется сложная адресация и никому не нужная команда «MOVZX». Изменилась только точка входа в цикл. Вместо того, чтобы проверять значение аргумента n до входа в цикл, компилятор сформировал условный переход на проверку, выполняемую перед выходом из цикла. То есть использовал тот же самый трюк, что и мы в нашей ассемблерной программе, однако, в отличие от нас, компилятор использовал 4 регистра, а не 3, и к тому же сгенерировал стандартный пролог/эпилог, который, при желании, можно подавить ключами командной строки, но это все равно не поможет, поскольку цикл остается неразвернутым (под «разворотом» в общем случае понимается многократное дублирование цикла). На таком крохотном «пяточке» процессору просто негде развернуться, поэтому все будет очень жутко тормозить – как при ручной оптимизации, так и при машинной. Компилятор MS VC вообще не умеет разворачивать циклы. По жизни. А GCC умеет, но по умолчанию не делает этого даже на уровне оптимизации O3, разве что его специально попросить, указав ключ -funroll-all-loops в командной строке. Циклы с известным количеством итераций, где const <= 32 разворачиваются полностью, при const > 32 — на ~4-x (точное значение зависит от количества инструкций в теле цикла), но циклы с неизвестным количеством итераций (то есть такие циклы, параметр которых — переменная, а не константа) не разворачиваются вообще! И в этом есть свой резон.

Назад на стр. 071-024-3  Содержание  Вперед на стр. 071-024-5