второй. Вынос strlen за тело цикла
Повторный запуск "обновленной" программы под профилировщиком показывает, что количество "горячих" точек в ней уменьшилось с 187 до 106. Конечно, это хорошо, но ведь горячие точки все еще есть! Кликнув в окне "View" расположенным в правом верхнем углу окна "Hot Spots" по радио – кнопке "Hotspots by function" (сортировать горячие точки по функциями), мы узнаем, что ~80% времени наша программа проводит в недрах функции Calculate CRC, затем с большим отрывом следует gen_pswd – ~12% и по ~3% делят функции Check CRK и do_pswd.
Ну это никуда не годится! Какая-то там задрипанная Calculate CRC без зазрения совести поглощает практически все быстродействие программы! Эх, вот бы еще узнать, какая именно часть функции в наибольшей степени влияет на производительность… И VTune позволяет это сделать!
Дважды кликнем по красному прямоугольнику, чтобы увеличить его на весь экран. Оказывается, внутри функции Calculate CRC насчитывается 18 горячих точек, три их которых наиболее горячи – ~30%, ~25% и ~10% соответственно (см. рис 0x003). Вот с первой из них мы и начнем. Дважды кликнем по самому высокому из прямоугольников и… VTune обижено пискнув сообщит, что "No source found for offset 0x69 into F:\.OPTIMIZE\src\Profil\pswd.exe. Proceed with disassembly only?" ("Исходные тексты не найдены. Продолжать с отображением только дизассемблерного текста?"). Действительно, поскольку программа откомпилирована без отладочной информации, то VTune не может знать, какой байт ассемблерного когда, какой строке соответствует, а компилятор не соглашается предоставить эту информацию в силу того, что в оптимизированной программе соответствие между исходным текстом и сгенерированным машинным кодов в общем-то не столь однозначно.
Конечно, можно профилировать и не оптимизированную программу, – но… какой в этом резон? Ведь это будет другая программа и с другими
горячими точками! По любому, качественная оптимизация без знаний ассемблера невозможна, поэтому, прогнав все страхи прочь, смело нажмем кнопочку, "ОК", то есть "Да, мы соглашаемся работать без исходных текстов непосредственно с ассемблерным кодом".
Рисунок 8 0x003 Распределение температуры внутри функции Calculate CRC (снимок сделан с высоким разрешением)
VTune тут же тыкает нас носом в инструкцию REPNE SCANB. Не нужно быть провидцем, чтобы распознать в ней ядро функции strlen. Использовали ли мы strlen в исходном тексте программы? А то как же! Смотрим сюда:
int CalculateCRC(char *pswd)
{
int a;
int x = -1; // ошибка вычисления CRC
for (a = 0; a <= strlen(pswd); a++)
x += *(int *)((int)pswd + a);
return x;
}
Листинг 13 Вызов функции strlen в заголовке цикла привел к тому, что компилятор, не распознав в ней инварианта, не вынес ее из цикла, "благодаря" чему длина одной и той же строки стала подсчитываться на каждой итерации
Судя по всему бестолковый компилятор не вынес вызов strlen за тело цикла, хотя ее аргумент – переменная pswd не модифицировалась в цикле! Хорошо, если гора не идет к Магомету, пойдем навстречу компилятору и перепишем этот участок кода так:
int length;
length=strlen(pswd);
for (a = 0; a <= length; a++)
Листинг 14 Вынос функции strlen за пределы цикла
Перекомпилировав программу, мы с удовлетворением отметим, что теперь ее быстродействие возросло до трех с половиной миллионов паролей в секунду, т.е. практически в два с половиной раза больше, чем было в предыдущем случае.