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< Назад | Оглавление | Далее >

Сайт управляется системой uCoz