1.23M
Category: informaticsinformatics

Оптимизация и производительность ПО (занятие 2)

1.

Рефакторинг
Занятие 2. Оптимизация и
производительность ПО
Преподаватель
Нефедова Л.П.

2.

Все программы должны быть
правильными, но некоторые
программы должны быть
быстрыми.
Если программа обрабатывает видео-фреймы
или сетевые пакеты в реальном времени,
производительность является ключевым фактором.
Недостаточно
использовать
эффективные
алгоритмы и структуры данных. Нужно писать такой
код, который компилятор легко оптимизирует и
транслирует в быстрый исполняемый код.

3.

Оптимизация кода
- это различные методы преобразования кода ради
улучшения его характеристик и повышения
эффективности.
Оптимизация может проводиться как вручную,
программистом, так и автоматизировано.
Цели оптимизации:
уменьшения объема кода,
объема используемой программой оперативной
памяти,
ускорение работы программы.
Оптимизированная программа должна
иметь тот же результат и побочные эффекты
на том же наборе входных данных, что и
неоптимизированная программа.

4.


Возможные причины
Неэффективные алгоритмы.
Медленные операции обращения к памяти.
Большое количество итераций в циклах.
Утечка ресурсов(памяти , дескрипторов, сокетов, процессов
и пр)
Когда причина неэффективности кода известна, можно
приступать к разработке и реализации решения, изменяющего
код.
Не все изменения в коде ведут к повышению
производительности, поэтому следует использовать тест для
подтверждения факта ее повышения в результате внесенных
изменений.
Тест на производительность выполняется до и после
проведения оптимизации с целью выявить изменения в
производительности. Задача тестов – выявление фактов
повышения и понижения производительности, чтобы можно
было избежать таких модернизаций, которые затормозят
выполнение программы .

5.

Оптимизация кода
- это различные методы преобразования кода ради
улучшения его характеристик и повышения
эффективности.
Оптимизация программного кода обычно не влияет на
внешнее поведение программного компонента, а изменяет его
внутреннее устройство и повышает скорость работы. При этом
оптимизация производительности допускает такие внутренние
изменения, которые часто изменяют понимание кода.
Общий сценарий действий.
Для оптимизации нужно найти узкое место – критическую
часть кода, которая является основным потребителем
необходимого ресурса.
Обычно процесс оптимизации ПО начинается с разработки
теста на производительность, который применяется для
объективного измерения быстродействия всего приложения или
той его части(алгоритма), который оптимизируется.

6.

