Conversation
| - исправленная проблема перестала быть главной точкой роста. | ||
|
|
||
| ### Находка №6 | ||
| - `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `7.20% (15.82%) Array#include? [846230 calls, 846230 total]` |
There was a problem hiding this comment.
вот так чтобы не натыкаться на грабли собственной оптимизации, имеет смысл чуть расширять контекст кода, который нужно оптимизировать (я имею в виду находку №4)? т.е. условно не all?, а все формирование уникальных элементов.
There was a problem hiding this comment.
хм, я бы сказал можно так и так
с одной стороны в 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.
тут интересно, что несмотря на то, что это главная точка роста, время сократилось не так сильно, как на следующем этапе
There was a problem hiding this comment.
такое возможно из-за GC;
когда мы профлируем CPU, есть такая тонкость; возможно какая-то строчка не тормозит сама по себе, но создаёт много лишних объектов, которые потом наступит время убирать
поэтому если хочешь ускорить время работы максимально, то имеет смысл с обоих сторон смотреть - и по CPU, и по памяти (хотя на первый взгляд память это не про скорость; особенно в этом плане наверно важно кол-во аллоцированных объектов; если их слишком много создаётся и удаляется, то будет тормозить)
8cb83b6 to
41177e6
Compare
41177e6 to
7dbada5
Compare
spajic
left a comment
There was a problem hiding this comment.
Привет! Всё топчик, постарался развёрнуто покомментить. Вероятно ты это всё и сам знаешь, но просто в качестве беседы тогда ✅
| @@ -0,0 +1,10 @@ | |||
| result.json | |||
| data*.txt | |||
There was a problem hiding this comment.
respect за gitignore
надо бы добавить в репу, а то иногда прилетают PR'ы на 200к строк кода
case-study.md
Outdated
| Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
|
||
| ## Формирование метрики | ||
| Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: время выполнения программы для части данных (сначала 50к, потом 100к). |
There was a problem hiding this comment.
++
тут получается немного tricky
у нас есть некоторая целевая метрика, но мы не можем ей пользоваться из-за того что даже не можем дождаться
В качестве выхода мы можем взять какие-то промежуточные метрики; например, чтобы оценить одно изменение, или парочку.
Плюс если мы понимаем асимптотику (например если она линейная), то мы можем и довольно-таки обоснованно прикинуть, что для примера 1/100 данных должна обрабатываться за 300 мс, и тогда вероятно общее время выполнения будет около 30с
Хорошо подбирать объём данных так, чтобы программа успевала покрутиться пару секунд. Если она завершается слишком быстро (“не успевает поработать”) могут возникнуть какие-то перекосы (например, на полном объёме основная часть времени тратится в основном цикле, а если данных мало - то большая часть уходит на инициализацию и финализацию, например на чтение из файла и запись потом в файл)
И плюс когда время уже на миллисекунды - больше влияние погрешностей.
| Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
|
||
| ## Feedback-Loop | ||
| Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* |
| - 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.
и самое главное асимптотика уже стала из квадратичной - линейной
| - исправленная проблема перестала быть главной точкой роста, 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.
респект, что уточнил чуть глубже куда конкретно время уходит
| - исправленная проблема перестала быть главной точкой роста. | ||
|
|
||
| ### Находка №6 | ||
| - `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `7.20% (15.82%) Array#include? [846230 calls, 846230 total]` |
There was a problem hiding this comment.
хм, я бы сказал можно так и так
с одной стороны в 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.
такое возможно из-за GC;
когда мы профлируем CPU, есть такая тонкость; возможно какая-то строчка не тормозит сама по себе, но создаёт много лишних объектов, которые потом наступит время убирать
поэтому если хочешь ускорить время работы максимально, то имеет смысл с обоих сторон смотреть - и по CPU, и по памяти (хотя на первый взгляд память это не про скорость; особенно в этом плане наверно важно кол-во аллоцированных объектов; если их слишком много создаётся и удаляется, то будет тормозить)
case-study.md
Outdated
|
|
||
| ## Результаты | ||
| В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
| Удалось улучшить метрику системы на 100к с 115с до 5с секунд и уложиться в заданный бюджет. Для полного файла время выполнение стало 24с. |
case-study.md
Outdated
| Удалось улучшить метрику системы на 100к с 115с до 5с секунд и уложиться в заданный бюджет. Для полного файла время выполнение стало 24с. | ||
|
|
||
| ## Защита от регрессии производительности | ||
| Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавил два теста: прогон на 100к данных до 5 секунд, проверка на линейную асимптотику на основе данных от 1000 до 100000 записей. |
d36a77e to
f6610b9
Compare
No description provided.