В нашем проекте возникла серьёзная проблема.
Необходимо было обработать файл с данными, чуть больше ста мегабайт.
У нас уже была программа на ruby, которая умела делать нужную обработку.
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.
Я решил исправить эту проблему, оптимизировав эту программу.
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: Стремится к тому, чтобы метод должен выполняться быстрее 30 секунд.
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации.
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный feedback-loop, который позволил мне получать обратную связь по эффективности сделанных изменений за время, которое у вас получилось
Вот как я построил feedback_loop: как вы построили feedback_loop
Для того, чтобы найти "точки роста" для оптимизации я воспользовался инструментами, которыми вы воспользовались:
- добавил прогрессбар, понять, что нет смысла ждать, когда доедет до конца
- добавил ассерты, по которым принял решение, что будут укладываться по времени в нужный нам бюджет
- Добавил ruby-prof и на основе мини файла проверил, как распределяется нагрузка у программы
Вот какие проблемы удалось найти и решить
- parser_progressbar, показал что огромную часть времени занимает парсинг строк (более 10 минут, хотя должно быть менее 30 секунд)
- вместо сложения массивов использовал Array#push элемента в существующий массив
- уменьшил с >10 мин до 70с
- перестала быть главной точкой роста ибо у users.each (прогрессбар показывает более 4 дней выполнения)
- Подсчёт количества уникальных браузеров занимает времени больше чем 30с, значит в любом случае нужно оптимизировать
- сгруппировать по ключу "браузер" и взять только ключи
-
30c изменилось на моментальную
- перестала быть главной точкой роста
- Сбор браузеров занимает около 1.5 минуты
- использовать результат полученный ранее и вывести в строку
-
1.5мин изменилось на моментальную
- перестала быть главной точкой роста
-
users.each (прогрессбар показывает более 4 дней выполнения)
-
сгруппировал сессии по user_id вне массива, чтоб не заниматься этим каждый раз, по мимо этого изменил формат добавления в user_objects использую оператор "<<" вместо сложения сумм массивов
-
изменилась с >4 дней до 52c (Группировка сессий - 43c, Сбор cтатистики по пользователям - 9c)
-
перестала быть точкой роста
- парсинг строк (file_lines.each) показал что работа составляет около 1 минуты и 4с что больше чем 30с
- вместо сложения массивов использовал Array#push элемента в существующий массив
- уменьшил с >10 мин до 7c
- перестала быть главной точкой роста
- метод collect_stats_from_users на данный момент занимает около 33с работы от 41с вообщем
- добавил предварительные вычисления, теперь все данные для пользователя собираются в одном хеше и сразу обновляют соответствующую запись
- уменьшил с 33с до 15.30c
- перестала быть главной точкой роста
В результате проделанной оптимизации наконец удалось обработать файл с данными. Удалось улучшить метрику системы с так называемой бесконечности(не смог дождаться завершения) до 17-18 секунд и уложиться в заданный бюджет.
Какими ещё результами можете поделиться
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавлен тест о том, что он должен выполняться быстрее 30с