Сквозной пример
Регрессия валидации почт довела Мишу до профиля, и он назвал настоящее узкое место: регулярка компилировалась в цикле. Вынос компиляции исправил дело, повторный замер подтвердил — цикл «симптом → профиль → правка → подтверждение» закрылся.
профиль находит реальное узкое место, а не предполагаемое: в примере — регулярка, компилируемая в цикле, съедает и CPU, и аллокации, и всё лечится выносом компиляции из цикла.
Программа с узким местом
Программа валидирует список почтовых адресов регуляркой и приводит их к нижнему регистру:
func validate(addresses []string) []string {
var out []string
for _, a := range addresses {
re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if re.MatchString(a) {
out = append(out, strings.ToLower(a))
}
}
return out
}
validate компилирует регулярку внутри цикла — по разу на каждый адрес, 250 тысяч раз за прогон. Снимаем профили через runtime/pprof и смотрим.
Что показывают профили
В top по CPU наверху окажется сам runtime: сборщик мусора крутится почти без остановки. Среди прикладного кода самый жирный кусок — компиляция регулярки: regexp.MustCompile и функции из regexp/syntax. Heap-профиль в alloc_space подтверждает: почти все суммарные аллокации — те же компиляции: дерево разбора и программа строятся заново для каждого адреса. В нашем замере это около гигабайта на 250 тысяч адресов — число конкретного прогона, зависит от данных и версии Go, а не универсальный исход.
Лечим и проверяем
Лечится выносом в пакетную переменную — шаблон компилируется один раз при старте:
var emailRe = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
После правок повторный профиль рисует другую картину: в нашем замере прогон укладывается в полсекунды вместо трёх (цифры конкретного прогона — зависят от данных, машины и версии Go), компиляция из профиля исчезла, а вместе с ней успокоился и GC. Общий рецепт такой: выросла задержка или выросло CPU — CPU-профиль, top и flame graph, ищем самую жирную ветку; растёт память или дёргается GC — heap-профиль в inuse_space для живых объектов и в alloc_space для источника аллокаций. Профиль всегда показывает, что реально происходит, а не что ты думаешь про свой код.
Вы узнаете:
- какой полный список профилей pprof и когда каждый нужен
- как профилируют в проде канареечными сэмплами
Проверьте себя
regexp.MustCompile внутри цикла по 250 000 адресам. Что это даёт?
Как лечится компиляция регулярки в цикле?
Профилируют до или после оптимизации?
правильных ответов
Профилируй до оптимизации: профиль называет реальное узкое место, правка точечная, повторный профиль подтверждает. Курс окончен — вся цепочка от строк до профилировщика собрана.
10 000 ₽ · 60 минут · онлайн