- PVSM.RU - https://www.pvsm.ru -
Пару месяцев назад я прикрутил профилирование к нашей билд-системе (форке JamPlus [1]). Оно было реализовано на уже описанном мной ранее Chrome Tracing View [2], так что добавить его поддержку в Jam было просто. Jam написан на С, так что я просто нашел подходящую библиотеку для профилирования на С (это была minitrace [3]) и буквально несколькими строками обернул интересующие меня места (собственно, сборку).
Здесь нет ничего выдающегося. Однако… как только у вас появляются первые результаты профилирования, они чаще всего заставляют задуматься и начать кое-что замечать.
Как-то раз я занимался чем-то не связанным с темой данной статьи и для чего-то взглянул на вывод профилировщика для свежесобранного билда моего продукта. Опыт подсказывает, что в сборке С++ кода львиную долю времени занимает линковка. В этот раз, однако, дело было не в этом:
На диаграмме легко заметна большая задержка перед этапом линковки. Большая часть кода уже скомпилирована и лишь один файл с С++ кодом продолжает собираться. Тогда я был занят другой задачей, так что всего-лишь добавил задачу разобраться в этом на нашу доску с задачами. В другой раз я собирал билд другого компонента нашего продукта и снова взглянул на вывод профилировщика сборки:
И вот здесь дела выглядели уже откровенно плохо. Общее время сборки было около 10 минут и почти 7 из них занимала компиляция всего одного файла (5 из которых больше ничего не компилировалось). В этот момент стало ясно, что проблема в системе сборке такого масштаба, который не позволяет больше её игнорировать или откладывать.
Среднее время компиляции С++ файлов в данном проекте и в данной конфигурации составляло около 2 секунд. Была парочка файлов, которые собирались по 30 секунд, но 400+ секунд для сборки выходило за все разумные рамки. Что же происходит?
Я сделал несколько экспериментов и выяснил, что:
Был ли наш подход идеальным — отдельный вопрос, но тогда он давал нам достаточно преимуществ, чтобы не отказываться от него на ровном месте. Но всё-же что-то нужно было делать со скоростью компиляции.
Одним простым изменением, которое вполне было во власти системы сборки, могло бы стать исключение медленно компилируемых файлов из Unity-билдов. Весь их смысл в том, чтобы слегка сэкономить на запусках процесса компилятора и препроцессинге общих заголовочных файлов. Однако для нескольких файлов, компиляция которых занимает по 30+ секунд, этот выигрыш будет минимален, а вот необходимость ожидание нескольких минут на каждой сборке из-за «застрявшего» в конце сборки файлов — существенная проблема.
Хорошо было бы ещё как-то заставить систему сборки начать компиляцию «медленных» файлов как можно раньше. Раньше начнём — раньше закончим. Идеальным вариантом было бы прикрутить к системе сборки анализ исторических данных по предыдущим сборкам и автоматическое построение очереди компиляции на их основе. Но это не было нужно в данном конкретном случае — просто исключение файлов из unity-билдов в нашей системе сборки передвигало их в начало очереди. Ок, нам этого пока достаточно.
Этот трюк на самом деле ни на секунду не ускорил нашу 7-минутную сборку того «плохого» файла, но его было легко сделать и он сразу дал около минуты общего выиграша на всей сборке (которая до этого занимала 10 минут).
А после этого я сделал то, на что у меня вообще-то вообще не было надежд — я разбил в том «медленном» файле самую большую шаблонную функцию на несколько более мелких (некоторые из которых уже не были шаблонными). Тривиальный рефакторинг. Некоторые IDE умеют делать подобные вещи в режиме «выделил мышкой часть кода, правый клик, Extract Function». Ну вот только это С++ и код, как я уже говорил, содержал много макросов и шаблонов, так что пришлось всё делать вручную.
После выделения около 5 функций время компиляции проблемного файла упало с 420 секунд до 70. Стало в 6 раз быстрее!
Конечно, выделение функций означает, что они больше не являются инлайновым кодом и у нас появляются затраты на их вызов (передача аргументов, jump, возврат). В то же время такой подход всё-же позволяет вызывающей функции использовать регистры (лучше или хуже), уменьшить общий объём кода и т.д. Мы замерили скорость работы изменённого кода на разных платформах и пришли к выводу, что изменения производительности незначительны. Так что, в этот раз всё сработало!
Конечно, минута на компиляцию одного файла, это всё ещё много. Но дальнейшие попытки ускорения компиляции повлекли бы за собой существенные изменения в дизайне нашей математической библиотеки. Это требовало уже более продуманного планирования.
Сборка после сделанных изменений выглядит уже лучше. Больше нет кучи процессорных ядер, ожидающих завершения процесса компиляции на одном из них. Линковка всё ещё последовательна, но это не новость. Общее время сборки упало с 10 минут до 5 минут 10 секунд, т.е. стало почти в 2 раза быстрее.
Автор: tangro
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/264374
Ссылки в тексте:
[1] JamPlus: http://jamplus.org/
[2] Chrome Tracing View: https://habrahabr.ru/company/infopulse/blog/336584/
[3] minitrace: https://github.com/hrydgard/minitrace
[4] Unity-билдов: https://habrahabr.ru/post/117663/
[5] Источник: https://habrahabr.ru/post/338672/
Нажмите здесь для печати.