netlib.narod.ru | < Назад | Оглавление | Далее > |
Некоторое время назад я работал над критической процедурой на ассемблере, пытаясь заставить ее работать настолько быстро, насколько это возможно. Задачей процедуры было конструирование ниббла из отдельных разрядов, читаемых из различных байтов, циклический сдвиг и комбинирование разрядов, чтобы, в конечном счете, они были аккуратно выровнены в разрядах 3 – 0 одного байта. (Если вы любопытны, цель состояла в том, чтобы получить 16-цветный пиксель из разрядов, разбросанных по четырем байтам.) Я исследовал процедуру строка за строкой, сохранял такт здесь и такт там, пока код не показался действительно оптимизированным. Когда я закончил работу, ключевая часть кода выглядела примерно так:
LoopTop: lodsb ; получаем следующий байт, ; из которого будем извлекать бит and al,ah ; выделяем требуемый бит rol al,cl ; сдвигаем бит в требуемую позицию or bl,al ; добавляем бит к результату dec cx ; следующий бит должен быть помещен ; в результате на 1 позицию правее dec dx ; уменьшаем счетчик битов jnz LoopTop ; обрабатываем следующий бит, если он есть
Казалось бы, трудно написать код, который будет быстрее, чем семь инструкций, из которых только одна обращается к памяти, и многие программисты поставили бы на этом точку. Однако меня кое-что продолжало беспокоить, и я потратил немного времени, просматривая этот код еще раз. Внезапно ответ озарил меня — код сдвигал на место каждый бит отдельно, в результате в теле цикла каждый раз выполнялся циклический сдвиг на несколько разрядов, всего четыре отдельных отнимающих много времени циклических сдвига на несколько разрядов!
![]() |
В то время как каждая команда по отдельности была оптимизирована, общий подход не являлся лучшим из возможных применений этих команд. | |
Я заменил код на следующий:
LoopTop: lodsb ; получаем следующий байт, ; из которого будем извлекать бит and al,ah ; выделяем требуемый бит or bl,al ; помещаем бит в результат rol bl,1 ; освобождаем место для следующего бита dec dx ; уменьшаем счетчик битов jnz LoopTop ; обрабатываем следующий бит, если он есть rol bl,cl ; помещаем полученный результат ; в требуемые разряды байта
В результате дорогостоящий циклический сдвиг на несколько разрядов был вынесен за пределы цикла и исполнялся только один раз, а не четыре. Хотя на вид код почти не отличается от оригинала и фактически содержит то же самое количество команд, быстродействие процедуры в целом увеличилось от этого единственного изменения на 10 процентов. (Кстати, на этом оптимизация не закончилась; я исключил команды DEC и JNJ развернув четыре итерации цикла, — но это история для другой главы.)
Особенность в следующем: чтобы писать действительно превосходные программы на ассемблере вы должны знать, что именно делают различные команды, и какие команды выполняются быстрее всего... но это не все. Вы должны учиться смотреть на задачи программирования с различных точек зрения, чтобы помещать эти быстрые команды туда, где они будут работать наиболее эффективно.
netlib.narod.ru | < Назад | Оглавление | Далее > |