Почему мой класс профилирования очень медленный? - PullRequest
4 голосов
/ 20 ноября 2010

Я никогда не делал профилирование. Вчера я запрограммировал класс ProfilingTimer со статическим расписанием (карта ) для хранения времени.

Конструктор сохраняет начальный тик, а деструктор вычисляет истекшее время и добавляет его на карту:

ProfilingTimer::ProfilingTimer(std::string name)
 : mLocalNameLength(name.length())
{
 sNestedName += name;
 sNestedName += " > ";

 mStartTick = Platform::GetTimerTicks();
}

ProfilingTimer::~ProfilingTimer()
{
 long long totalTicks = Platform::GetTimerTicks() - mStartTick;

 sTimetable[sNestedName] += totalTicks;

 sNestedName.erase(sNestedName.length() - mLocalNameLength - 3);
}

В каждую функцию (или {блок}), которую я хочу профилировать, мне нужно добавить:

ProfilingTimer _ProfilingTimer("identifier");

Это профилирование отлично работает, когда я создаю версию выпуска из Visual C ++ 2010 Professional. Но когда я строю как Debug, я получаю огромное падение fps (с 63 до ~ 20).

Это числа, которые я получаю, когда печатаю свое расписание (отладочная сборка):

Update() > Tower::Update > : 2551 ms (84100m%)
Update() > Tower::Update > Tower::Update1 > : 1313 ms (43284m%)
Update() > Tower::Update > Tower::Update1 > Tower::FindNewTarget > : 6 ms (204m%)
Update() > Tower::Update > Tower::Update1 > Tower::HasTargetInRange > : 5 ms (184m%)
Update() > Tower::Update > Tower::Update2 > : 659 ms (21756m%)
Update() > Tower::Update > Tower::Update2 > Tower::HasTargetInRange > : 5 ms (187m%)

Update1 и Update2 умножены на первую и вторую половину соответственно Update. Почему они не составляют до 84,1%?

Тем не менее эти 84% являются огромным числом - при сборке релиза я получаю такой вывод:

Update() > : 770 ms (1549m%)
Update() > Tower::Update > : 722 ms (1452m%)
Update() > Tower::Update > Tower::FindNewTarget > : 44 ms (89m%)
Update() > Tower::Update > Tower::HasTargetInRange > : 92 ms (187m%)

1,4% вместо 84,1%. Это огромная разница!

Кто-нибудь знает почему?

РЕДАКТИРОВАТЬ: я думаю, что выпуск намного быстрее, чем отладка, но почему это профилирование занимает так много времени? Является ли std :: map тайм-хоггером или я делаю что-то крайне неправильное?

РЕДАКТИРОВАТЬ: Обновлен код. Инициирование не требовалось, и теперь в нем хранится длина mLocalName вместо фактической строки.

Ответы [ 3 ]

2 голосов
/ 20 ноября 2010

Использование длинных строк в качестве индекса для std::map, вероятно, не самый быстрый способ сделать это.Длинные строки с общим началом означают, что каждый раз, когда вы сравниваете две из этих строк, нужно просмотреть много символов, чтобы увидеть, равны ли строки или нет.std::map является в основном двоичным деревом и выполняет O (log (n)) сравнений при каждом поиске / вставке.Использование более коротких или цифровых клавиш должно ускорить все операции на карте.

Кроме того, использование std::unordered_map может (или не может) повысить скорость в зависимости от того, сколько элементов содержит карта и какие ключи используются.

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

ProfilingTimer::ProfilingTimer(const std::string &name)

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

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

2 голосов
/ 20 ноября 2010

Есть пара проблем с производительностью вашего кода.

  1. Вы напрасно создаете std :: string в конструкторе ProfilingTimer. Я бы рекомендовал использовать const char * в качестве параметра и использовать пользовательскую структуру Twine / Rope для добавления.
  2. Почему mLocalName вообще существует? Просто обратитесь к name напрямую.
  3. Как уже упоминалось, никогда не профилируйте в режиме отладки. Это бесполезно.
  4. Карты на самом деле довольно медленные на практике. Я предлагаю использовать хеш-таблицу. К сожалению, реализации зависят от компилятора. Если вы используете Microsoft, я думаю, у них есть unordered_map для вашего использования.
  5. Вместо выполнения sTimetable[sNestedName] = 0; используйте итератор, который вы уже получили.

    Timetable::iterator loc = sTimetable.find(sNestedName);
    if(loc == sTimetable.end())
        sTimetable[sNestedName] = 0;
    

Приложение: Visual Studio поставляется с профилировщиком, который я последний раз проверял. Почему бы просто не использовать это?

2 голосов
/ 20 ноября 2010

Microsoft добавляет множество проверок безопасности в свои библиотеки контейнеров в режиме отладки.Это выгодно.Вы бы предпочли ловить исключения за пределами границ и тому подобное в таких функциях, как vector::operator[], вместо того, чтобы расшифровывать повреждения памяти (все равно рекомендуется вызывать vector::at.) Однако есть и масса других вещей, которые используются для вставки отладочных хуковповлиять на ваш код и его производительность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...