2015-05-12 4 views
1

(Примечание: Следующая упоминает execinfo/ backtrace, но это только пример, поведение в вопросе появились с различными библиотеками..)Backtrace Запросы крайне медленно


Рассмотрим библиотеку утилита, которая отслеживает выделение ресурсов некоторого приложения, связанного с ним. Поскольку функции выделяют и освобождают ресурсы, они вызывают функцию отслеживания, которая записывает детали операции, а также некоторую информацию, которая может быть использована для восстановления пути вызова. Иногда запрашивается библиотека для разбивки операций по путям вызовов.

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

К моему удивлению, просто вызов backtrace замедлял выполнение на ~ 4000% (!) Относительно вызова malloc. Поскольку backtrace принимает заданную (максимальную) глубину стека в качестве аргумента, и ее можно вызвать по путям вызовов с разными глубинами стека, я попытался понять, как эти параметры влияют на ее производительность. Насколько я вижу, просто вызов этой функции каким-либо образом имеет огромный штраф.

Для измерений я написал следующий простой код (см также full version):

const size_t max_backtrace_size = 100; 
void *backtrace_buf[max_backtrace_size]; 
static void track() 
{ 
    if(backtrace_size > 0) 
     ::backtrace(backtrace_buf, backtrace_size); 
} 

static void op(size_t i) 
{ 
    if(i == 0) 
    { 
     track(); 
     return; 
    } 
    op(i - 1); 
} 

Первый из этих двух функций, track, моделирует фактическое отслеживание (обратите внимание, что backtrace_size == 0 полностью отключает вызов backtrace); второй, op - это рекурсия, которая завершается вызовом track. Используя эти две функции, я изменил параметры и измерил результаты (см. Также the IPython Notebook).

На следующем рисунке показано время отслеживания в зависимости от разных размеров стека для каждого вызова backtrace с backtrace_size == 1 или не называть его (у которого такое малое время, что оно лежит на оси X, и его вряд ли можно увидеть на рисунке). backtrace имеет огромные накладные расходы, даже если вызывается с небольшими параметрами.

time as function of stack depth

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

time as a function of stack depth and backtrace size

  1. Есть ли какой-либо технический способ уменьшить накладные расходы нахождения трассировки? (возможно, в другой библиотеке или в разных настройках сборки.)

  2. В отсутствие 1. существует ли совершенно другая жизнеспособная альтернатива проблеме наверху?

ответ

1

К моему удивлению, просто вызвав трассировку замедлилось выполнение на ~ 4000% (!).

Это утверждение само по себе не имеет смысла. Даже если backtrace() сработал до инструкции, он все равно будет составлять +INF накладные расходы, если ваш другой код не содержит инструкций.

40x накладные расходы, вероятно, означает, что ресурс, который вы пытаетесь объяснить, чрезвычайно дешев для получения. Если да, возможно, не каждый экземпляр этого ресурса должен быть учтен? Можете ли вы, например, отслеживать трассировку стека только для каждого N-го распределения ресурсов?

Есть ли какой-либо технический способ уменьшить накладные расходы при поиске обратной линии? (возможно, в другой библиотеке или в разных настройках сборки.)

Есть несколько возможностей рассмотреть.

Предполагая, что вы спрашиваете о Linux/x86_64, одна из причин того, что backtrace является медленной, заключается в том, что при отсутствии указателей на кадры она должна находить и интерпретировать информацию для размотки. Другая причина: в основном используется для обработки исключений и никогда не оптимизировалась для скорости.

Если у вас есть полный контроль над приложением, которое будет использовать вашу библиотеку, то здание все, что с -fno-omit-frame-pointer позволит вам использовать гораздо более быстрый разворот на основе рамок.

Если вы не можете этого сделать, libunwindbacktrace может быть значительно быстрее, чем GLIBC (хотя он все еще не может работать с размотчиком на основе кадра).

+0

Спасибо за ваш ответ! Многое из того, что вы написали в начале re. относительные накладные расходы являются правильными, но, к сожалению, это связано с моим непреднамеренным отсутствием относительных накладных расходов на вызов '' malloc'' (я исправлю это в вопросе - спасибо).О выборке - вот что я в итоге сделал. Ваша мысль о указателе фрейма стека особенно освещается для него, и я буду читать дальше. –

1

Все, что вы говорите, это то, что backtrace занимает больше времени, чем malloc. Если вам нужно то, что он вам скажет, есть цена для оплаты.

Было ли это когда-либо предназначено для суперэффективности, поэтому его можно было бы назвать с высокой частотой?

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

Когда вы обнаружите проблемы, вы можете исправить их, , и когда вам больше не нужно backtrace, вы можете перестать называть его и быть счастливым, что это помогло вам найти их.

+0

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

+0

@AmiTavory: Но все же, независимо от того, эффективно ли он, зависит от процента времени, которое он затрачивает, что зависит от того, сколько раз в секунду оно должно быть вызвано. Есть так много вопросов о SO, где люди говорят: «Как я могу ускорить библиотечную процедуру Foo?», Не спрашивая, действительно ли им нужно называть это так часто, как они есть. –

+0

Еще раз спасибо. Вы, конечно, правы, что это действительно вопрос о (#/time) доле вызовов. В этом случае отслеживание было связано с '' malloc' ', вызванным операциями, инициированными клиентами сервера, и скорость запросов клиентов не поддавалась нашему контролю. Мы справлялись с этим, выборочно случайным образом определяя только некоторые операционные системы (что в основном представляет собой реализацию вашей идеи здесь). Тем не менее, чем эффективнее отслеживание, тем выше частота дискретизации (и меньше шума). Отсюда исходный вопрос. –

0

Если вы используете libunwind, убедитесь, чтобы построить свой код с UNW_LOCAL_ONLY определяется:

#define UNW_LOCAL_ONLY 
#include <unwind/libunwind.h> 

Я нашел, что это также помогло добавить «--disable-блок-сигналов» в команду Configure - без него , libunwind может в конечном итоге потратить довольно много времени, блокируя и разблокируя сигналы вокруг частей кода. На ARM (где я тестировал) это было довольно значительным.

Даже после этого, я думаю, что есть еще некоторые улучшения, которые могут быть достигнуты для производительности Libunwind. Я использовал perf, чтобы попытаться в этом немного поработать.