пятница, 2 декабря 2011 г.

Оптимизация

Оптимизировать надо то, что тормозит
С моей точки зрения это и есть главное правило оптимизации. Очень часто я встречаю подход, при котором оптимизируется абсолютно весь код, на это уходят часы, а иногда даже недели или месяцы работы, и при этом особого результата не заметно. Обычно это происходит от того, что начинающие программисты считают, что исходный код программы будет работать быстро, если его перелопатить сверху до низу и выжать из него каждую микросекунду. Я не могу сказать, что такой подход к разработке программ совсем уж не верный – в некоторых ситуациях так делать и нужно. Но эти ситуации встречаются редко, гораздо чаще встречаются ситуации, когда из многих тысяч строк программы достаточно переработать лишь десяток-два строк кода и получить огромный прирост производительности.

Профайлинг – ключ к успешной оптимизации

Что бы начинать оптимизацию программы, прежде всего необходимо понять, какие именно участки кода создают не нужные “тормоза” – как правило “тормозит” от 1% до 5% кода, весь остальной код просто не оказывает никакого влияния на производительность. Проще всего найти эти участки кода запустив профайлер. Проще всего использовать встроенный в Visual Studio, либо Intel Perfomance Analyzer. Как правило, достаточно 1 раз запустить вашу программу под профайлером и заставить её поработать от нескольких секунд до нескольких минут, а потом просто посмотреть отчёт, сформированный профайлером. Из этого отчёта Вам понадобится лишь несколько первых строк, в которых и находятся те самые участки программы (функции), которые занимают больше всего процессорного времени – именно их и надо оптимизировать в первую очередь. И лишь после того, как Вы это сделаете, надо судить о том, стоит ли продолжать процесс оптимизации или уже пора остановиться – незачем тратить часы и дни работы на то, что бы, например, поднять ФПС игры с 90 до 91 кадра в секунду.

Методы оптимизации

Существую несколько достаточно простых правил, которые позволяют ускорить работу вашей программы, т.е. прооптимизировать её. Как правило, это следующие методы:
  • Локализация данных
  • Векторизация
  • Минимизация вызова функций
  • Инлайн-функции

Оптимизация через локализацию данных

Как известно, CPU обычно имеет внутри себя от одного до нескольких уровней кешей – с ними процессор работает намного (в разы/десятки/сотни раз) быстрее, чем с обычной оперативной памятью. Как только процессор обращается к каким-то данным, которые находятся вне его кеша – он читает эти данные из памяти, располагает их в кеше и потом работает с ними. Работать напрямую с памятью он обычно не умеет. При этом читает он обычно не только те данные, которые ему нужны в данный момент, но и небольшой “запас”, предполагая при этом, что они тоже могут понадобиться. Процесс чтения данных из обычной памяти в кеш не слишком быстрый (если даже не сказать что он очень медленный), а процесс чтения этих данных потом из кеша в процессор – быстрый.
Соответственно, если расположить обрабатываемые данные в памяти таким образом, что бы они, при работе, обрабатывались последовательно, то работа с такими данными может оказаться в сотни раз быстрее, чем при работе с данными, разбросанными по всем концам оперативной памяти.
Соответственно, совет в данном случае очень простой – просто располагайте данные в памяти последовательно. Например, не создавайте сотни объектов через оператор new, а создавайте сразу массив с помощью оператора new[]. Если количество создаваемых объектов велико (сотни, тысячи), то вы можете получить существенный прирост производительности – иногда даже в сотни раз.

Оптимизация через векторизацию

Если ваши данные локализованы в одном участке памяти и вам необходимо обработать массив этих данных, то очень часто может помочь оптимизация через векторизацию. Обычно всё, что требуется с вашей стороны – это включить соответствующие опции компилятора, как правило это: разрешить использовать SSE/SSE2 и указать максимальный уп=ровень оптимизации. После этого, при обработке массивов данных (в цикле), компилятор сам постарается сделать специальный код, которые будет использовать SSE-инструкции для обработки массивов – при больших размерах массива, часто это может привести к ускорению работы программы в несколько раз.

Оптимизация с помощью минимизации вызова функций

Обычно вызов каждой функции требует от центрального процессора поместить данные, передаваемые функции (параметры функции) в “стек”, вызывать функцию и прочитать результат работы функции (возвращаемое значение) из стека. Фактически, вызов каждой, даже самой простой, функции превращается в кучу работы. Соответственно, если Вы, вместо того, что бы вызывать функцию, опишите операции, которые надо провести над данными непосредственно, без вызова функции – скорее всего вы получите прирост производительности. При этом, как правило, такую операцию имеет смысл делать лишь для достаточно небольших функций – размером не больше нескольких (до десяка-двух) строк кода, не более – для более больших функций разница в скорости будет уже практически незаметна. И наоборот – чем меньше функция, тем более заметен будет результат. При этом надо заметить, что часто компилятор (при включённых опциях оптимизации) сам проводит такую оптимизацию и получает хороший код без участия программиста. Но он не всегда может сам до этого догадаться, потому ему иногда надо помогать, просто прооптимизировав код вручную.

Оптимизация с помощью инлайн-функций

Инлайн-функции, это такие функции, которые компилятор пытается встроить непосредственно в код, вместо создания вызова функции. Потому это правило оптимизации является прямым следствием предыдущего. Обычно для того, что бы функция заинлайнилась надо указать при её декларации ключевое слово inline или __inline – и компилятор заинлайнит её, если посчитает это разумным.

Когда не надо оптимизировать программу?

Ответ на этот вопрос можно дать простой и короткий – программу не надо оптимизировать до того момента, пока работа над ней (её функционалом) полностью не законченна. Т.е. сначала необходимо реализовать полностью весь функционал программы (или её отдельного модуля), потом убедиться что эта программа (или модуль) реально требует оптимизации и лишь после этого приступать к делу.
Если начать делать оптимизацию до того, как вы завершили основную часть работы над функционалом – очень высока вероятность того, что Вам позже придётся вносить какие-то дополнительные изменения в уже прооптимизированный участок кода, а это приведёт одновременно к тому, что вы будете путаться (читать и понимать оптимизированный код обычно сложнее) и при этом Вам потом ещё придётся повторно проводить оптимизацию (т.к. Вы поменяете код и не факт, что после этого он будет работать так же быстро, как и прежде).
Иными словами, Вы будете делать двойную, тройную, и более, работу – старайтесь оптимизировать не только производительность, но и своё рабочее время – его заменить нечем. Вместо того, что бы “разгонять” совершенно не важный участок программы, возможно, следует заняться более важными вещами – реализацией новых функций, повышением удобства пользовательского интерфейса, поиском багов и т.д.
Взято тут

Комментариев нет:

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