-
Notifications
You must be signed in to change notification settings - Fork 192
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
[potashin] optimization #141
base: master
Are you sure you want to change the base?
[potashin] optimization #141
Conversation
- исправленная проблема перестала быть главной точкой роста. | ||
|
||
### Находка №6 | ||
- `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `7.20% (15.82%) Array#include? [846230 calls, 846230 total]` |
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.
вот так чтобы не натыкаться на грабли собственной оптимизации, имеет смысл чуть расширять контекст кода, который нужно оптимизировать (я имею в виду находку №4)? т.е. условно не all?
, а все формирование уникальных элементов.
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.
хм, я бы сказал можно так и так
с одной стороны в 4м шаге ты убрал на тот момент топовую проблему из топа, и перешёл к следующей; тут она опять пробилась в топ - опять поправил - по идее это весьма логично
с другой стороны тут случай, что можно заметить, что есть возможность использовать более подходящую структуру данных/алгоритм, по идее можно эту сразу было сделать (но тут есть риск что-то поломать иногда; но тесты должны подстраховать; но это в случае если они есть)
### Находка №7 | ||
- `ruby-prof` в режиме `CallGrind` показывает, что точкой роста является `Object::collect_stats_from_users`-> `Array::map`->`String::upcase` | ||
- поскольку используется только `upcase` версия браузера, при парсинге сессия сразу записываем `upcase` версию. Поскольку не так много видов браузеров относительно общего количества сессий, используем мемоизацию. | ||
- время выполнения программы для 1кк входных данных сократилось с 6.9 до с 6.4c |
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.
тут интересно, что несмотря на то, что это главная точка роста, время сократилось не так сильно, как на следующем этапе
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.
такое возможно из-за GC;
когда мы профлируем CPU, есть такая тонкость; возможно какая-то строчка не тормозит сама по себе, но создаёт много лишних объектов, которые потом наступит время убирать
поэтому если хочешь ускорить время работы максимально, то имеет смысл с обоих сторон смотреть - и по CPU, и по памяти (хотя на первый взгляд память это не про скорость; особенно в этом плане наверно важно кол-во аллоцированных объектов; если их слишком много создаётся и удаляется, то будет тормозить)
8cb83b6
to
41177e6
Compare
41177e6
to
7dbada5
Compare
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.
Привет! Всё топчик, постарался развёрнуто покомментить. Вероятно ты это всё и сам знаешь, но просто в качестве беседы тогда ✅
@@ -0,0 +1,10 @@ | |||
result.json | |||
data*.txt |
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.
respect за gitignore
надо бы добавить в репу, а то иногда прилетают PR'ы на 200к строк кода
case-study.md
Outdated
Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
||
## Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: время выполнения программы для части данных (сначала 50к, потом 100к). |
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.
++
тут получается немного tricky
у нас есть некоторая целевая метрика, но мы не можем ей пользоваться из-за того что даже не можем дождаться
В качестве выхода мы можем взять какие-то промежуточные метрики; например, чтобы оценить одно изменение, или парочку.
Плюс если мы понимаем асимптотику (например если она линейная), то мы можем и довольно-таки обоснованно прикинуть, что для примера 1/100 данных должна обрабатываться за 300 мс, и тогда вероятно общее время выполнения будет около 30с
Хорошо подбирать объём данных так, чтобы программа успевала покрутиться пару секунд. Если она завершается слишком быстро (“не успевает поработать”) могут возникнуть какие-то перекосы (например, на полном объёме основная часть времени тратится в основном цикле, а если данных мало - то большая часть уходит на инициализацию и финализацию, например на чтение из файла и запись потом в файл)
И плюс когда время уже на миллисекунды - больше влияние погрешностей.
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
||
## Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* |
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.
*это был плейсхолдер*
- rbspy показал `83.55 83.55 block (2 levels) in work - task-1.rb:101`: вызов `sessions.filter {}` на каждой итерации по `users.each`; | ||
- перед `users.each` сгруппировал `sessions_by_user = sessions.group_by { |session| session['user_id'] }`, в `each` использовал как `sessions_by_user[user['id']] || []` | ||
- время выполнения программы для 100к входных данных сократилось с 115с до 4с | ||
- исправленная проблема перестала быть главной точкой роста, rbspy показал, что теперь это `98.49 100.00 block in work - task-1.rb:56` |
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.
и самое главное асимптотика уже стала из квадратичной - линейной
- исправленная проблема перестала быть главной точкой роста, rbspy показал, что теперь это `98.49 100.00 block in work - task-1.rb:56` | ||
|
||
### Находка №2 | ||
- stackprof cli показал `7126 (99.4%) 11 (0.2%) Array#each`, он вызывается несколько раз, наибольшее `6504 ( 91.3%) Object#work]`. Поскольку rbspy указывал на `task-1.rb:56`, что является `end` `each` блока, пробую вынести этот`each` в отдельный метод `parse_file`и подтвердить гипотезу, которая и подтверждается: `5765 (99.8%) 5525 (95.7%) Object#parse_file`. Теперь нужно разобраться, какая именно операция в этом блоке `each` требует оптимизации, `stackprof stackprof.dump --method Object#parse_file` показывает, что это заполнение массива сессий: `5261 (93.2%) / 5133 (90.9%) | 52 | sessions = sessions + [parse_session(line)] if cols[0] == 'session'`. |
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.
респект, что уточнил чуть глубже куда конкретно время уходит
- исправленная проблема перестала быть главной точкой роста. | ||
|
||
### Находка №6 | ||
- `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `7.20% (15.82%) Array#include? [846230 calls, 846230 total]` |
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.
хм, я бы сказал можно так и так
с одной стороны в 4м шаге ты убрал на тот момент топовую проблему из топа, и перешёл к следующей; тут она опять пробилась в топ - опять поправил - по идее это весьма логично
с другой стороны тут случай, что можно заметить, что есть возможность использовать более подходящую структуру данных/алгоритм, по идее можно эту сразу было сделать (но тут есть риск что-то поломать иногда; но тесты должны подстраховать; но это в случае если они есть)
### Находка №7 | ||
- `ruby-prof` в режиме `CallGrind` показывает, что точкой роста является `Object::collect_stats_from_users`-> `Array::map`->`String::upcase` | ||
- поскольку используется только `upcase` версия браузера, при парсинге сессия сразу записываем `upcase` версию. Поскольку не так много видов браузеров относительно общего количества сессий, используем мемоизацию. | ||
- время выполнения программы для 1кк входных данных сократилось с 6.9 до с 6.4c |
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.
такое возможно из-за GC;
когда мы профлируем CPU, есть такая тонкость; возможно какая-то строчка не тормозит сама по себе, но создаёт много лишних объектов, которые потом наступит время убирать
поэтому если хочешь ускорить время работы максимально, то имеет смысл с обоих сторон смотреть - и по CPU, и по памяти (хотя на первый взгляд память это не про скорость; особенно в этом плане наверно важно кол-во аллоцированных объектов; если их слишком много создаётся и удаляется, то будет тормозить)
case-study.md
Outdated
|
||
## Результаты | ||
В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
Удалось улучшить метрику системы на 100к с 115с до 5с секунд и уложиться в заданный бюджет. Для полного файла время выполнение стало 24с. |
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.
👍
case-study.md
Outdated
Удалось улучшить метрику системы на 100к с 115с до 5с секунд и уложиться в заданный бюджет. Для полного файла время выполнение стало 24с. | ||
|
||
## Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавил два теста: прогон на 100к данных до 5 секунд, проверка на линейную асимптотику на основе данных от 1000 до 100000 записей. |
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.
👍
d36a77e
to
f6610b9
Compare
No description provided.