Пример использования инструментов профилирования и оптимизации памяти в Ruby
В файле deotimized.rb
находится ruby-программа, которая выполняет обработку данных из файла.
В файл встроен тест, который показывает, как программа должна работать.
С помощью этой программы нужно обработать файл данных data_large.txt
.
Проблема в том, что это происходит слишком долго, дождаться пока никому не удавалось.
- Профилировать программу с помощью различных инструментов
- Добиться того, чтобы программа корректно обработала файл
data_large.txt
;
Файл data_large.txt
- 129Mb слишком большой для быстрого feedback, создан файл data.txt
на 380Kb, все замеры производятся на нем.
memory_profiler
показал что наибольшее количество памяти аллоцируется массивами
allocated memory by class
-----------------------------------
416.75 MB Array
в стоке конкатенации массивов sessions = sessions + [parse_session(line)] if cols[0] == 'session'
Замена на Array#push
allocated memory by class
-----------------------------------
129.58 MB Array
memory_profiler
показал что наибольшее количество объектов аллоцируется строками
allocated objects by class
-----------------------------------
224211 String
использование # frozen_string_literal: true
allocated objects by class
-----------------------------------
188061 String
stackprof
показал, что наибольшее количество семплов приходится на метода parse_session, вызов String#split
TOTAL (pct) SAMPLES (pct) FRAME
407692 (100.0%) 294644 (72.3%) Object#work
76176 (18.7%) 76176 (18.7%) Object#parse_session
191118 (46.9%) 24584 (6.0%) Object#collect_stats_from_users
12288 (3.0%) 12288 (3.0%) Object#parse_user
Убрать лишние вызовы String#split
TOTAL (pct) SAMPLES (pct) FRAME
329228 (100.0%) 294644 (89.5%) Object#work
191118 (58.1%) 24584 (7.5%) Object#collect_stats_from_users
8464 (2.6%) 8464 (2.6%) Object#parse_session
1536 (0.5%) 1536 (0.5%) Object#parse_user
ruby-prof
в режиме WALL_TIME
выявил, что 90% процента времени тратятся в методе Array#select
Total allocated: 142.14 MB
Использовать Hash вместо Array для коллекций user и sessions Потребление RAM упало c 140Mb до 21Mb, скорость выполнения программы на 10_000 строк снизилась до 0.1c
Total allocated: 21.44 MB
Около 20% времени уходит на прасинг даты
Убрать ненужный парсинг, скорость выполнения программы на файле 100_000 строк снизилась с 0.95 до 0.70 сек
ruby-prof
в режиме MEMORY
выявил, что большое количество памяти тратится на чтение файла
Считывать файл построчно с помощью File.open(file).each
Удалось улучшить скорость программы более, чем в 44 раза
Calculating -------------------------------------
deotimized 0.746 (± 0.0%) i/s - 4.000 in 5.366921s
optimized 32.824 (± 3.0%) i/s - 166.000 in 5.059471s
Comparison:
optimized: 32.8 i/s
deotimized: 0.7 i/s - 44.02x (± 0.00) slower
Уменьшить потребление памяти более чем в 4 раза
Файл data_large.txt
обрабатывается за ~25сек.
Измерение времени программы time_bench.rb
Не оптимизированный результат: 1.40s
Запрос у ОС на количество потребляемой памяти rss_bench.rb
Не оптимизированный результат на файле 10_000 строк:
Before 19 MB
After 72 MB
Класс показывающий количество аллоцировнных объектов object_space_bench.rb
Не оптимизированный результат на файле 10_000 строк: Total objects diff: 119416
gem memory_profiler
Семплирующий профайлер показывающий количество аллоцировнных объектов memory_profiler_bench.rb
Семплирующий профайлер показывающий количество аллоцировнных объектов, можно создать flamegraph и graphviz
stackprof_bench.rb
Трасирующий профайлер