В распоряжении программиста при решении задач есть два основных ресурса - это память компьютера и его быстродействие. Кажется, что оба эти ресурса практически безграничны, и потому можно не задумываться о том, как они расходуются. Эти представления иллюзорны. Многие задачи, возникающие на практике, таковы, что имеющихся ресурсов не хватает и требуется жесткая их экономия. Вот два простых примера. Если в программе есть трехмерный массив A: double[,,]; A = new double[n,n,n], то уже при n =1000 оперативной памяти современных компьютеров не хватит для хранения элементов этого массива. Если приходится решать задачу, подобную задаче о "ханойской башне", где время решения задачи , то уже при n = 64 никакого быстродействия всех современных компьютеров не хватит для решения этой задачи в сколь-либо допустимые сроки. Программист обязан уметь оценивать объем ресурсов, требуемых программе.
Говоря о ресурсах, требуемых программе P, часто используют термины "временная" и "емкостная сложность" - T(P) и V(P). Выражения представляют хорошую начальную базу для оценивания этих характеристик.
Характеристики T(P) и V(P) обычно взаимосвязаны. Увеличивая расходы памяти, можно уменьшить время решения задачи или, выбирая другое решение, сократить расходы памяти, увеличивая время работы. Одна из реальных задач, стоящих перед профессиональным программистом - это нахождение нужного компромисса между памятью и временем. Помните:
"Выбора тяжко бремя - память или время!"
Как этот компромисс достигается на уровне выражений? Если в исходном выражении можно выделить повторяющиеся подвыражения, то для них следует ввести временные переменные. Увеличивая расходы памяти на введение дополнительных переменных, уменьшаем общее время вычисления выражения, поскольку каждое из подвыражений будет вычисляться только один раз. Этот прием целесообразно применять и тогда, когда не преследуется цель экономии времени. Введение дополнительных переменных уменьшает сложность выражения, что облегчает его отладку и способствует повышению надежности программы. Вероятность допустить ошибку в записи громоздкого выражения значительно выше, чем при записи нескольких простых выражений.
Еще один важный урок, который следует помнить, касается констант, участвующих в записи выражения.
"Каждой константе имя давайте,
Числа без имени из программ изгоняйте!"
Исключением могут быть простые константы - 0, 1, 2, 3. Если, как это часто бывает, изменяется значение константы, то это изменение должно делаться только в одном месте - там, где эта константа определяется. Введение констант уменьшает время вычислений, поскольку константы, заданные выражениями, вычисляются еще на этапе компиляции.
Рассмотрим в качестве примера вычисление значений переменных и , заданных следующими выражениями:
Вычислять эти выражения, точно следуя приведенной записи, не следует. Вот как можно организовать эти вычисления:
public void EvalXY(double a, out double x, out double y) { const double C1 = 53.5 * 33 / (37 * 37); const double C2 = 133 + C1, C3 = 1.0 / 3; double t1 = a + C1, t2 = a - C1; x = t1 * t2 / Math.Pow(C2, C3); y = t1 / t2; }
Заметьте, константы будут вычислены еще на этапе компиляции, так что для вычисления выражений потребуется 5 арифметических операций и один вызов стандартной функции. Выигрыш кажется незначительным при тех скоростях, которыми обладают компьютеры. Но стоит учесть, что метод EvalXY может вызываться многократно. И главное - даже не выигрыш во времени вычислений. Более важно, что запись выражения становится простой и позволяет легко обнаруживать ее ошибки.
Многие из моих студентов совершают типичную ошибку, записывая, например, выражение для вычисления x следующим образом:
x = t1 * t2 / Math.Pow(133 + C1, 1 / 3)
Надеюсь, что читатель ошибку видит, но на всякий случай поясню, что она связана с вычислением второго аргумента функции возведения в степень Pow. Здесь применяется операция деления, операнды которой - целые числа, потому результат деления нацело будет равен нулю. Обнаружить ошибку студенты могут далеко не сразу. В процедуре EvalXY ошибка становится видна мгновенно, стоит только взглянуть на значения констант, вычисленных еще на этапе компиляции.