В нашем проекте возникла серьёзная проблема.
Необходимо было обработать файл с данными, чуть больше ста мегабайт.
У нас уже была программа на ruby, которая умела делать нужную обработку.
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.
Я решил исправить эту проблему, оптимизировав эту программу.
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: использованая память (memory usage)
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации.
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный feedback-loop, который позволил мне получать обратную связь по эффективности сделанных изменений за пол минуты без времени работы программы
Вот как я построил feedback_loop: замер -> изменения в точках роста -> проверка работоспособности -> замер
Для того, чтобы найти "точки роста" для оптимизации я воспользовался профайлерами памяти, в первую очередь гемом memory_profiler, файлом с 50 000 строк
Вот какие проблемы удалось найти и решить
- гем memory_profiler показал строчку
sessions = sessions + [parse_session(line)] if cols[0] == 'session' - убрал клонирование массива и начал использовать оператор <<
- метроика улучшилась с 8 ГБ до 3.5 ГБ
- теперь показывает другую строку
- гем memory_profiler показал строчку
user_sessions = sessions.select { |session| session['user_id'] == user['id'] } - использовать ту же оптимизацию что использовал в прошлом задании
- метроика улучшилась с 3.5 ГБ до 772 МБ
- теперь показывает другую строку
- гем memory_profiler показал строчку
users = users + [parse_user(line)] if cols[0] == 'user' - так же как и в первой находке
- метроика улучшилась с 772 МБ до 517 МБ
- теперь показывает другую строку
- гем memory_profiler показал строчку
{ 'dates' => user.sessions.map{|s| s['date']}.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 } } - использовать ту же оптимизацию что использовал в прошлом задании
- метроика улучшилась с 517 МБ до 295 МБ
- теперь показывает другую строку
- прочитал в задании что программу надо переписать в потоковом стиле
- попробовал переписать читая по строчке за раз
- метроика улучшилась с 295 МБ до 35 МБ
- теперь показывает другую строку
В результате проделанной оптимизации наконец удалось обработать файл с данными. Удалось улучшить метрику системы с более 8000+ мегабайт с оригинальным кодом и файлом данными на 50000 строк до 35 МБ с исправленым кодом и файлом data_large.txt и уложиться в заданный бюджет.
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы написал тест соответствующий требованию бюджета