Главная страница Visual 2000 · Общий список статей
Поговорим о программированииАндрей Колесов
© Андрей Колесов, 2001ПРИЛОЖЕНИЕ (отдельный файл): Разбор полетов
Эффективность программного кода — показатель квалификации разработчика
Говоря в предыдущей части об отличительных чертах хорошей программы, я поставил эффективность кода на четвертое место после соответствия спецификациям, сроков исполнения и простоты сопровождения. Но еще раз подчеркну, что, несмотря на стремительный рост вычислительных мощностей компьютеров, эффективность кода (здесь два основных показателя — скорость выполнения программы и требуемый объем памяти) является крайне важной. И в любом случае — это наиболее очевидный показатель квалификации программиста. Ведь проверка специалиста, например, на уживчивость в коллективе и способность выполнять задания в срок требует времени, а неумение писать компактный код сразу видно невооруженным (хотя и опытным) глазом.
Этой зимой я встречался с руководителем одной российской компании (довольно крупной по нашим меркам), которая занимается разработкой заказного ПО для западных компаний — оффшорным программированием. Одной из тем обсуждения была подготовка разработчиков нашей системой образования. Мой собеседник выразил неудовлетворенность уровнем выпускников вузов, сказав, что даже вынужден был открыть небольшой учебный центр, где отобранных на работу специалистов учат "правильно программировать". Умение писать эффективный код является для компании одним из обязательных критериев (не единственным, конечно) при найме разработчиков.
Хотел бы еще раз подчеркнуть мысль, высказанную в прошлый раз: создание эффективного кода в целом не противоречит требованиям снижения трудозатрат. Сошлюсь на собственный опыт, который показывает, что внимательное "вылизывание" своего собственного кода первого работоспособного варианта программы почти всегда позволяет повысить его эффективность на 20-50%. В действительности это не так много, и такой повторный анализ программ целесообразен только для критически важных приложений (например, для коробочного продукта это может быть очень важным).
Но проблема заключается в том, что слишком часто встречаются примеры реальных программ, неэффективность которых сразу же бросается в глаза, и повышение, например, быстродействия которых в 2-3 раза обеспечивается весьма простыми приемами.
В связи с этим вспоминается общение с одним программистом, ставшее одной из побудительных причин начала "Размышлений" (см. приложение "Разбор полетов").
Несколько советов по поводу повышения эффективности программ
На тему о том, как писать эффективные программы, можно говорить очень много. Поэтому, совсем не претендуя на сколь-нибудь полное освещение проблемы (не говоря уже об их детальном изложении), попробую изложить некоторые соображения по этому поводу в тезисном виде.
Вопросы эффективности, по моему мнению, можно достаточно условно разделить на несколько уровней:
Проще говоря, нужно выполнять оптимизацию вычислительных или логических конструкций. В тривиальном примере это означает, что вместо:
y = (d * x) + (x * c)
нужно писать:
y = x * (d + c).
Хотя в такой алгебраической записи преобразование представляется совершенно очевидным, в программе его довольно часто не замечают. Особое внимание следует уделять вычислениям в цикле, минимизируя повторные операции. Например, конструкцию:
A = 0 For i = 1 To N A = A + Sin(x) * i Next
нужно заменить на:
A = 0: y = Sin(x) For i = 1 To N A = A + y * i Next
Еще лучше на:
A =0 For i = 1 To N A = A + i Next A = A * Sin(x)
А еще лучше вспомнить формулу суммы арифметической последовательности и написать:
A = Sin(x) * N * (N + 1) / 2
То же самое относится к упрощению логических конструкций:
If v0 And v1 Then ... If v0 And v2 Then ... If v0 And v3 Then
лучше заменить на:
If v0 Then If v1 Then ... If v2 Then ... If v3 Then ... End If
Кроме того, при выборе алгебраических или логических конструкций бывает полезно учитывать возможные значения переменных. Например, у вас есть такая проверка условия:
If v0 And v1 Then...
и при этом вы знаете, что v1 в 90% случаев имеет значение False. В этом случае имеет смысл написать по-другому:
If v1 Then If v0 Then... End If
или записать в такой последовательности, если вы знаете, что компилятор сам выполняет оптимизацию вычислений:
If v1 And v0 Then...
Подобные соображения необходимо учитывать и тогда, когда применяется конструкция типа Select. Например, вы пишете код для какой-то обработки прописных и строчных латинских букв:
Select Case AnsiCode Case 65 To 90 ' прописные (верхний регистр) Case 97 To 122 ' строчные End Select
Но если вы работаете с обычными текстами, где процент прописных букв довольно низкий, то, конечно, приведенный выше порядок проверки нужно поменять местами.
Итак, понятно, что качественное программирование требует хорошего знания принципов физической реализации вычислений. Здесь полезно вспомнить, что компьютеры имеют дело только с арифметической и логической обработкой числовых данных, причем чаще всего — только целых и вещественных. Все остальное — строки, числа с двойной точностью, структуры, массивы и пр. — обычно (мы не говорим о каких-то специализированных процессорах) реализуется лишь на программном уровне.
Собственно, программирование — это процедура формулировки абстрактных математических алгоритмов в компьютерных терминах. В основном она заключается в правильном выборе типов данных и операций их обработки. Само наличие в системах программирования разных типов данных вызвано необходимостью оптимизации программного кода (иначе вместо всех числовых данных мы бы просто применяли один формат, например вещественный с двойной точностью).
Здесь всегда необходимо помнить об очень простых вещах:
Список этих советов можно продолжить, но пока я просто приведу несколько фрагментов кода. Например, такие конструкции, как:
A = 0 For i = 1 To N A = A + i Next A = A * Sin(x)или
A = Sin(x) * N * (N + 1) / 2
лучше записать в следующем виде, указав тип переменных в явном виде (в предыдущем примере по умолчанию использовался Variant):
Dim Ai As Integer, i As Integer, A As Single Ai = 0 For i = 1 To N A = A + i Next A = Ai * Sin(x)или
Ai = N * (N + 1) / 2 A = Sin(x) * Ai
В общем случае вариант:
A = 2.* Sin(x)
всегда будет работать быстрее, чем:
A = 2 * Sin(x)
В последнем случае каждый раз выполняется преобразование целочисленной константы в вещественный вид.
Как всегда, особое внимание следует уделять повторяющимся конструкциям, в том числе циклам. Например, если у вас имеется такой вариант обращения процедуры:
For i = 1 To Ni For j = 1 To Nj Call MyProcedur (MyArray (i, j)) Next Next
то явно стоит подумать о написании другого варианта процедуры:
Call MyProcedur (MyArray())
перенеся туда вложенные циклы.
Учет специфики языка программирования
Здесь не следует забывать, что в разных системах программирования похожие конструкции реализуются различным образом. Кстати, многие тесты на производительность не учитывают подобные различия и дают весьма далекие от реальности результаты.
Например, результаты одного подобного исследования (во времена MS-DOS) показали, что скорость работы Fortran-программы была в несколько раз выше по сравнению программой, написанной на QuickBasic. Но авторы текста забыли (или не знали), что по умолчанию Fortran использует статические переменные, а QB — динамические. Стоило лишь вставить в QB-программы ключевое слово Static, и по быстродействию программы практически сравнялись бы...
Управление обменом данными с внешней памятью и СУБД
Основной принцип здесь заключается в минимизации числа обращений к внешней памяти. Проще говоря, прочитать файл целиком в оперативную память, обработать его, а потом также целиком сохранить — всегда быстрее, чем обмениваться данными по записям. Впрочем, большинство современных операционных систем поддерживают достаточно эффективные схемы кэширования, но все же...
Что касается работы с СУБД, то это отдельная огромная тема. Если дефекты написания обычных вычислений могут привести к разбросу производительности в несколько раз, то неоптимальность структуры базы данных и алгоритмов взаимодействия в нем приводит к разрыву в производительности в десятки и сотни раз.
ПРИЛОЖЕНИЕ (отдельный файл): Разбор полетов