|
Арифметические операции выполняются с различной скоростью. Полезно знать, какие операции выполняются быстрее, так как иногда бывает целесообразно заменить одну операцию другой. Перечислим математические операции в порядке возрастания времени их выполнения: 1) сложение или вычитание, 2) умножение, 3) деление, 4) возведение в степень.
Некоторые медленно выполняемые операции легко заменить на более быстрые.
Сложение выполняется быстрее, чем умножение, поэтому умножение на небольшое целое число следует заменять сложением. Так, I должно быть заменено на I+I+I. Если в выражении не все числа являются целыми, то при замене может быть утеряна точность. Ошибка округления действительных чисел имеет тенденцию накапливаться, а не уменьшаться. Так, если R.— действительное число, а I — целое, то I*R более правильно, чем R+R+R+R... (I раз).
Преобразование уравнений может привести к исключению операций. Например, выражение X=2*Y+(A— 1)/Р+2*Т можно заменить уравнением Х=2* (Y+T) + (A—1)/Р, что исключает одну операцию умножения.
Поскольку деление является более медленной операцией, всюду, где возможно, его следует заменять умножением. Умножение выполняется по меньшей мере в два раза быстрее деления. Исключайте деление из вашей программы всюду, где это возможно: вместо А/5.0 пишите А*0.2.
Если в вычислениях вы все время делите на некоторое число, например на X, замените его на обратную величину. Например:
А = 1.0/Х
C=B+D/X
Используйте вместо этого обратную величину:
RX —1.0/Х
A =RX
C = B*+D*RX
В этом примере несколько действий деления заменено одним.
Важно также правильно задать тип показателя степени в операции возведения в степень. Всегда, когда это возможно, следует использовать целые числа. Например,
Медленный способ: А**8.0 или А**Р, где Р —число с плавающей точкой.
Более быстрый способ: А**8 или А**1, где I — целое число.
Второй способ обеспечивает более быстрое выполнение; кроме-того, он и более точен, так как при этом исключаются некоторые типы ошибок. Например, (—6)**1 разрешено, если I — целое число, но (—6) **Р не разрешается, если Р — число с плавающей точкой. Пусть Р = Р,5, тогда мы имели бы корень квадратный из отрицательного числа. Таким образом, если выполняется возведение в степень целых чисел, делайте показатель степени целым числом.
Медленный способ: В=А**Р, где Р — число с плавающей точкой.
Более быстрый способ: 1Р = Р
В=А**1Р, где IP — целое число.
Если показатель степени — целое число, то операцию возведения в степень выполняют повторяющимся умножением. Если показатель степени является числом с плавающей точкой, то для выполнения операции возведения в степень необходимо вызвать специальную подпрограмму.
Функция извлечения квадратного корня реализуется обычно гораздо быстрее, и точность при этом выше, чем при выполнении операции возведения в степень:
Медленный способ: А**0.5 Более быстрый способ: SQRT(A)
Умножение выполняется значительно быстрее возведения в степень, поэтому, если показатель степени — небольшое целое число, то операцию возведения в степень следует заменять несколькими операциями умножения:
Медленный способ: VOL = (4.0 *R* *3)/3.0 Более быстрый способ: VOL= (4.0*R*R* R)/3.0
Для возведения в степень обычно требуется библиотечная программа. Поэтому замена его несколькими операциями умножения экономит и память, и время, если показатель степени является небольшим целым числом.
Заменяйте Х**2 на Х*Х Заменяйте Х**3 на Х*Х*Х Заменяйте Х**4 на (Х*Х)*(Х*Х)
или на (((Х*Х)*Х)*Х)
Последний пример содержит повторяющееся вычисление (Х*Х), которое в дальнейшем может быть оптимизировано. Замена одной операции другой, выполняемой более быстро, называется уменьшением силы операции.
Уменьшение силы операции может иногда ухудшить удобочитаемость программ, и об этом следует всегда помнить. Кроме того, при таком преобразовании некоторое количество машинного времени затрачивается на управление промежуточными результатами.
3.9.1. АРИФМЕТИКА С ФИКСИРОВАННОЙ ТОЧКОЙ
Большинство машинных языков допускает целочисленную арифметику. Она может применяться для любых типов вычислительных операций. Для целых чисел используются специальные процедуры, потому что многие вычислительные задачи имеют дело только с целыми числами' (обработкой информации, связанной с инвентаризацией, переписью), а целочисленная арифметика обычно выполняется одной машинной командой, в то время как арифметика с плавающей точкой часто выполняется подпрограммами, включающими множество команд машинного языка.
Некоторые машины могут выполнять 50 операций сложения целых чисел за время, требуемое для выполнения одной операции сложения с плавающей точкой. В этом случае целочисленную арифметику следует использовать всюду, где это возможно, особенно для выполнения большого числа простых арифметических операций над целыми числами, такими, как индексы. Использование неправильного типа переменных для индексов может значительно увеличить время выполнения любой программы.
Впрочем, некоторые машины выполняют операции с плавающей точкой быстрее, чем операции с фиксированной точкой. Обычно это большие машины, служащие для проведения научных расчетов. Они имеют специальное оборудование, обеспечивающее выполнение арифметических операций е плавающей точкой.
Особое внимание следует уделить тому, чтобы внутренние переключатели, счетчики и переменные, встречающиеся в многочисленных вычислениях, были такого типа, который приводит к самым эффективным вычислениям. Это чаще всего является проблемой при работе с ПЛ/1 и КОБОЛом, где допускаются арифметические операции с переменными, являющимися строками символов. Если возникает такая ситуация, то для каждого вычисления необходимо делать многочисленные преобразования. И память, и время можно сэкономить правильным описанием переменных.
3.9.2. СМЕШАННЫЕ ТИПЫ ДАННЫХ
Смешанные типы данных получаются в результате использования чисел различного типа в арифметических и логических операциях. Если вы смешиваете числа разного типа, то при выполнении арифметических операций часто бывают необходимы преобразования. Ситуацию можно улучшить, объявляя как можно больше переменных одинакового типа. В этом случае нужно меньше заботиться о том, чтобы избежать смешанных вычислений, поскольку почти все переменные одного типа. Хотя смешанная арифметика допускается, чтобы уменьшить количество ошибок и помочь программисту, ее следует избегать, так как она занимает больше времени и памяти.
Избегайте смешанных типов данных.
3.9.3. СПОСОБ УСТРАНЕНИЯ ОШИБОК
В некоторых простых компиляторах следует тщательно выбирать тип используемых констант. Например, __
А = 0 Неэффективно А = 0.0 Эффективно
Некоторые компиляторы требуют преобразования целого нуля в вещественный во время выполнения программы, как в случае А = 0; во втором случае необходимость в преобразовании отсутствует. Хороший компилятор запоминает константу в нужной форме при компилировании, а не во время выполнения.
Если преобразование необходимо, оно может занять очень много времени при его выполнении внутри цикла. Например,
ФОРТРАН:
DO 10 1 = 1,1000 А(1) = 0 , 10 CONTINUE
Вышеуказанные операторы должны выполнить 1000 преобразований. Использование оператора
А(1) = 0.0
позволит избежать преобразований.
Подобная ситуация возникает при использовании оператора
Y = 1/Х где преобразование необходимо, в то время как для оператора
' Y=1.0/X его не требуется. Простой способ избежать подобных проблем состоит в том, чтобы писать все константы того типа, который преобладает в выражении.
Если при использовании КОБОЛа вы выполняете расширенную арифметику на полях DISPLAY вместо COMPUTATIONAL, это приводит к неэффективному использованию памяти и увеличению времени выполнения в 4—10 раз. Числовые поля, которые не используются в арифметических операциях, следует определять как буквенно-цифровые (X PICTURE).
3.9.4. ВЫРАВНИВАНИЕ ДЕСЯТИЧНЫХ ЧИСЕЛ
Программы, использующие переменные в фиксированном десятичном формате, можно сделать более эффективными, если тщательно выбирать их атрибуты. Если эффективность важна, полезно изучить руководство по языку для вашей машины, чтобы знать, когда необходимо выполнять преобразования в арифметических операциях.
В КОБОЛе и ПЛ/1 десятичные точки должны быть выравнены, поэтому программист должен тщательно выбирать атрибуты переменных. Например,
КОБОЛ:
WORKING-STORAGE SECTION. 77 A PICTURE S999V99. 77 В PICTURE S99V9.
PROCEDURE DIVISION. ADD A ТО В.
И время, и внутреннюю память можно сэкономить, если определить В как
77 В PICTURE S999V99.
Это исключает необходимость в дополнительных командах для выравнивания десятичной точки.
3.9.5. УПОРЯДОЧИВАНИЕ ПАМЯТИ
Можно получить значительную экономию времени и памяти, если переменные в памяти надлежащим образом упорядочить. Другими словами, некоторые типы переменных должны быть выравнены в памяти на границу слова или двойного слова. Если этого не сделать, то компилятор будет создавать команды, которые производят соответствующее выравнивание во время выполнения программы.
В ФОРТРАНе переменные типа COMMON будут надлежащим образом выравнены, если они расположены в порядке уменьшения их длин, т. е. сначала комплексные, затем с двойной точностью, действительные и целые. В КОБОЛе переменные типа COMPUTATIONAL могут быть выравнены при использовании оператора SYNCRONIZED. Если массивы не выравнены, могут иметь место значительные потери времени и памяти. Чтобы получить точную информацию о выравнивании, следует обратиться к руководству по программированию для соответствующего языка.
3.9.6. ГРУППИРОВКА
При выполнении операций одного приоритета над операндами разного типа следует группировать операнды одного типа, заключая их в круглые скобки. Например, если операнд I относится к типу 1, операнд А —к типу 2, операнд R — к типу 3, то выражение вида I*A*I*R*A* R* I следует записать как ((1*1*1)* А*А)* R*R.
Группировка и скобки помогает избежать преобразований, которые необходимо выполнить для первого выражения.
Можно избежать лишних преобразований, если вначале выполнить . преобразование одного типа данных в другой, а затем использовать нужный тип. Например, если I и А — переменные, которые используются вместе и требуют дополнительных преобразований, преобразуйте их сразу и используйте полученную форму в дальнейшем во всех математических операциях.
Например,
Медленный способ:
В=А*1
С=(А+1)*2.0
D=A*A/I
Эти операторы используют переменные А и I несколько раз. Лучше преобразовать один раз переменную I в переменную типа А и затем пользоваться новой переменной.
Более быстрый способ:
А1 = 1 В=А*А1 С=(А+А1)*2.0 D=A*A/AI
Здесь переменную I следует преобразовать не три раза, а только один.
3.9.7. ИСПОЛЬЗОВАНИЕ ЛИСТИНГА АССЕМБЛЕРА
Один из простейших путей проверки эффективности различных операторов состоит в том, чтобы воспользоваться распечаткой кодов, созданных ассемблером для каждой команды. Выборочная проверка количества команд, созданных ассемблером для каждой команды более высокого уровня, поможет продемонстрировать относительную эффективность различных способов составления программы. Приведем пример программы и соответствующего ей листинга ассемблера:
ФОРТРАН:
0008 А=А+В
0009 А = А+1
Ассемблер:
000190 А=А+В 8 LE 0,100(0,13) А
000194 AF 0,104(0,13) В
000198 STE 0,100(0,13) А
000190 А=А+1 9 L 0,108(0,13) I
0001А0 LPR 1,0
0001А2 ST 1,156(0,13)
0001А6 LD 0,152(0,13)
0001АА AD 0,136(0,13)
0001АЕ LTR 0,0
0001В0 BALR 14,0
0001В2 ВС 11,6(0,14)
0001В6 LCDR 0,0
0001В8 АЕ 0,100(0,13) А
0001ВС STE 0,100(0,13) А
Второй оператор сложения требует преобразования типов переменных.
3.9,8. ПОВТОРЯЮЩИЕСЯ ВЫЧИСЛЕНИЯ
Может показаться очевидным утверждение, что никакие операции не следует повторять, однако повторяющиеся операции обычно имеются во многих частях программы, особенно в циклах. Лучше сделать как можно больше простых вычислений в самом начале программы, а затем использовать их результаты в качестве переменных во всей программе. Этот процесс называется удалением избыточных команд и выполняется некоторыми компиляторами. Рассмотрим примеры повторяющихся операций.
Вместо операторов
X=Y+A/B*C Z=W+A/B*C лучше использовать
ABC=А/В* С
X=Y+ABC
Z=W+ABC
Хотя вторая группа операторов длиннее, она более эффективна и использует меньше памяти. Меньшее число операторов исходной программы еще не доказывает, что такая программа более эффективна по времени или памяти. Дешевле обходится двукратное обращение к переменной ABC, чем двойное вычисление А/В*С. так как пересылки очень дешевы. Приведем еще примеры.
Неэффективное решение:
SIGMA 1 = SIN(THETA) + SIN(THETA) * * 2 SIGMA2 = SIN(THETA)/3.0
Эффективное решение:
RHO = SIN(THETA)
SIGMAl = RHO -f RHO * RHO
SIGMA2 = RHO/3.0
В последнем примере функцию SIN нужно вычислить только один раз, тогда как в первом примере она вычисляется трижды.
Естественный способ решения задачи часто приводит к избыточным выражениям. Например, обычный способ нахождения корней квадратного уравнения состоит в следующем:
ROOT1 = (—В -f SQRT(B**2—4.0*А*С))/(2.0*А) ROOT2 = (—В — SQRT(B**2— 4.0*А*С))/(2.0*А)
Однако более эффективной (но менее удобочитаемой) программой является такая:
D = А + А
DIS = SQRT(B*B — 4.0*А*С) ROOT1 — (—В + DIS)/D ROOT2 = (—В — DIS)/D
Количество сэкономленного времени или памяти варьируется в зависимости от типа используемой машины. На некоторых машинах исключение очень простого повторяющегося выражения не дает почти никаких результатов. Но чем сложнее повторяющееся выражение и чем чаще оно встречается, тем большая получается экономия. На некоторых малых машинах операции с плавающей точкой занимают почти все время, особенно если в машине нет специального блока для арифметики с плавающей точкой. В этом случае имеются возможности для получения большой экономии времени.
⇐3.8. Инициирование переменных || Оглавление || 3.10. Обращения к функциям⇒
|