Начать нужно с тех фрагментов, которые расходуют
основную часть времени выполнения программы.
Проводится исследование этих фрагментов программного
кода с целью обнаружить причину больших временных
затрат на выполнение этого участка кода.
Производится поиск незакрытых дескрипторов объектов
(файлов, сокетов, процессов и т.п.) Для поиска утечек
дескрипторов можно сделать макрос, оборачивающий
каждый вызов функций открытия и закрытия дескрипторов в
вызов функции и инкремента/декремента счётчика, но это
решение пока выглядит громоздким и сложно будет
обернуть
всё
множество
функций
открывающих
дескрипторы.
Выполняется замер производительности в сравнении.
Например для поиска утечек памяти существует способ
(https://docs.microsoft.com/en-us/visualstudio/debugger/findingmemory-leaks-using-the-crt-library?view=vs-2019).

7.

Тест на производительность
• Используется совместно с инструментами анализа
производительности, которые работают лучше
тогда, когда приложение можно прогнать несколько
раз одинаковым способом, тестируя одни и те же
фрагменты кода.
• Тесты на производительность могут служить как для
определения производительности, так и для
помощи инструментам анализа.
Оптимизация – процесс, зависящий от квалификации
программиста. Невозможно создать алгоритм,
оптимизирующий любую программу. Можно лишь
обратить внимание на те аспекты, в которых скрыты
резервы оптимизации.

8.

Область оптимизации
- это
часть
программы
(множество
операторов), над которой выполняется
оптимизирующее
преобразование
не
зависимо от других операторов программы.
Областью оптимизации может быть вся
программа.
Улучшение примерно 20 % кода иногда
влечёт за собой изменение 80 % результатов.
Для поиска узких мест используются
специальные программы — профайлеры.

9.

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

10.

Инструментальные средства анализа программы
(программы профилирующие производительность)
Профилирование - сбор характеристик работы
программы, таких как время выполнения отдельных
фрагментов (обычно подпрограмм), число верно
предсказанных условных переходов, число кэшпромахов и т. д. Инструмент, используемый для анализа
работы, называют профилировщиком или профайлером
(англ.
profiler).
Обычно
выполняется
совместно
с оптимизацией программы.

11.

Характеристики могут быть аппаратными (время) или вызванные
программным обеспечением (функциональный запрос).
Инструментальные средства анализа программы чрезвычайно важны
для того, чтобы понять поведение программы. Проектировщики ПО
нуждаются в таких инструментальных средствах, чтобы оценить, как
хорошо
выполнена
работа.
Программисты
нуждаются
в
инструментальных средствах, чтобы проанализировать их программы и
идентифицировать критические участки программы.
*Критический участок исполняемого кода программы — секция в
которой производится доступ к общему ресурсу (данным или устройству),
который не должен быть одновременно использован более чем
одним потоком выполнения. При нахождении в критической секции двух
(или более) потоков возникает состояние «гонки» («состязания»). Во
избежание данной ситуации необходимо выполнение четырех условий:
• Два потока не должны одновременно находиться в критических
областях.
• В программе не должно быть предположений о скорости или
количестве процессоров.
• Поток, находящийся вне критической области, не может блокировать
другие потоки.
• Невозможна ситуация, в которой поток вечно ждет попадания в
критическую область.

12.

Профилирование
критических
участков
программы
часто
используется, чтобы определить, как долго выполняются определенные
части программы, как часто они выполняются, или генерировать граф
вызовов (Call Graph).
Граф вызовов (Call graph) в теории построения компиляторов —
ориентированный граф, который изображает вызовы между подпрограммами в компьютерной программе. Узел графа - некоторая процедура, а
каждая дуга (f, g) показывает, что процедура f вызывает процедуру g.
Граф вызовов — результат анализа программы, который может быть
использован для понимания программы человеком, или в качестве основы
для дальнейших анализов. Одно простое применение графа вызовов — это
поиск процедур, которые никогда не вызываются.
Граф вызовов может быть динамическим или статическим.
Динамический граф вызовов представляет собой запись выполнения
программы. Статический граф вызовов предназначен для представления
всех возможных вариантов выполнения программы. Полученная
информация используется, чтобы идентифицировать те участки программы,
которые работают больше всего. Такие трудоёмкие участки могут быть
оптимизированы, чтобы выполняться быстрее.
Многие программы, написанные на таких языках программирования,
как Си и Фортран, осуществляют вызовы процедур непосредственно, так
что целевой код каждого вызова может быть определён статически. В этом
случае каждая точка вызова в графе имеет единственное ребро ровно к
одной процедуре. Косвенные вызовы весьма распространены в объектноориентированных языках программирования.

13.

Также выделяют анализ покрытия (Code Coverage) — процесс
выявления неиспользуемых участков кода при помощи, например,
многократного запуска программы.
Покры́ тие ко́да — мера, используемая при тестировании
программного обеспечения. Она показывает процент исходного
кода программы, который выполняется в процессе тестирования, то есть
покрыт тестами. Техника покрытия кода была одной из первых методик,
изобретённых для систематического тестирования программного
обеспечения (особенно, при тестировании белого ящика).
Существует несколько различных способов измерения покрытия, основные из них:
• покрытие операторов — каждая ли строка исходного кода была выполнена и
протестирована;
• покрытие условий — каждая ли точка решения (вычисления истинно ли или
ложно выражение) была выполнена и протестирована;
• покрытие путей — все ли возможные пути через заданную часть кода были
выполнены и протестированы;
• покрытие функций — каждая ли функция программы была выполнена;
• покрытие вход/выход — все ли вызовы функций и возвраты из них были
выполнены.
• покрытие значений параметров — все ли типовые и граничные значения
параметров были проверены.
• Для программ с особыми требованиями к безопасности часто требуется
продемонстрировать, что тестами достигается 100 % покрытие для одного из
критериев.

14.

Измерение производительности
приложения в Visual Studio
Средства профилирования и диагностики помогают диагностировать
проблемы с использованием памяти и ЦП и другими проблемами на уровне
приложений. С помощью этих средств можно накапливать данные о
производительности во время запуска приложения.
Вы можете:
• найти проблемы с производительностью при отладке с помощью средства
диагностики использования ЦП, встроенного в отладчика.
• проанализировать использование ЦП без подключения отладчика или
нацелив на работающее приложение.
Когда отладчик приостанавливается, средство Загрузка ЦП в окне средств
диагностики собирает информацию о выполняемых в вашем приложении
функциях. В средстве перечислены функции, выполняющие работу, и
предоставляется график временной шкалы, который можно использовать для
фокуса на определенных сегментах сеанса выборки.
Важно!
Средства диагностики, интегрированные с отладчиком, поддерживаются для
разработки .NET в Visual Studio, включая ASP.NET, ASP.NET Core и для
разработки машинного кода или C++. Требуется соответствующая рабочая
нагрузка Visual Studio . Windows 8 и более поздние версии требуются для
запуска средств профилирования с помощью отладчика (окно средств
диагностики).

15.

Профилирование использования ЦП
1.
Сбор данных об использовании ЦП.
При попытке определить проблемы с производительностью
выполните несколько измерений. Производительность естественным
образом меняется от запуска к запуску, и кодовые пути обычно
выполняются медленнее в первый раз из-за работы по однократной
инициализации, такой как загрузка библиотек DLL, компиляция методов
JIT и инициализация кэшей.
Выполняя несколько измерений, вы получите лучшее представление
о диапазоне и медиане отображаемой метрики, что позволяет сравнить
первый раз и стабильную производительность области кода.
2.
Анализ данных об использовании ЦП.
Дополнительно можно выполнить просмотр внешнего кода, который
вызывает функции Windows и определить свои дальнейшие действия.
Внешний код — это функции в компонентах системы и платформы, которые
выполняются в написанном коде. Внешний код включает функции, которые
запускают и останавливают приложение, рисуют пользовательский
интерфейс, потоки управления и предоставляют другие низкоуровневые
службы приложению. В большинстве случаев вас не интересует внешний
код, поэтому инструмент использования ЦП объединяет внешние функции
пользовательского метода в один узел (внешний вызов).

16.

Пример анализа
Основная проблема производительности (как пример) заключается в том,
как он управляет вычислительными ресурсами и взаимодействует с базой
данных. Приложение имеет узкое место производительности, которое
значительно влияет на его эффективность и, как следствие, на вычислительные
затраты, связанные с его выполнением.
Проблема включает следующие симптомы:
• высокий уровень использования ЦП. Приложения могут выполнять
неэффективные вычисления или задачи обработки таким образом, что
ненужно потребляет большое количество ресурсов ЦП. Это может привести к
замедлению времени отклика и увеличению операционных затрат.
• неэффективное выделение памяти. Приложения иногда могут столкнуться с
проблемами, связанными с использованием памяти и выделением. В
приложениях .NET неэффективное управление памятью может привести к
увеличению сборки мусора, что, в свою очередь, может повлиять на
производительность приложения.
• затраты на взаимодействие с базой данных: приложения, выполняющие
большое количество запросов к базе данных, могут столкнуться с узкими
местами, связанными с взаимодействием с базой данных. Это включает в
себя неэффективные запросы, чрезмерные вызовы базы данных и плохое
использование возможностей Entity Framework, все из которых могут снизить
производительность.

17.

Профилирование использования памяти
Найдите утечки памяти и неэффективную память при отладке можно
с
помощью
средства
диагностики
использования
памяти,
интегрированного с отладчиком.
Средство использования памяти позволяет принимать один или
несколько моментальных снимков управляемых и собственных кучи
памяти, чтобы понять влияние использования памяти типов объектов. Вы
также можете анализировать использование памяти без подключения
отладчика или нацеливаясь на работающее приложение.
Хотя вы можете собирать моментальные снимки памяти в любое
время в средстве использования памяти, вы можете использовать
отладчик Visual Studio для управления выполнением приложения при
изучении проблем с производительностью.
Настройка точек останова, пошаговой отладки, остановка всех
процессов и других действий отладчика может помочь вам
сосредоточиться на исследованиях производительности кода на
наиболее важных путях выполнения. Выполнение этих действий во
время работы приложения может исключить шум из кода, который не
интересует вас, и может значительно сократить время, необходимое для
диагностики проблемы.

18.

Средства
диагностики,
интегрированные
с
отладчиком, поддерживаются для разработки .NET в Visual
Studio, включая ASP.NET, ASP.NET Core, разработку
машинного кода/C++ и приложения смешанного режима
(.NET и собственные).
Рекомендуется выполнять следующие шаги:
• Делайте моментальные снимки памяти
• Анализ данных об использовании памяти
Если использование памяти не предоставляет
необходимые данные, другие средства в профилировщике
производительности предоставляют различные типы
сведений, которые могут оказаться для вас полезными.
Во многих случаях узкое место в производительности
вашего приложения может быть вызвано не памятью, а
другими факторами, например процессором, отрисовкой
пользовательского интерфейса или временем сетевого
запроса.

19.

Профилировщик памяти в собственном коде
работает путем сбора данных о событиях,
создаваемых во время выполнения событий ET W.
Трассировка событий для Windows (ETW)
предоставляет механизм трассировки и журналов
событий,
создаваемых
приложениями
пользовательского режима и драйверами режима
ядра.
EtW реализуется в операционной системе
Windows
и
предоставляет
разработчикам
универсальный набор функций трассировки событий.

20.

*Visual Studio Team System Profiler
- коммерческий профайлер ( «профилировщик») от корпорации Microsoft, входящий в состав
пакета Visual Studio Team System (VSTS) и версии Development Edition среды разработки Visual
Studio. Данный инструмент может работать или в режиме семплирования, в котором через
определенные промежутки времени производится запись снимков состояния программы, или
в режиме измерения, в котором статистика собирается за счет измерений входных и выходных
значений функций. Достоинством режима измерения является более тщательный сбор
статистики, однако он вынуждает программу работать гораздо медленнее из-за выполнения
дополнительного кода при измерениях.
Профайлер VSTS призван помочь обнаруживать и способствовать решению проблем
производительности в коде, написанном для платформы .NET или родного скомпилированного
кода Visual C++. Профайлер собирает информацию по характеристикам производительности для
методов, вызванных на данном этапе работы профайлера, включая количество вызовов функции
и весь стек вызовов при вызове функции. Профайлер VSTS можно использовать на 32- и 64-битных
Windows-платформах для профилирования как 32-битных, так и 64-битных программ.
Приложение должно быть вызвано из самого профайлера, а для корректного анализа
программа должна нормально запуститься и отработать. По завершении профайлер выдаст
итоговую оценку затраченного времени для каждой из функций и количество вызовов каждой
функции. Кроме того, также может отслеживаться и объём потребляемой памяти объектами под
хранение своих данных.
Статистику,
выдаваемую
профайлером, можно посмотреть при помощи графического
интерфейса Performance Explorer (Обозреватель производительности), входящего в пакет VSTS, и доступ к
которому можно получить, создав Performance Session (Производительность сессии).

21.

Приемы оптимизации программ
(превентивные методы)
1. Инициирование переменных. Если начальные значения
присваиваются переменным(константам) одновременно с их
объявлением, то экономится память и время выполнения
программы, так как в этом случае переменные получают
начальные значения во время компилирования программы, а
не во время выполнения.
2. Уменьшение числа переменных. Например, управляющая
переменная цикла по завершению работы цикла продолжает
занимать область памяти и дальше в течении выполнения
программы. Ее необходимо удалить (очистить) или
использовать далее в качестве временной переменной(в
другом цикле), чтобы не создавать новую. Тип остается
неизменным,
так
как
идентификатор
обозначает
определенную ячейку памяти. Таким образом минимизируется
память для хранения промежуточных результатов.
Не используемая, но объявленная переменная,
присвоения между подобными переменными – такая лишняя
переменная должна быть удалена.

22.

3. Выбор типов данных. Переменные разных типов
обрабатываются с разной затратой времени и памяти.
Необходимо понимание особенностей программ.
4. Удаление излишних операторов присваивания.
Удаление операторов присваивания и замена
переменных, являющихся левой частью удаляемых
операторов, выражениями, являющимися правой
частью операторов. Выполняется объединение
операторов.
5. Удаление тождественных операторов. Такие
операторы появляются в результате оптимизационных
и других мероприятий.
6. Удаление невыполняемых операторов.
Операторы не выполняются ни при каких наборах
данных

23.

7. Использование ввода-вывода. Операции вводавывода выполняются по времени долго - должны быть
минимизированы. Вычисляемые данные не нужно
вводить.
8. Выделение процедур и функций. Повторяющиеся
действия с разными идентификаторами и значениями
констант необходимо выделить в подпрограммы. Однако,
вызовы замедляют работу программы. Неразумно
создавать небольшую процедуру, вызываемую редко.
9. Альтернативы. При сравнении одной переменной с
несколькими значениями нужно использовать
конструкцию else – сравнение будет прекращено как
только будет найдено истинное условие. Аналогично и с
конструкцией выбора Case
10. Замена операций. Арифметические операции
выполняются с разной скоростью, поэтому целесообразно
заменить одни операции другими. Самыми быстрыми
являются операции сложения и вычитания. Более
медленным является умножение, затем идёт деление.

24.

11. Оптимизация выражений. Замена сложных
семантических эквивалентных выражений на одно
простое. Эффект в сокращении времени выполнения
программы и уменьшении объема памяти.
12. Оптимизация циклов.
Выражения, не меняющие значения в теле цикла
должны быть размещены вне цикла.
Иногда полезнее отказаться от цикла – сам процесс
установки
начального
и
конечного
значений,
наращивания, повторения и прочее по времени
длительный.
Объединение одинаковых циклов.
Удаление
инвариантных фрагменов кода - внутри циклов могут
встречаться инвариантные выражения(не связанные с
параметром цикла). Современные компиляторы часто
определяют наличие таких фрагментов и выполняют их
автоматическую оптимизацию.

25.

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

26.

Приоритеты оптимизации
• Интерфейсный:
желательно
заранее
ВСЁ
согласовать с другими участниками проекта,
включая кому сколько процессорного времени на
конкретном ПК.
Если ПК не является современным и многопроцессорным,
заведомо сильно ограничить суммарный CPU расход, можно
утилитами типа slowcpu (программа для замедления процессора
перед стартом или во время работы старых досовских программ и игр, не
работающих
с
современными
процессорами).
Необходимо
согласовать и обсудить каждую мелочь вплоть до того где
использовать ООП, а где нет - для ускорения.
• Алгоритмический. Это самый эффективный способ, тем
более наиболее оптимизируемые алгоритмы уже давно
оптимизированы и можно посмотреть на их реализацию.
Существует минус – принцип: чем оптимизированней тем менее понятно, тем больше багов.

27.

Многие «лучшие практики»
программирования, которым сегодня
обучают — потенциальные катастрофы
для производительности.

28.

Один из самых частых советов программистам,
особенно начинающим, гласит, что они должны
писать «чистый» код. Это понятие сопровождается
длинным списком правил, которые указывают, что вы
должны делать, чтобы ваш код был «чистым».
Большая часть этих правил фактически не влияют
на скорость выполнения вашего кода. Правила такого
рода не могут быть объективно оценены, и в этом
даже нет нужды, потому что в этом контексте они
довольно произвольны. Но в то же время, несколько
правил написания «чистого» кода, некоторые
из которых сильно подчеркнуты, мы вполне можем
объективно оценить, потому что они налагают свое
влияние на поведение кода в ходе его выполнения.

29.

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

30.

• Сокращение времени вычислений означает
сокращение затрат, поэтому оптимизация кода
может сэкономить деньги. Оценка оптимальности
учитывает использование ЦП, выделение объектов
NET, структуры базы данных.
• Анализ использования ЦП помогает определить
какие вычислительные ресурсы используются в
приложении. Представления использования ЦП,
такие как дерево вызовов и диаграмма пламени,
предоставляют хорошую визуализировать время,
затраченное в приложении. Кроме того, аналитика
может показать точные оптимизации, которые
могут оказать большое влияние на изолирование
проблемы.

31.

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

32.

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

33.

Пример 1.
Короткая функция сложения
function add(x, y) {
return x+y;
}
Она содержит единственный оператор «+» и два
операнда. Операнды — это любые объекты, с которыми мы
совершаем разные действия при помощи операторов.
Операнды в нашем примере — переменные x и y.
Холстед предложил использовать информацию о количестве
операторов и операндов для расчета следующих
характеристик:
Объем программы или нужное количество
информации для понимания ее назначения.
Сложность программы или умственные затраты
программиста на создание кода.
Количество ошибок, которые, скорее всего, будут
обнаружены в системе.

34.

Пример 2. Функция, вычисляющая простые множители целых
чисел. Уникальные операторы и операнды и количество раз, которое
они встречаются в программе, перечислены далее в таблице.
function primeFactors(number) {
function isPrime(number) {
for (let i = 2; i <= Math.sqrt(number); i++) {
if (number % i === 0) return false;
}
return true;
}
const result = [];
for (let i = 2; i <= number; i++) {
while (isPrime(i) && number % i === 0) {
if (!result.includes(i)) result.push(i);
number /= i;
}
}
return result;
}

35.

36.

Приведенный код содержит 18 уникальных
операторов (n1), 14 уникальных операндов (n2) и всего
операндов 37 (N2). Вот предложенная Холстедом формула
вычисления относительной сложности чтения программы:
D = (n1/2)*(N2/n2)
Подстановка значений даст следующий результат:
D = (18/2) *(37/14)
D = 23,78
Сама по себе эта цифра особого значения не имеет.
Но работая с отдельными фрагментами кода, постепенно
можно понять, как эта оценка соотносится с вашим опытом.
Со временем, сопоставляя вычисляемые значения с
реализациями кода, можно научиться интерпретировать их
в контексте приложения.
Эта метрика, может применяться в разных масштабах
— для оценки сложности отдельной функции или целого
модуля. Метрика сложности Холстеда рассчитывается даже
для всего файла, например, как сумма сложностей всех
входящих в него функций.

37.

Цикломатическая сложность
Предложенная в 1976 году Томасом Маккейбом
цикломатическая сложность — количество линейно независимых
маршрутов через программный код. Это подсчет операторов
потока управления в программе. Сюда входят операторы if, циклы
while и for и блоки case в конструкциях switch.
Для начала возьмем простую программу без управляющих
конструкций (пример 3). Для вычисления цикломатической
сложности присвоим 1 объявлению функции. Этот параметр
должен увеличиваться с каждой встречной точкой принятия
решения. В нашем примере через функцию есть только один путь.
Соответственно, ее цикломатическая сложность равна 1.
Пример 3. Функция пересчета температуры
function convertToFahrenheit(celsius) {
return celsius * (9/5) + 32;
}
Для более сложного примера возьмем уже знакомую функцию
разложения на простые множители. В примере 4 пронумеруем
каждую точку потока управления, получив в итоге
цикломатическую сложность 6.

38.

Пример 4. Факторизация целых чисел
function primeFactors(number) { //1
function isPrime(number) {
for (let i = 2; i <= Math.sqrt(number); i++) { //2
if (number % i === 0) return false; //3
}
return true;
}
const result = [];
for (let i = 2; i <= number; i++) { //5
while (isPrime(i) && number % i === 0) { //6
if (!result.includes(i)) result.push(i); //7
number /= i;
}
}
return result;
}
1 Первая
управляющая
точка —
объявление
функции.
2 Вторая —
первый цикл for.
3 Третья — первый
оператор if.
4 Четвертая —
второй цикл for.
5 Пятая — цикл
while.
6 Шестая —
второй оператор if.

39.

Каждый раз, когда при чтении кода появляется ветвление
(оператор if, цикл for и т. п.), возможны несколько путей
выполнения. Для понимания того, что делает код, нужно уметь
удерживать в голове больше информации. Цикломатическая
сложность 6 позволяет сделать вывод, что функция primeFactors,
вероятно, не так уж сложна для чтения и понимания. Подсчет числа
точек принятия решения — это упрощение предложенного
Маккейбом метода расчета сложности программы.
Математически можно вычислить цикломатическую
сложность
структурированной
программы,
создав
ориентированный граф, описывающий ее поток управления.
Каждый узел — это базовый блок (прямолинейная кодовая
последовательность без ветвей), связывающие узлы ребра
показывают способ перехода от одного блока к другому. Сложность
M определяется по формуле:
M = E − N + 2P, где E — количество ребер, N — количество
узлов, а P — количество компонентов связности (подграф, в
котором все узлы достижимы друг для друга).

40.

Для очень больших кодовых баз (которые обычно и
нуждаются
в
рефакторинге)
рассчитать
цикломатическую
сложность
непросто.
Здесь
появляется такой показатель, как количество строк
кода. Он менее научно обоснован, чем алгоритмы
Холстеда или цикломатическая сложность. Но в
сочетании с другими показателями размер программы
поможет в определении вероятных болевых точек
приложения.
Когда нужен прагматичный и простой подход к
количественной оценке сложности кода, лучше всего
подойдут размерно-ориентированные метрики.
Измерить длину кода можно несколькими
способами. Разработчики предпочитают подсчитывать
логические строки кода, полностью игнорируя пустые
строки и комментарии. Как и с метриками потока
управления, проводить оценку можно с разным
разрешением. Опереться можно на следующие
параметры.

41.

Количество строк кода (lines of code, LOC) в файле. В каждой кодовой
базе есть будто бы бесконечные файлы. Можно сказать, что подсчет строк в
них дает представление о психологической нагрузке на разработчика,
попытавшегося разобраться в структуре и назначении кода в редакторе.
Длина функции. В каждом бесконечном файле будет хоть одна
бесконечная функция (чаще всего они обнаруживаются именно в бесконечных
файлах). Измерение длины функций или методов в приложении позволяет
оценить их индивидуальную сложность.
Средняя длина функции внутри файла, модуля или класса. Еще можно
воспользоваться таким показателем, как средняя длина функции/метода на
логическую единицу. Это средняя длина каждого метода внутри класса или
пакета в объектно-ориентированных базах кода. В коде на императивных
языках измеряют среднюю длину каждой функции в файле или более крупном
модуле. Вне зависимости от использованных организационных единиц знание
средней длины входящих в нее более мелких логических компонентов может
дать представление об относительной сложности этой единицы как целого.
Параметр LOC может сильно зависеть от языка программы или стиля
программирования. Но если сравнение происходит в рамках одной
программы, беспокоиться не о чем.

42.

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

43.

Вопросы для самоконтроля
• Чем оптимизации отличается от понятия
«чистый код»?
• Каковы возможные причины для выполнения
оптимизации?
• Какие основные приемы оптимизации
существуют?
• Какими критериями можно руководствоваться
при проведении оптимизации?
• Приведите пример неоптимизированного
применения управляющих конструкций языка.

44.

Источники
• https://pvs-studio.com/ru/blog/terms/0084/
• https://znanium.com/read?id=103589
• https://habr.com/ru/post/309796/
• https://parallel.uran.ru/book/export/html/25
• https://ru.wikipedia.org/wiki/GNU_Compiler_Col
lection
• https://habr.com/ru/companies/vk/articles/3191
94/
• https://learn.microsoft.com/ruru/visualstudio/profiling/?view=vs-2022

45.

• https://tproger.ru/translations/performancevs-simplicity
• https://scilead.ru/article/4913-primeneniematematicheskikh-algoritmov-v-opti
• https://cyberleninka.ru/article/n/proizvoditeln
ost-i-optimizatsiya-programm-populyarnyealgoritmy/viewer
• https://habr.com/ru/companies/vk/articles/31
9194/

46.

*
English     Русский Rules