|
|
|||||||||||||||||||||||||||||
|
О тонкостях повышения performance на С++, или как делать не надоИсточник: habrahabr viklequick
Вкратце, задача была такой - есть некий робот на С++, обдирающий HTML страницы, и собранное складывающий в БД (MySQL). С массой функционала и вебом на LAMP - но это к повествованию отношения не имеет. Предыдущая команда умудрилась на 4-ядерном Xeon в облаке получить фантастическую скорость сбора аж в 2 страницы в секунду, при 100% утилизации CPU как сборщика, так и БД на отдельном таком же сервере. К слову, поняв что они не справляются - "команда крепких профессионалов" из г. Бангалор сдалась и разбежалась, так что кроме горки исходников - "ничего! помимо бус" (С). О тонкостях наведения порядка в PHP и в схеме БД поговорим как-нибудь в другой раз, приведу только один пример приехавшего к нам мастерства.
Приступаем к вскрытию
Задачи из интерфейса разумеется складывались в БД, а робот 50 раз в секунду опрашивал - а не появилась ли новая задача? Причем данные естественно разложены так, как удобно интерфейсу, а не роботу. Итог - три inner join в запросе. Тут же увеличиваю интервал на "раз в секунду". Убираю безумный запрос, то есть - добавляю новую табличку из трех полей и пишу триггера на таблицы из веба, чтобы заполнялось автоматом, и меняю на простой
Новая картинка - сборщик по-прежнему занят на 100%, БД на 2%, теперь четыре страницы в секунду.
Берем в руки профилировщик
Вечер перестает быть томным, а я понимаю что работы - много и переписывать придется от души. И разумеется - парсить HTML Самое время ознакомиться - а что было улучшено до меня?
Об опасности premature optimizations мысленным лучомВидя, что БД загружена на 100%, ребята были твердо уверены, что тормозит вставка в список новых URL для обработки. Я даже затрудняюсь понять - чем они руководствовались, оптимизируя именно этот кусок кода. Но сам подход! У нас по идее тормозит вот тут, давайте мы затормозим еще. Для этого, они придумали такие трюки:
И конечно же много других перлов было в наличии. Вообще, наблюдать за эволюциями кода было весьма поучительно. Благо в запасливости не откажешь - все аккуратно закомментировано. Вот примерно так
Что делал я
Автоинкрементные поля также убрал, вместо них вставил UUID (для подсчета нового значения может приползать неявный lock table). Заодно серьезно уменьшил таблицу, а то по 20К на строчку - неудивительно что БД проседает. Магические константы также убрал, вместо них сделал нормальный thread pool с общей очередью задач и отдельной ниткой заполнения очереди, чтобы не пустовала и не переполнялась. Результат - 15 страниц в секунду. Однако, повторное профилирование не показало прорывных улучшений. Конечно, ускорение в 7 раз за счет улучшения архитектуры - это тоже хлеб, но - мало. Ведь по сути все исходные косяки остались, я убрал только вусмерть заоптимизированные куски.
Регулярные выражения для разбора мегабайтных структурированных файлов - это плохо
Ме-то-ди-ка! С грациозностью трактора ребята решали проблему доставания данных так (каждому действу свой набор регулярных выражений).
Удивительно с таким подходом, что оно хотя бы 2 страницы в секунду пережевывало. Понятно, сами выражения после их тюнинга я не привожу - это огромная простыня нечитаемых закорючек. Это еще не все - разумеется, была использована правильная библиотека boost, а все операции проводились над std::string ( правильно - а куда еще HTML складывать? char* не концептуально! Только хардкор! ). Вот отсюда и безумное количество реаллокаций памяти. Беру char* и простенький парсер HTML в SAX-style, нужные цифры запоминаю, параллельно вытаскиваю URL. Два дня работы, и вот. Результат - 200 страниц в секунду. Уже приемлемо, но - мало. Всего в 100 раз.
Еще один подход к снаряду
Первое, что бросается в глаза - это могучий класс URL, цельнотянутый из Java. Ну правильно - ведь это С++, он по любому быстрее будет, подумаешь что аллокаторы разные . Так что пачка копий и substring() - наше индусское все. И конечно же to_lower прямо к URL::host применять ни-ни - надо на каждом сравнении и упоминании и непременно boost-ом. Убираю чрезмерное употребление to_lower(), переписываю URL на char* без переаллокаций вместо std::string. Заодно оптимизирую пару циклов. Результат - 300 страниц в секунду. На этом закончил, ускорение было достигнуто в 150 раз, хотя еще были резервы для ускорения. И так убил больше 2х недель.
Выводы
И да пребудет с вами Ссылки по теме
|
|