Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Практика БЖД / Пример ПЗ.docx
Скачиваний:
42
Добавлен:
11.05.2015
Размер:
82.44 Кб
Скачать
      1. Зависимость по данным и параллелизм команд

Рассмотрим еще одну характеристику, оказывающую влияние на быстродействие алгоритма – зависимость по данным. ПроцессорIntelPentium®4 способен выполнить шесть команд за один такт. Однако на практике такой эффективности достичь не удается из-за зависимости команд по данным. Здесь необходимо вспомнить определение пропускной способности процессора для конкретной команды – число тактов после начала выполнения данной команды, необходимое для начала выполнения следующей такой же команды [5].

Рассмотрим эту ситуацию на примере. Пусть нам необходимо произвести следующие действия:

Пусть для нашего примера латентность умножения составляет три такта, а пропускная способность – один такт. Все переменные целочисленные. Как мы видим, все команды используют разные аргументы, то есть они независимы по данным. Это позволяет за первый такт начать выполнение первой команды, за второй такт продолжить выполнение первой и приступить к выполнению второй, за третий такт продолжить выполнение предыдущих двух и начать выполнение третьей. Таким образом, все команды будут выполнены не за 9 тактов (3 такта латентности * 3 команды), а всего за 5 тактов, что почти в два раза быстрее.

Теперь рассмотрим другую ситуацию, когда вторая команда зависит по данным от первой, а первая и третья команды не зависимы по данным:

Выполнение данных команд в такой последовательности окажется не самым производительным и займет 7 тактов, так как второй команде придется ждать полного завершения первой команды перед началом своего выполнения. Теперь попробуем изменить порядок операций:

Такая последовательность дает нам максимально быстрое решение задачи, которое займет 6 тактов процессора.

Рассмотрим более сложный пример. Пусть нам необходимо найти сумму тысячи элементов массива. Запишем это следующим образом [5]:

a = 0;

for (x = 0; x < 1000; x++)

a += buffer[x];

В данном примере мы можем начать прибавление к переменной aследующего элемента массива и тут же приступить к инкременту итератораx. В этих операциях нет зависимости по данным. Но приступить к прибавлению следующего элемента массива мы уже не сможем до окончания инкремента итератораx. Эту зависимость можно устранить следующим образом [5]:

a = b = c = d = 0;

for (x = 0; x < 1000; x+=4)

{

a += buffer[x];

b += buffer[x+1];

c += buffer[x+2];

d += buffer[x+3];

}

a += b + c + d;

Помимо уменьшения зависимостей по данным мы, таким образом, уменьшили и число увеличений значения итерационной переменной x, а так же число сравнений ее значения с количеством складываемых элементов массива.

    1. Оптимизация имеющейся программной реализации алгоритма

Следующим шагом после выбора оптимального алгоритма является оптимальная его реализация на языке программирования. В данном случае рассмотрим язык высокого уровня Си, а также Ассемблер, но без применения наборов векторных команд процессора.

      1. Оптимизация циклов

На первом шаге оптимизации необходимо устранить излишние вычисления, то есть такие команды, которые выполняются неоднократно без необходимости их повторного выполнения. Данную ситуацию можно показать на простом примере:

a = 3.14;

for(i=0; i<1000; i++)

buffer[i] = cos(a);

В данном случае результат вычислений функции косинуса будет одинаков для всех элементов массива buffer. Подобного рода вычисления можно устранить переносом вычисления функции косинуса перед циклом, что может дать существенный прирост производительности, так как вычисление тригонометрических функций – затратные по времени операции. При вычислении в цикле значений косинуса от периодически повторяющихся аргументов также целесообразно будет перенести вычисление данных функций перед циклом, а результаты занести в массив. Таким же образом следует тщательно отследить все действия, выполняемые в цикле, будь то дорогостоящие арифметические операции или проверка условий, так как цикл увеличивает затраты на выполнение данных операций кратно количеству итераций.

Еще одно часто встречающееся преобразование цикла – развертывание. Под развертыванием цикла понимается приведение его к такому виду, когда одна итерация модифицированного цикла эквивалентна по данным нескольким итерациям неразвернутого цикла. Как уже упоминалось ранее, такая оптимизация дает возможность уменьшить зависимость по данным. Однако при сильном развертывании цикла можно столкнуться с тем, что с определенного момента рост производительности прекратится вновь из-за зависимости по данным. Можно сформулировать общее правило по развертыванию циклов: полезно такое развертывание циклов, которое ведет к меньшему количеству по данным или к более удачному упорядочиванию команд [5].

Соседние файлы в папке Практика БЖД