-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cpu optimization #147
base: master
Are you sure you want to change the base?
Cpu optimization #147
Changes from all commits
a400fa7
56fa6a5
46c1920
06897ea
ee68989
2b29e40
6ec50e9
b87d704
6170021
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
data* | ||
!data3.txt | ||
result.json | ||
/ruby_prof_reports/ | ||
/stackprof_reports/ |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Case-study оптимизации | ||
|
||
## Актуальная проблема | ||
Задача - обработать файл с данными, чуть больше ста мегабайт. | ||
|
||
У нас уже была программа на `ruby`, которая умела делать нужную обработку. | ||
|
||
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время. | ||
|
||
## Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я решила использовать такую метрику: | ||
Итерации в секунду работы программы на входном файле в 13000 строк (`data3.txt`). Последние две итерации - использовала итерации в секунду для основного входного файла (`data_large.txt`). Бюджет на эту последнюю метрику - 30 секунд. | ||
|
||
## Гарантия корректности работы оптимизированной программы | ||
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
||
## Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроила эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за пару минут. | ||
|
||
Вот как я построила `feedback_loop`: | ||
- Профилировала программу на тестовом файле, используя один из [профилировщиков](#profiler-names) | ||
- Выделяла одну "точку роста" | ||
- Пробовала оптимизировать ее | ||
- Проверяла, как изменилась метрика | ||
- Смотрела, изменился ли отчет профилировщика | ||
- Если да, сохраняла изменения | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
## Вникаем в детали системы, чтобы найти главные точки роста | ||
Для того, чтобы найти "точки роста" для оптимизации я использовала <a id="profiler-names"></a>профилировщики `rbspy`, `ruby-prof` и `stackprof`. | ||
|
||
Вот какие проблемы удалось найти и решить | ||
|
||
### sessions.select для поиска сессий конкретного пользователя | ||
- Видно на отчете `ruby-prof` (использовала принтер `Flat`) | ||
- Для оптимизации я добавила хэш `uid_to_sessions`, в который сразу добавляла сессии для конкретных пользователей при парсинге файла | ||
- Метрика выросла с 0.750 i/s до **5.980 i/s** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. я бы тут не советовал использовать ips ips это больше для бенчмарков, когда что-то выполняется реально много раз в секунду а у нас тут < 1 ips зачастую |
||
- Исправленная проблема больше не была на первых строках отчета профилировщика | ||
|
||
### Подсчет количества уникальных браузеров | ||
- Следующую точку роста так же показал отчет `ruby-prof` (использовала принтер `Callstack`) | ||
- Для подсчета уникальных браузеров мы создавали массив, проходили его для каждой записи сессии и сравнивали, что ни один из элементов не равен браузеру из сессии. Для оптимизации я добавила `Set` для хранения уникальных браузеров, избавившись от массива | ||
- Метрика выросла до **7.790 i/s** | ||
- Проблема перестала появляться в первых строках отчета профилировщика | ||
|
||
### Обновление массивов users и sessions с помощью метода + | ||
- Определила эту точку роста при помощи профилировщика `ruby-prof`, используя принтер `graph` | ||
- Так как старые массивы нам нет надобности сохранять, заменила выражения | ||
```ruby | ||
sessions = sessions + ... | ||
users = users + ... | ||
``` | ||
на эквивалентные | ||
```ruby | ||
sessions.concat(...) | ||
users.concat(...) | ||
``` | ||
- Метрика выросла до **11.930 i/s** | ||
- Проблема перестала появляться в отчете профилировщика `ruby-prof` | ||
|
||
### Обновление массива users_objects с помощью метода + | ||
- Данная точка роста была обнаружена при помощи профилировщика `rbspy` (Появлялась только при прогоне программы на достаточно большом входном файле, файл `data_large.txt` до сих пор обрабатывался очень долго) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. лайк за rbspy; его киллер-фича что можно подключиться к работающему процессу и посмотреть где он там застрял или можно например профилировать процесс пумы и посмотреть на что уходит основное время работы сервера в целом |
||
- Поменяла, как и в предыдущем пункте, метод `+` на метод `concat` для обновления массива `users_objects` | ||
- На маленьком файле (`data3.txt`) метрика выросла лишь до **12.400 i/s**, но основной файл (`data_large.txt`) впервые начал обрабатываться менее чем за 45 секунд | ||
- `rbspy` перестал выводить данный метод | ||
|
||
### Использование Date.parse | ||
- Проблема найдена при помощи профилировщика `ruby-prof` | ||
- Заменила цепочку методов | ||
```ruby | ||
.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 } | ||
``` | ||
на | ||
```ruby | ||
.sort | ||
.reverse | ||
.map do |date| | ||
date_ary = date.split('-') | ||
Date.new(date_ary[0].to_i, date_ary[1].to_i, date_ary[2].to_i).iso8601 | ||
end | ||
``` | ||
- Метрика выросла до **15.200 i/s**, а метрика для файла `data_large.txt` составила **0.033 i/s** | ||
- Метод `Date.parse` стал занимать меньше процентов в отчете профилировщика | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. с датой можно вообще ничего не делать, это пасхалочка |
||
|
||
### Array.each внутри `collect_stats_from_users` | ||
- Показывали профилировщики `ruby-prof` и `stackprof` | ||
- Оптимизировала вычисление статов - слила вызов шести разных методов в один `evaluate_stats` | ||
- Метрика для файла `data_large.txt` увеличилась до **0.037 i/s** (таким образом стала укладываться в бюджет) | ||
- Метод Array.each перестал быть главной точкой роста в отчете профилировщика `ruby-prof` | ||
|
||
### Array#split и Array#[] внутри метода parse_to_users_and_sessions | ||
- Показывал профилировщик `ruby-prof` | ||
- Заменила вызов этих двух методов на вызов `.start_with?` | ||
- Метрика выросла до **0.042 i/s** | ||
- Методы стали занимать меньше процентов в отчете профилировщика | ||
|
||
## Результаты | ||
В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
Удалось улучшить метрику системы - количество итераций работы программы на файле `data3.txt` - c *0.750 i/s* до *20.820 i/s*, а количетсво итераций работы программы на файле `data_large.txt` до **0.042 i/s** и уложиться в заданный бюджет. | ||
|
||
## Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы я добавила perfomance-тест `spec/task-1_spec.rb` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Пара минут 👌👌