СОЛО на клавиатуре

Поговорим о программировании

21.04.2010

Размышления бывшего программиста. Часть 4

Эффективность программного кода — показатель квалификации разработчика

Говоря в предыдущей части об отличительных чертах хорошей программы, я поставил эффективность кода на четвертое место после соответствия спецификациям, сроков исполнения и простоты сопровождения. Но еще раз подчеркну, что, несмотря на стремительный рост вычислительных мощностей компьютеров, эффективность кода (здесь два основных показателя — скорость выполнения программы и требуемый объем памяти) является крайне важной. И в любом случае — это наиболее очевидный показатель квалификации программиста. Ведь проверка специалиста, например, на уживчивость в коллективе и способность выполнять задания в срок требует времени, а неумение писать компактный код сразу видно невооруженным (хотя и опытным) глазом.

Однажды я встречался с руководителем одной российской компании (довольно крупной по нашим меркам), которая занимается разработкой заказного ПО для западных компаний — оффшорным программированием. Одной из тем обсуждения была подготовка разработчиков нашей системой образования. Мой собеседник выразил неудовлетворенность уровнем выпускников вузов, сказав, что даже вынужден был открыть небольшой учебный центр, где отобранных на работу специалистов учат «правильно программировать». Умение писать эффективный код является для компании одним из обязательных критериев (не единственным, конечно) при найме разработчиков.

Хотел бы еще раз подчеркнуть мысль, высказанную в прошлый раз: создание эффективного кода в целом не противоречит требованиям снижения трудозатрат. Сошлюсь на собственный опыт, который показывает, что внимательное «вылизывание» своего собственного кода первого работоспособного варианта программы почти всегда позволяет повысить его эффективность на 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, и по быстродействию программы практически сравнялись бы...

Управление обменом данными с внешней памятью и СУБД

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

Что касается работы с СУБД, то это отдельная огромная тема. Если дефекты написания обычных вычислений могут привести к разбросу производительности в несколько раз, то неоптимальность структуры базы данных и алгоритмов взаимодействия в нем приводит к разрыву в производительности в десятки и сотни раз.

Ваш Андрей Александрович Колесов


← назадоглавлениедалее →

Оставить комментарий


Ваш комментарий будет опубликован после модерации.


Rambler's Top100
ErgoSolo
© 1997— «ЭргоСОЛО»
Дизайн: Алексей Викторович Андреев
Вебмастер: Евгений Алексеевич Никитин
Пишите нам:
Звоните нам по тел. +7 (495) 995-82-95. Мы работаем круглосуточно. Прямо сейчас на все Ваши вопросы готова ответить наша служба поддержки:
Круглосуточная трансляция из офиса «ЭргоСОЛО»

Поможем бросить курить
Все права на материалы, находящиеся на сайте ergosolo.ru, охраняются в соответствии с законодательством РФ, в том числе, об авторском праве и смежных правах.
Использование материалов сайта без разрешения ООО "ЭргоСоло" ЗАПРЕЩЕНО!
return_links(); ?>