В нашем проекте возникла серьёзная проблема.
Необходимо было обработать файл с данными, чуть больше ста мегабайт.
У нас уже была программа на ruby, которая умела делать нужную обработку.
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.
Я решил исправить эту проблему, оптимизировав эту программу.
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: Calculating ------------------------------------- Process 2500 4.440 (± 0.0%) i/s - 23.000 in 5.181137s Process 5000 1.295 (± 0.0%) i/s - 7.000 in 5.406855s Process 10000 0.363 (± 0.0%) i/s - 2.000 in 5.512669s Process 20000 0.069 (± 0.0%) i/s - 1.000 in 14.437365s Process 40000 0.012 (± 0.0%) i/s - 1.000 in 81.002661s
Comparison: Process 2500: 4.4 i/s Process 5000: 1.3 i/s - 3.43x slower Process 10000: 0.4 i/s - 12.23x slower Process 20000: 0.1 i/s - 64.11x slower Process 40000: 0.0 i/s - 359.68x slower
Программа поставлялась с тестом. Выполнение этого теста позволяет не допустить изменения логики программы при оптимизации.
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный feedback-loop, который позволил мне получать обратную связь по эффективности сделанных изменений за
Вот как я построил feedback_loop: создал из исходного файла с данными, файлы меньшим размером, ориентируясь на количество строк
head -n N data_large.txt > dataN.txt
Подготовил файл asymptotics.rb который автоматически собирает все метрики и делает некоторые замеры.
Для того, чтобы найти "точки роста" для оптимизации я воспользовался rubyprof (WALL_TIME, ALLOCATIONS), rbspy, valgrind
Вот какие проблемы удалось найти и решить
При увеличении количества обрабатываемых строк исходных данных в два раза, время выполнения программы увеличивается в пять раз, количество потребляемой памяти в четыре раза.
Было обнаружено, что на выполнение следующей строки кода уходит 93% времени
user_sessions = sessions.select { |session| session['user_id'] == user['id'] }
В этой строке происходит выборка сессий пользователя, после чего создается новый объект Пользователь с выбранными сессиями и остальными атрибутами.
Для оптимизации этой логики было принято решение избавится от объекта Пользователя в пользу Хэша, с аналогичными параметрами, которые можно собирать в момент обработки исходного файла.
Результат оптимизации в папке metrics/optimizations Получилось избавиться от кратной регрессии - увеличение объема данных в два раза, в приблизительно в два раза увеличивает время исполнения программы и в два раза увеличивает потребляемый объем оперативной памяти
В результате проделанной оптимизации наконец удалось обработать файл с данными. Удалось улучшить метрику системы с того, что нельзя было дождаться исполнения скрипта, до того, что 1_000_000 строк обрабатывается за 36 секунд и потребляет 1Gb оперативной памяти Так как параметры необходимой оптимизации не устанавливались, оптимизацию на этом прекратил.
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавлен тест на производительность