Можно использовать профилировщик, но почему бы просто не остановить программу?
Если что-то делает однопоточную программу, скажем, в 10 раз дольше, чем нужно, вы можете запустить на ней профилировщик. Вы также можете просто остановить его с помощью кнопки "пауза", и вы точно увидите, что он делает.
Даже если это всего лишь на 10% медленнее, чем должно быть, если вы остановите его несколько раз, вскоре вы увидите, что он постоянно делает ненужные вещи. Обычно проблема заключается в вызове функции где-то посередине стека, который на самом деле не нужен. Это не измеряет проблему, но, безусловно, находит ее.
Изменить: возражения в основном предполагают, что вы берете только 1 образец. Если вы серьезно, возьмите 10. Любая строка кода, вызывающая некоторый процент потерь, например, 40%, появится в стеке в этой части выборки в среднем. Узкие места (в однопоточном коде) не могут от него скрыться.
РЕДАКТИРОВАТЬ: Чтобы показать, что я имею в виду, многие возражения имеют форму "не хватает образцов, поэтому то, что вы видите, может быть полностью ложным" - смутные представления о случайности. Но если что-то из какого-либо узнаваемого описания, а не просто из рутины или из-за того, что рутина активна, действует в течение 30% времени, тогда вероятность увидеть это в любом конкретном образце составляет 30%.
Тогда предположим, что взято только 10 образцов. Количество раз, когда проблема будет видна в 10 образцах, следует за биномиальным распределением, и вероятность увидеть ее 0 раз составляет 0,028. Вероятность увидеть его 1 раз составляет.121. В 2 раза вероятность составляет 0,233, а в 3 раза - 0,267, после чего она падает. Поскольку вероятность увидеть его менее двух раз равна.028 + .121 = .139, это означает, что вероятность увидеть его два или более раза составляет 1 - .139 = .861. Общее правило: если вы видите что-то, что вы можете исправить на двух или более образцах, это стоит исправить.
В этом случае шанс увидеть его в 10 образцах составляет 86%. Если вы среди тех 14%, кто этого не видит, просто берите больше образцов, пока не увидите. (Если количество образцов увеличивается до 20, вероятность увидеть его в два или более раз увеличивается до более чем 99%.) Таким образом, оно не было точно измерено, но оно было точно найдено, и важно понимать, что это может быть что-то, что профилировщик не может найти на самом деле, например что-то, связанное с состоянием данных, а не счетчик программы.
17 ответов
На Java-серверах всегда было удобно делать 2-3 быстрых Ctrl-разбивает подряд и получает 2-3 потока вывода всех запущенных потоков. Простой взгляд на то, где "все потоки" находятся, может очень быстро определить, где ваши проблемы с производительностью.
Этот метод может выявить больше проблем с производительностью за 2 минуты, чем любой другой метод, который я знаю.
Потому что иногда это работает, а иногда дает совершенно неправильные ответы. Профилировщик имеет гораздо лучшие результаты поиска правильного ответа, и обычно он добирается быстрее.
Выполнение этого вручную нельзя назвать "быстрым" или "эффективным", но есть несколько инструментов профилирования, которые делают это автоматически; также известный как статистическое профилирование.
Выборка из стека вызовов является очень полезной техникой для профилирования, особенно когда вы смотрите на большую и сложную кодовую базу, которая может проводить свое время в любом количестве мест. Он имеет преимущество в измерении использования ЦП по времени настенных часов, что имеет значение для интерактивности, а получение стеков вызовов с каждым образцом позволяет понять, почему вызывается функция. Я часто его использую, но я использую для этого автоматизированные инструменты, такие как Luke Stackwalker и OProfile, а также различные вещи, поставляемые оборудованием.
Причиной, по которой я предпочитаю автоматизированные инструменты, а не ручную выборку для выполняемой работы, является статистическая мощность. Взять десять образцов вручную - это хорошо, если у вас есть одна функция, занимающая 40% времени выполнения, потому что в среднем вы получите четыре образца и всегда как минимум один. Но вам нужно больше выборок, когда у вас плоский профиль с сотнями конечных функций, ни одна из которых не занимает более 1,5% времени выполнения.
Скажем, у вас есть озеро с множеством разных видов рыб. Если 40% рыбы в озере - лосось (и 60% "все остальное"), то вам нужно всего лишь поймать десять рыб, чтобы узнать, что в озере много лосося. Но если у вас есть сотни различных видов рыб, и каждый вид по отдельности не превышает 1%, вам нужно поймать намного больше, чем десять рыб, чтобы сказать: "это озеро - 0,8% лосося и 0,6% форели". ".
Точно так же в играх, над которыми я работаю, есть несколько основных систем, каждая из которых вызывает десятки функций в сотнях различных объектов, и все это происходит 60 раз в секунду. Временные последовательности некоторых из этих функций превращаются в обычные операции (например, malloc
), но в большинстве случаев этого не происходит, и в любом случае нет ни одного листа, который занимал бы более 1000 мкс на кадр.
Я могу взглянуть на функции соединительной линии и увидеть: "Мы тратим 10% нашего времени на столкновение", но это не очень полезно: мне нужно точно знать, где находится столкновение, поэтому я знаю, какие функции нужно сжать. Просто "делай меньше столкновений" только так далеко, особенно когда это означает, что нужно выбрасывать функции. Я бы предпочел знать, что "мы тратим в среднем 600 мкс / кадр на промахы кэша в узкой фазе октодерева, потому что волшебная ракета движется так быстро и касается многих ячеек", потому что тогда я могу отследить точное исправление: или лучшее дерево, или более медленные ракеты.
Ручная выборка была бы хороша, если бы, скажем stricmp
, но с нашими профилями это не так. Вместо этого у меня есть сотни функций, которые мне нужно получить, скажем, от 0,6% кадра до 0,4% кадра. Мне нужно сбрасывать 10 мкс каждые 50 мкс функцию, которая вызывается 300 раз в секунду. Чтобы получить такую точность, мне нужно больше образцов.
Но в глубине души Люк Stackwalker делает то, что вы описываете: каждую миллисекунду или около того он останавливает программу и записывает стек вызовов (включая точную инструкцию и номер строки IP). Некоторые программы просто нуждаются в десятках тысяч образцов, чтобы их можно было профилировать.
(Конечно, мы говорили об этом раньше, но я подумал, что это хорошее место для подведения итогов дискуссии.)
Я удивлен религиозным тоном с обеих сторон.
Профилирование - это здорово, и, безусловно, оно более изящно и точно, когда вы можете это сделать. Иногда вы не можете, и приятно иметь надежную резервную копию. Техника паузы похожа на ручную отвертку, которую вы используете, когда ваш электроинструмент находится слишком далеко или аккумуляторы разрядились.
Вот короткая правдивая история. Приложение (своего рода задача пакетной обработки) нормально работало в течение шести месяцев, внезапно операторы вызывают разработчиков, потому что оно "слишком медленное". Они не позволят нам подключить пробоотборник в производство! Вы должны работать с уже установленными инструментами. Не останавливая производственный процесс, просто используя Process Explorer(операторы которого уже установлены на компьютере), мы могли видеть снимок стека потока. Вы можете взглянуть на вершину стека, отклонить его клавишей ввода и получить еще один снимок с помощью другого щелчка мыши. Вы можете легко получить образец каждую секунду или около того.
Это не займет много времени, чтобы увидеть, находится ли вершина стека чаще всего в DLL библиотеки клиента базы данных (ожидание в базе данных), или в другой системной DLL (ожидание операции системы), или на самом деле в некотором методе само приложение В этом случае, если я правильно помню, мы быстро заметили, что 8 раз из 10 приложение выполняло системный вызов DLL для чтения или записи сетевого файла. Конечно, недавние "обновления" изменили характеристики производительности общего файлового ресурса. Без быстрого и грязного и (санкционированного системным администратором) подхода к просмотру того, что приложение делало в работе, мы потратили бы гораздо больше времени, пытаясь измерить проблему, чем ее исправить.
С другой стороны, когда требования к производительности выходят за рамки "достаточно хороших", чтобы действительно раздвинуть конверт, профилировщик становится необходимым, так что вы можете попытаться сбить циклы со всех своих тесно связанных топ-десять или двадцать горячих точек. Даже если вы просто пытаетесь придерживаться умеренных требований к производительности во время проекта, когда вы можете подобрать правильные инструменты, которые помогут вам измерить и протестировать, и даже интегрировать их в ваш автоматизированный процесс тестирования, это может быть фантастически полезным.
Но когда питание отсутствует (так сказать), а батареи разряжены, хорошо знать, как использовать эту ручную отвертку.
Так что прямой ответ: знайте, что вы можете узнать, остановив программу, но не бойтесь точных инструментов. Самое главное знать, какие рабочие места требуют каких инструментов.
Есть разница между тем, что на самом деле делают программисты, и тем, что они рекомендуют делать другим.
Я знаю много программистов (включая меня), которые на самом деле используют этот метод. Это только помогает найти наиболее очевидные проблемы с производительностью, но это быстро и грязно, и это работает.
Но я бы не сказал другим программистам делать это, потому что мне потребовалось бы слишком много времени, чтобы объяснить все предостережения. Слишком легко сделать неточный вывод, основанный на этом методе, и есть много областей, где он просто не работает вообще. (например, этот метод не показывает никакого кода, который вызывается пользовательским вводом).
Так что, как и при использовании детекторов лжи в суде, или заявления "goto", мы просто не рекомендуем вам это делать, даже если они все имеют свое применение.
Профилировщики выборки полезны только когда
- Вы отслеживаете время выполнения с небольшим количеством потоков. Желательно один.
- Глубина стека вызовов каждого потока относительно мала (чтобы уменьшить невероятные накладные расходы при сборе выборки).
- Вы обеспокоены только временем настенных часов, а не другими метрами или узкими местами ресурса.
- Вы не инструментировали код для целей управления и мониторинга (следовательно, запросы на дамп стека)
- Вы ошибочно полагаете, что удаление стекового фрейма является эффективной стратегией повышения производительности независимо от того, являются ли внутренние затраты (за исключением вызываемых абонентов) практически нулевыми
- Вы не можете быть обеспокоены тем, как ежедневно применять инженерию производительности программного обеспечения в своей работе.
- ....
Если мы возьмем вопрос "Почему это не известно лучше?" тогда ответ будет субъективным. Предположительно, причина, по которой он недостаточно известен, заключается в том, что профилирование обеспечивает долгосрочное решение, а не текущее решение проблемы. Он не эффективен для многопоточных приложений и не эффективен для приложений, таких как игры, которые проводят значительную часть своего времени рендеринга.
Кроме того, в однопоточных приложениях, если у вас есть метод, который вы ожидаете использовать больше всего времени выполнения, и вы хотите сократить время выполнения всех других методов, тогда будет сложнее определить, какие вторичные методы сосредоточить ваши усилия по первому.
Ваш процесс профилирования - это приемлемый метод, который может и работает, но профилирование дает вам больше информации и дает возможность более детально показать улучшения производительности и регрессии.
Если у вас есть хорошо оснащенный код, то вы можете исследовать не только продолжительность конкретного метода; Вы можете увидеть все методы.
С профилированием:
Затем вы можете повторно запускать свой сценарий после каждого изменения, чтобы определить степень улучшения / снижения производительности.
Вы можете профилировать код на разных конфигурациях оборудования, чтобы определить, будет ли достаточно производственного оборудования.
Вы можете профилировать код в сценариях нагрузочного и стресс-тестирования, чтобы определить, как объем информации влияет на производительность.
Вы можете упростить для младших разработчиков визуализацию воздействия своих изменений на ваш код, потому что они могут изменить профиль кода через шесть месяцев, пока вы находитесь на пляже или в пабе, или и то, и другое. Пляж-паб, ftw.
Профилированию уделяется больше внимания, потому что корпоративный код всегда должен иметь некоторую степень профилирования из-за преимуществ, которые он дает организации в течение длительного периода времени. Чем важнее код, тем больше профилирования и тестирования вы выполняете.
Ваш подход действителен и является еще одним элементом панели инструментов разработчика. Это просто перевешивается профилированием.
Нажатие кнопки паузы во время выполнения программы в режиме "отладки" может не дать нужных данных для оптимизации производительности. Говоря прямо, это грубая форма профилирования.
Если вы должны избегать использования профилировщика, лучше использовать регистратор, а затем применить фактор замедления, чтобы "угадать", где настоящая проблема. Профилировщики, однако, являются лучшими инструментами для угадывания.
Причина, по которой нажатие кнопки паузы в режиме отладки может не дать реальной картины поведения приложения, заключается в том, что отладчики вводят дополнительный исполняемый код, который может замедлять работу определенных частей приложения. Можно сослаться на сообщение в блоге Майка Сталла о возможных причинах замедления работы приложений в среде отладки. Эта публикация проливает свет на некоторые причины, такие как слишком много точек останова, создание объектов исключений, неоптимизированный код и т. Д. Важна часть о неоптимизированном коде - режим "отладки" приведет к большому количеству оптимизаций (обычно встраивание кода и повторное выполнение кода). упорядочение) выбрасывается из окна, чтобы разрешить узлу отладки (процессу, выполняющему ваш код) и IDE синхронизировать выполнение кода. Следовательно, повторное нажатие паузы в режиме "отладки" может быть плохой идеей.
Это должны быть тривиальные примеры, с которыми вы работаете, чтобы получить полезные результаты с помощью вашего метода. Я не могу придумать проект, в котором профилирование было бы полезным (каким бы то ни было способом), которое бы получило приличные результаты с вашим "быстрым и эффективным" методом. Время, необходимое для запуска и остановки некоторых приложений, уже ставит под сомнение ваше "быстрое".
Опять же, с нетривиальными программами метод, который вы защищаете, бесполезен.
РЕДАКТИРОВАТЬ: Относительно "почему это не более известно"?
По моему опыту, обзоры кода избегают некачественного кода и алгоритмов, и профилирование также может их найти. Если вы хотите продолжить свой метод, который великолепен - но я думаю, что для большинства профессионального сообщества это так далеко в списке вещей, чтобы попытаться, что он никогда не получит положительного подкрепления как хорошее использование времени.
Кажется, что это довольно неточно с небольшими наборами образцов, и для получения больших наборов образцов потребуется много времени, которое было бы лучше потратить на другие полезные действия.
Снимки трассировки стека позволяют видеть только стробоскопические рентгеновские снимки вашего приложения. Вам может потребоваться больше накопленных знаний, которые может дать вам профилировщик.
Хитрость заключается в том, чтобы хорошо знать свои инструменты и выбирать лучшее для работы под рукой.
Что делать, если программа находится в производстве и используется одновременно, оплачивая клиентов или коллег. Профилировщик позволяет вам наблюдать без вмешательства (хотя бы потому, что, конечно, он тоже будет иметь небольшой удар по принципу Гейзенберга).
Профилирование также может дать вам гораздо более подробные и подробные отчеты. Это будет быстрее в долгосрочной перспективе.
Шаги по коду отлично подходят для просмотра мельчайших деталей и алгоритмов устранения неполадок. Это похоже на то, как будто вы смотрите на дерево очень близко и следите за каждой жилой коры и ветви отдельно.
Профилирование позволяет увидеть общую картину и быстро определить проблемные моменты - например, сделать шаг назад, посмотреть на весь лес и заметить самые высокие деревья. Сортируя вызовы функций по длительности выполнения, вы можете быстро определить области, которые являются проблемными точками.
Я использовал этот метод для Commodore 64 BASIC много лет назад. Удивительно, насколько хорошо это работает.
РЕДАКТИРОВАНИЕ 2008/11/25: Хорошо, ответ Vineet наконец заставил меня видеть, в чем проблема здесь. Лучше поздно, чем никогда.
Каким-то образом появилась идея о том, что проблемы с производительностью обнаруживаются путем измерения производительности. Это путает средства с целями. Каким-то образом я избежал этого, давным-давно пошаговые целые программы. Я не ругал себя за то, что замедлил его до человеческой скорости. Я пытался понять, делал ли он что-то неправильное или ненужное. Вот как быстро сделать программное обеспечение - найти и удалить ненужные операции.
В наши дни ни у кого не хватает терпения для одиночного шага, но следующая лучшая вещь - это выбрать случайное количество циклов и спросить, каковы их причины. (Вот что часто может сказать вам стек вызовов.) Если у хорошего процента из них нет веских причин, вы можете что-то с этим сделать.
В наши дни труднее, чем с многопоточностью и асинхронностью, но так я настраиваю программное обеспечение - находя ненужные циклы. Не видя, как это быстро - я делаю это в конце.
Вот почему выборка стека вызовов не может дать неправильный ответ, и почему не нужно много выборок.
В течение интересующего промежутка времени, когда программа занимает больше времени, чем вы хотели бы, стек вызовов существует непрерывно, даже если вы не используете его.
- Если бы инструкция I находилась в стеке вызовов для фракции P(I) того времени, удаление ее из программы, если бы вы могли, сэкономило бы именно столько. Если это не очевидно, подумайте немного.
Если инструкция отображается для M = 2 или более выборок, из N ее P(I) составляет приблизительно M/N и, безусловно, имеет значение.
Единственный способ, которым вы можете не увидеть инструкцию, - это волшебным образом рассчитать время всех ваших выборок, когда инструкция не находится в стеке вызовов. Простой факт, что он присутствует в течение некоторого времени, - это то, что подвергает его вашим исследованиям.
Таким образом, процесс настройки производительности - это простой вопрос выбора инструкций (в основном, инструкций вызова функций), которые поднимают голову, обращаясь к нескольким выборкам стека вызовов. Это высокие деревья в лесу.
Обратите внимание, что нам не нужно заботиться о графе вызовов, о том, сколько времени занимают функции, сколько раз они вызываются или рекурсии.
Я против запутывания, а не против профилировщиков. Они дают вам много статистики, но большинство не дают P(I), и большинство пользователей не понимают, что это важно.
Вы можете говорить о лесах и деревьях, но для любой проблемы с производительностью, которую вы можете исправить, изменив код, вам нужно изменить инструкции, в частности инструкции с высоким P(I). Так что вам нужно знать, где они, желательно без Шерлока Холмса. Выборка из стека говорит вам точно, где они находятся.
Эту технику сложнее использовать в многопоточных, управляемых событиями или в производственных системах. Вот где профилировщики, если они сообщат о P(I), действительно могут помочь.
Чем больше ваша программа, тем более полезным будет профилировщик. Если вам нужно оптимизировать программу, которая содержит тысячи условных веток, профилировщик может быть незаменимым. Введите самую большую выборку тестовых данных, а после завершения импортируйте данные профилирования в Excel. Затем вы проверяете свои предположения о вероятных горячих точках по фактическим данным. Всегда есть сюрпризы.
Обычно я использовал его в программах реального времени, которые превышали их временные рамки. Вы не можете вручную остановить и перезапустить код, который должен запускаться 60 раз в секунду.
Я также использовал его, чтобы отследить узкое место в написанном мной компиляторе. Вы не захотите пытаться разбить такую программу вручную, потому что у вас действительно нет возможности узнать, что вы ломаете в том месте, где есть узкое место, или просто в том месте, где есть узкое место, когда ОС разрешено вернуться в прекрати это Кроме того, что, если главное узкое место - это то, с чем вы ничего не можете сделать, но вы бы хотели избавиться от всех других крупных узких мест в системе? Как вы расставляете приоритеты, какие узкие места атаковать первыми, когда у вас нет хороших данных о том, где они все находятся, и каково их относительное влияние?