Comments 56
Кроме того, Perforator умеет читать де‑факто стандартный механизм для JIT‑компилируемых языков —
perf-pid.map
Это даст только понимание где лежит бинарный код - а правила раскрутки как создаёте? Анализ бинарного кода (скажем, как в HPC Toolkit) делается/планируется?
Часто рантаймы в режиме с perf-pid.map генерируют бинарный код с frame pointers, иначе perf не сможет раскрутить. Поэтому оно не слишком сильно болит. Но в любом случае есть ситуации, где нет ни DWARF, ни frame pointers (рукописный ассемблер из популярного). Для таких ситуаций мы сейчас как раз экспериментируем с анализом бинарного кода для синтеза правил раскрутки стека; есть основания надеяться, что подход сработает
По карйней мере в простых случаях (стек фрейм выделяется в прологе и удаляется в эпилоге, в середине sp не меняется и все вызовы cdecl) должно работать надёжно, в том же HPC Toolkit давно сделано (сходу не вспомню в какой конкретно статье описывали детали).
Да, так и думаем. Эвристика работать очень хорошо должна. Там оно упирается скорее в скорость дизассемблирования всех бинарей, но это решаемо.
Другое дело, что бывают совсем странные случаи, где в рукописном ассемблере сохраняют rsp в xmm регистр; тут стек вообще может не получиться восстановить. Но такого кода, надеюсь, мало
где в рукописном ассемблере сохраняют rsp в xmm регистр; тут стек вообще может не получиться восстановить.
Такое скорее только в листовых функциях и на семпле теоретически можно и xmm зачитать. Но я видел только когда какой нибудь rcx в качестве альтернативного фрейм пойнтера при выравнивании стека использовали.
Я с подобным возился когда была актуальна Win32, там сплошные push/pop из за недостатка регистров и паскалевские вызовы, так что посмотреть на пролог/эпилог было недостаточно.
С xmm проблема скорее в том, что из контекста eBPF-программы их не достать, да и из ядра нетривиально. Но это совсем маргинальный сценарий, конечно, тут проще код переписать. Оригинально вот в этом коде первый раз заметили: https://www.nayuki.io/res/fast-md5-hash-implementation-in-x86-assembly/md5-fast-x8664.S
Так происходит потому, что современные компиляторы по умолчанию не генерируют указатели стековых кадров (frame pointers). Это позволяет сэкономить пару инструкций на функцию и освободить один регистр, однако лишает нас возможности легко профилировать. У Брендана Грега есть отличный обзор проблемы. Популярным решением стало возвращение frame pointers в сборку. В среднем просадка производительности небольшая, около 1–2%. Такой подход часто используют большие компании и дистрибутивы Linux.
А потом
Однако пересобрать все программы и библиотеки с
-fno-omit-frame-pointer
сложно. Даже если собрать так основной бинарь, всё равно возникнут системные библиотеки, которые собраны с-fomit-frame-pointer
. И стеки, которые проходят, например, через glibc, получаются битые. Кроме того, точные цифры замедления сильно зависят от конкретной нагрузки. В некоторых сценариях просадка намного заметнее, вплоть до десятков процентов.
Грег пишет - Ubuntu, Fedora, Arch уже с -fno-omit-frame-pointer
тогда возникает вопрос, а что используется, если пересобрать "сложно" ? Если уж мы пошли ловить мух - значит у нас всё должно быть под контролем ? А то мало ли как собрали библиотеки.
А что можете сказать по поводу ORC unwinder в ядре ?
И хотелось бы пример где одно и то же профилировалось с помощью perf, а потом Perforator.
Отличные вопросы, спасибо!
Если уж мы пошли ловить мух - значит у нас всё должно быть под контролем ? А то мало ли как собрали библиотеки.
В большом и сильно гетерогенном окружении это достаточно нетривиально реализовать. На масштабах Яндекса, например, есть точно разные бинарные файлы. Ну и совсем не все исполняемые файлы собираются дистрибутивами. Frame pointers везде – это хороший и правильный, но это не панацея, и так не выйдет достичь идеального качества раскрутки.
Например, есть VDSO (стеки приложения через gettimeofday не будут собиратся без DWARF), есть крайне много рукописного ассемблера в ключевых библиотеках и самых горячих местах, есть проприетарные бинарные блобы (libcuda.so), есть JIT-компилированый код и так далее. Нашей основной задачей было добиться идеального качества нативной раскрутки, потому что на масштабе perf нас не устраивал, в том числе из-за проблем на 20% стеков.
А что можете сказать по поводу ORC unwinder в ядре ?
Тоже звучит очень правильно и полезно. В частности, ORC нас вдохновил подумать ещё раз, а можно ли все же DWARF раскручивать, из этого Perforator и вышел. На самом деле, мы бы очень хотели вместе с сообществом по опыту написания профилироващиков и дебаггеров сделать популярным в тулчейнах подобный компактный и простой формат таблиц раскрутки для юзерспейсных приложений. DWARF, все же, is a complex mess. Вон компиляторы значительное количество вычислительных ресурсов тратят на то, чтоб поддержвать DWARF в процессе оптимизации, а будто бы можно сильно проще, если не пытаться генерализовать все подряд.
Другое дело, что смысл Perforator в том, что он работает в рамках существующих ограничений.
B хотелось бы пример где одно и то же профилировалось с помощью perf, а потом Perforator.
Вот взял профили Hyprland на свежем Arch, собрал через perf с fp и perforator. Для воспроизведения стоит учитывать, что в Arch все бинари стрипнутые, дебагинфу для perforator достал через debuginfod автоматически, для perf пришлось руками через `perf buildid-cache -a`. Отрендерил через нашу рисовалку профилей: https://perforator.tech/static/perforator-vs-perf/perf.html vs https://perforator.tech/static/perforator-vs-perf/perforator.html. Perf не справляется с блобом от Nvidia, например, и ещё несколько похожих нюансов.
Планируется ли поддержка arm64? Сейчас с подачи Nvidia серверные вычисления, связанные с видеокартами (GB200 и прочие новые системы с общей памятью) идут в эту сторону.
Бороться с размером профиля perf частично позволяет компрессия (-z, --compression-level опция). Кроме того, при правильно подобранном уровне снижается и оверхед коллекции.
Не рассматривали SFrame открутку (SFrame based stack tracer for user space in the kernel [LWN.net] )?
Если получилось сделать надежную и быструю DWARF-based открутку стеков, почему бы ее не добавить в perf подсистему Linux, улучшив уже существующий инструмент?
Да, с размером на диске помогает, но с трафиком в памяти – нет (на каждый семпл нужно скопировать 65К бессмысленных байт сначала из userspace в kernelspace, потом из kernelspace в userspace, потом ещё внутри perf пару раз потрогать). Memory bandwidh на современных серверах дорогой. Ну и фича довольно новая, экспериментировали пару лет наззад с -z – perf регулярно падал.
Смотрели, да. У нас основная идея – мы не требуем пересборки бинарей. Так что можно только идеями вдохновляться для построения нашей упрощенной таблицы раскрутки для eBPF программы. Но, как в соседней ветке отвечал, в мире явно назрела необходимость начать переходить на какой-то более адекватный формат для раскрутки стека.
Мы думали об этом. Скорее всего, в каком-то виде это можно сделать, с некоторой вероятностью допушим в апстрим. Основные сложности тут скорее технические: мы активно используем LLVM, код as-is переиспользовать будет сложно; ну и архитектура у perf немного отличается от нашей. Но тут очень важно то, что perforator – не только замена perf, но и инфраструктура для cluster-wide профилирования. Там тоже немало подводных камней.
Планируете ли в рисовалку профилей добавить возможность загружать профили, созданные в других профайлерах?
Привет. А кроме flamegraph другие форматы отображения доступны? Данный формат временами бывает очень неудобен. Ну или просто возможность получить профиль в prof/pprof формате для дальнейшей работы привычными инструментами.
Да. Мы внутри используем pprof (но постепенно мигрируем на новый формат из-за серьезных ограничений в масштабируемости pprof), поэтому через API и CLI можно получить сырой профиль в pprof формате. В CLI надо указать --format=pprof
.
Используете ли вы распределённую трассировку? Комбинация этих двух технологий (профилирование и трассировка) даёт ещё больший эффект на оптимизацию производительности приложений.
Если используете, то почему просто не помогли допилить OpenTelemetry, как это сделал Elastic?
Да и вообще там ребята семимильными шагами идут, уже кучу языков поддерживают.
Трассировку используем, и Perforator умеет аннотировать семплы идентификаторами трасс. Но там много нюансов: у нас количество семплов настолько большое, что по конкретному идентфикатору трассы лукапить будет крайне дорого. Но уже сейчас внутри активно используем знание про id трассы для профилей по срезам запросов пользователей в батч-режиме.
Про ребят из Elastic знаем, как и про несколько похожих стартапов. eBPF развивается крайне активно, вот только последние годы стало возможным реализовать такого рода профилировщики, и они параллельно с нами развивались в closed source (Elastic купил стартап Optimyze и только совсем недавно выложил в open source). Мы верим, что в нашей разработке есть много полезного для сообщества (в частности, у Elastic агент, это небольшая, хоть и ключевая часть системы).
Сейчас активно думаем, как нам лучше всего проинтегрироваться с сообществом, чтоб кумулятивный эффект был от комбинации разработок.
Интересно, что мы тоже для своих целей создали формат файлов с символьной информацией. И он получился довольно похожим на GSYM.
О, звучит очень интересно. А можно почитать где-нибудь деталей? В GSYM нас немного расстроила необходимость поддерживать структуры в памяти для каждой функции, не получается сделать совсем красиво с zero-copy использованием секции, из-за этого усложняется параллельная обработка.
О том, зачем это всё, можно почитать здесь: https://habr.com/ru/companies/isp_ras/articles/788490/
Структуры мы тоже в память грузим отдельно. Параллельной обработке это мешать не должно, но в нашем случае она пока не нужна.
В статье упоминается AutoFDO со ссылкой на документацию. А в документации говорится про sPGO.
А поддерживается ли сборка профилей для AutoFDO или CSSPGO ?
Под капотом профили генерируем через AutoFDO, да. CSSPGO пока руки не дошли попробовать. Кажется, в наш пайплайн тоже должно хорошо ложиться. Завел issue подумать, спасибо!
Ещё с BOLT экспериментируем, но там пока не все так гладко, BOLT слишком уж агрессивно бинари перемалывает (появляются не встречающиеся в дикой природе несколько исполняемых секций, когда-то DWARF ломался, ещё пара нюансов).
Спасибо Яндексу и команде!
Крутой и полезный проект, спасибо!
Подскажите, нормально ли выглядят профили в приложениях с корутинами? По моим представлении там будет полная мешанина в рамках одного физического потока.
Сильно зависит от приложения и вида корутин. Стекфул видно хорошо, активно внутри на такое смотрим (те же горутины). Тут вопрос скорее в правильной агрегации стеков между разными потоками.
Со стеклесс тяжелее, конечно. Самое печальное, что там стек вызовов совсем никак не материализован, так что по профилю понять можно сильно меньше. Думаем пока, есть ли варианты хорошие.
Навскидку приходит в голову stitching - но ему нужна инструментация кода и сбор трассы, для постоянного профилирования в продакшине не подойдёт.
Спасибо!
Скажите а планируется ли поддержка Java?
Планируется! Сейчас есть базовая поддержка для некоторых сценариев, но мы активно работаем над качественной zero-configuration поддержкой. Знаем про много смежных проектов, которые это уже хорошо делают, поэтому дело техники дальше.
У меня есть Spark приложение, можно его профилировать уже сейчас или пока рано ?
Мы не пробовали такую конфигурацию.
Тем не менее, если я правильно понимаю происходящее (а именно, что Spark application это набор обычных системных процессов, которые исполняются на нодах кластера спарка), то попробовать можно - опциями spark.executorEnv и spark.executor.extraJavaOptions можно выставить требуемые настройки JVM.
Еще важно, какая формируется иерархия контейнеров (насколько я понимаю, это зависит от режима развертывания самого Spark). Если приложения не запускаются в контейнерах с одинакаковыми для всех нод именами, то агрегированные флеймграфы не будут работать должным образом (но единичные профили это не затронет). Из трех предлагаемых систем оркестрации специальную нормализацию Perforator умеет делать только для Kubernetes (mesos и YARN, опять же, мы не пробовали). Думаю, проще всего это проверить на опыте - посмотреть, нормально ли агрегируются разные профили одного приложения, собранные с разных нод и запусков, или нет.
Так что в целом принципиальных препятствий не вижу, но есть риск упереться в нереализованные фичи. Думаю, можно попытаться и посмотреь на результат. Если возникнут проблемы - будем рады фидбеку, подумаем что можно улучшить.
Можно попробовать поиграться с spark.executor.extraJavaOptions.
Я вижу, что требуется JVM 17. А планирутся ли поддержка более старой версии как 11 ?
Сейчас таких планов нет. Можно завести issue в нашем репозитории, в нем померять интерес сообщества.
В целом проблема в том, что Perforator использует команду Compiler.perfmap
, в JDK 11 ее еще нет, нужны альтернативные источники данных.
При этом текущее решение (на основе perf-map) мы рассматриваем как промежуточное. Удалять его мы не планируем (потому что оно еще и для других языков какой-то уровень поддержки создает), но поддержку Java хотим делать другую, более умную. И добавление старых версий JDK в текущий механизм не продвинет нас к этому новому миру.
Теоретически, можно попробовать на старую JVM https://github.com/jvm-profiling-tools/perf-map-agent, и на получившуюся конструкцию натравить Perforator без указания java=true
(т.е. он не будет самостоятельно посылать команду Compiler.perfmap
, а вместо этого будет ориентироваться на сгенерированную агентом мапу). Но это, конечно, довольно неудобное решение(
Отличный проект и статья — спасибо!
Вы пишете (в разделе про FDO) "ускорение до 10%". Подскажите — это сравнение с режимом без профиля вообще, или с профилем, собранным perf’ом? Если первое, было ли сравнение того, как более высокая точность perforator’а влияет на результаты FDO (по сравнению с perf’ом)? И было ли сравнение с PGO?
И еще вопрос, если позволите: в начале статьи вы пишете "мы регулярно оптимизируем самые крупные сервисы в Яндексе… на десятки процентов". Речь идёт о ручной оптимизации (программист посмотрел в профиль, сказал "ага!" и соптимизировал) или автоматической (тот же FDO — или что-то другое?)
Сравнение с режимом ThinLTO. Если сравнивать с LBR-профилем от perf с одной машинки, то у нас виден эффект на единицы процентов от того, что мы можем обрабатывать прям много профилей (дни), но это уже довольно минорная штука. Ключевое тут скорее то, что вокруг perf так же придется инфру для PGO пилить, а у нас она общая с обычным профилированием.
А в чём отличие от pyroscope?
Ключевое и понятное – мы целимся в AutoFDO / BOLT и более продвинутые методы оптимизации, а pyroscope скорее про https://github.com/grafana/pyroscope/discussions/2783?ysclid=m6ky5ow7l9186278470. Ещё в Pyroscope был более урезанная логика раскрутки через DWARF когда-то, но ручаться не буду: с выходом elastic/opentelemetry-ebpf-profiler и развитием eBPF все меняется слишком часто (вон коллеги из parca, которые параллельно похожую конструкцию развивали, на него переехали).
В целом, конечно, похожие решения есть. Суть в деталях и масштабе. Надо сравнивать. Но PGO развивать тут мы не бросим, самим очень нужно.
Вот тут есть некоторое дополнительное сравнение: https://github.com/yandex/perforator/discussions/14. TLDR: мы фокусировались на качественной поддержке нативных языков, и она, судя по всему, у нас реализована лучше.
А собрать несколько PMU событий в текущей версии можно? Мультиплексинг поддерживается?
Можно, поддерживает (масштабируем число событий на коэффициенты, которые ядро возвращает). Разве что сейчас нет поддержки записи группы событий, все независимо будут семплироваться. Ну и мы видели кучу багов с мультиплексингом в ядре (не только у нас, perf так же себя ведет). Может быть, дойдут руки в апстрим зарепортить содержательно.
Sorry за глупый вопрос - но не разобрался, как несколько событий через командную строчку прокинуть, запятая в -e вроде не поддерживается, с несколькими опциями - так и не понял, собирается всё или последнее. И сами события - правильно понимаю, что поддерживаются только имена отсюда? Хотелось бы иметь возможность явно число (в сыром виде, с umask/cmask в отдельных битиках) указать.
С мультиплексингом есть нюанс с масштабированием когда событие в несколько групп попадает, уж не знаю бага это или фича.
Извиняюсь за напоминание - но покажите plz как в текущем командлайне указать несколько событий со своими интервалами (или хотя бы где дальше в коде есть скармливание нескольких событий perf'у). Хотелось бы посравнивать оверхеды с dwarf/fp на коллекции нескольких уровней TMA.
Я догадываюсь, что Perforator это от perf'а пошло.
Но если в гугле искать картинки по словам "перфоратор" и "perforator", то вылезают разные инструменты :) У команды разработчиков с каким инструментов больше ассоциация?
пытаюсь по https://perforator.tech/docs/en/tutorials/native-profiling на ubuntu 22.04
ERROR profiler.mount_info_scanner mountinfo/mountinfo.go:52 Failed to close mount point directory {"error": "invalid argument"} и
ERROR CachedBinariesBatch symbolize/cachedbinaries.go:69 Failed to acquire binary ....
и изза этого, наверно, flamegraph на такой как в примере
что не так ?
В каком виде вы планируете поддержку JIT? В нашем проекте мы динамически генерируем немного кода для "вычислительного графа". Одноврменно с этим мы генерируем dwarf, что позволяет исключениям корректно через него пролетать, перфу собирать стеки и gdb/lldb отлаживать. Но перфоратор с раскруткой стека не справился.
Интересно, можно ли ему как-то помочь сейчас, или он в каком-то виде научится в будущем.
И как только вам удалось настолько сложную тему объяснить таким доступным и понятным языком! Браво!
Perforator: новая система непрерывного профилирования теперь в опенсорсе