-
Notifications
You must be signed in to change notification settings - Fork 183
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
scherbakov task #136
base: master
Are you sure you want to change the base?
scherbakov task #136
Changes from all commits
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,64 @@ | ||
continue | ||
report | ||
report['usersStats'][user_key] ||= {} | ||
user_key = "#{user['first_name']} #{user['last_name']}" | ||
report | ||
stats | ||
user | ||
continue | ||
report | ||
report['usersStats'][user_key] = report['usersStats'][user_key].merge(stats) | ||
report | ||
report['usersStats'][user_key] ||= {} | ||
report | ||
user_key = "#{user['first_name']} #{user['last_name']}" | ||
stats | ||
sessions.select('pluck') | ||
sessions.pluck('time') | ||
time_sessions = sessions.map { |s| s['time'] }.map(&:to_i) | ||
sessions.count | ||
sessions | ||
continue | ||
report | ||
collect_stats_for_user(report, u, sessions[u['id']]) | ||
report | ||
collect_stats_for_user(report, u, sessions[u['id']]) | ||
u = users.first | ||
users | ||
pp sessions["0"] | ||
sessions["0"] | ||
sessions | ||
continue | ||
sessions["0"] | ||
sessions | ||
line | ||
continue | ||
line | ||
continue | ||
sessions | ||
line | ||
continue | ||
line | ||
sessions | ||
continue | ||
sessions | ||
current_user | ||
continue | ||
current_user | ||
user | ||
sessions["0"] | ||
sessions | ||
line | ||
continue | ||
line | ||
continue | ||
sessions | ||
line | ||
continue | ||
line | ||
continue | ||
line | ||
continue | ||
line | ||
continue | ||
line |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,45 +12,76 @@ | |
Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
||
## Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика* | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: | ||
изначально дождаться выполнения скрипта на больших данных не удалось, поэтому непонятно каким должен быть бюджет в плане сложности. | ||
для начала сделаю так, чтобы данные были обработаны в течение, хотя бы, минуты. | ||
|
||
## Гарантия корректности работы оптимизированной программы | ||
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
||
## Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* | ||
|
||
Вот как я построил `feedback_loop`: *как вы построили feedback_loop* | ||
Вот как я построил `feedback_loop`: сначала выделить тест в отдельный файл. запустить профилировщики, посмотреть на самые тяжеловесные вычисления, | ||
попробовать их оптимизировать, запустить тест, проверить метрики. | ||
|
||
## Вникаем в детали системы, чтобы найти главные точки роста | ||
Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* | ||
Для того, чтобы найти "точки роста" для оптимизации я воспользовался профилировщиками. rbspy установить не получилось, | ||
какие то проблемы с зависимостями glibc на mint 20.3, stackprof выдал пустой результат, | ||
а ruby-prof оказался полезен. особо callgrind | ||
|
||
Вот какие проблемы удалось найти и решить | ||
|
||
### Ваша находка №1 | ||
- какой отчёт показал главную точку роста | ||
- как вы решили её оптимизировать | ||
- как изменилась метрика | ||
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? | ||
- Больше всего времени(21%) занимает метод collect_stats_from_users, в частности Date.parse все тормозит. | ||
- Посмотрел входные данные - увидел, что формат даты всегда одинаковый. значит вместо Date.parse можно использовать парсер, которому не нужно угадывать формат. | ||
Заменил Date.parse(d) на Date.new(*d.split('-')) | ||
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. с датой можно вообще ничего не делать |
||
- парсинг даты теперь не занимает времени совсем, и переместился с 1 места на 6, но чуть чуть прибавилось времени у split | ||
- вроде норм, перейдем к другим проблемам | ||
|
||
### Ваша находка №2 | ||
- какой отчёт показал главную точку роста | ||
- как вы решили её оптимизировать | ||
- как изменилась метрика | ||
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? | ||
|
||
### Ваша находка №X | ||
- какой отчёт показал главную точку роста | ||
- как вы решили её оптимизировать | ||
- как изменилась метрика | ||
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста? | ||
- теперь больше всего времени занимают IO операции, read(16%) и write(14%) | ||
- для вывода данных выбран формат json. можно попробовать сделать это с помощью библиотеки oj. | ||
для записи данных использовал Oj.to_file('result.json', report, mode: :compat) | ||
для чтения попробую использовать readlines | ||
- значительных изменений нет | ||
- IO::read осталось то же, IO::write даже увеличилось. мне кажется это нельзя изменить, поэтому нужно выбрать другую точку роста | ||
|
||
### Ваша находка №3 | ||
- collect_stats_from_users вызывается 7 раз и занимает 33% времени. с помощью kcachegrind увидел, что в нем много раз вызывается map | ||
- кажется, что метод много раз вызывается на одних и тех же данных. это похоже на n+1. порефакторим. | ||
- доля array#each уменьшилась с 13% до 6, | ||
- незначительно уменьшил долю each в общем выполнении | ||
|
||
### Ваша находка №4 | ||
- что если не нужно ждать окончания readlines, после которого выполнять обработку данных, а сразу выполнять обработку? | ||
получается читаем строку, сразу же ее обрабатываем - не нужно будет позже подгружать пользователя и все сессии. | ||
когда блок сессий закончится, эти же данные можно записать в файл, но это выглядит сложно, и проблема ведь не в памяти? | ||
можно начать с оптимизации подсчета статистик. например убрать .all? , использовать set для проверки уникальности браузеров | ||
- почему-то во flat.txt не указывается IO::write. время выполнения уменьшилось на 300мс. но тк из 500 прошлых 24% занимал IO::write, а это около 100, | ||
то можно считать, что улучшилось на 200. set реально работает | ||
|
||
### Ваша находка №5 | ||
- помимо метода readlines оставшееся время съедает некий Array.each... их в общем то в коде много, надо подумать как уменьшить проходы по спискам | ||
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. в таких случаях желательно уточнить, какой именно из вызовов each самый проблемный (или map, или ещё что-то) |
||
- each это линейная операция, значит нужно взять что-то лучше. бинпоиск не поможет тк сначала данные нужно отсортировать, у нас нет на это времени. | ||
можно взять хеш, у него время доступа O(1). осталось придумать только в каких местах можно его применить. | ||
на первый взгляд можно убрать отдельный цикл sessions.each для подсчета браузеров. сделать это внутри readlines | ||
- доля времени write выросла, each уменьшилась. большой файл все еще не читается | ||
|
||
### Ваша находка №6 | ||
- в цикле users.each происходит еще один цикл users.select. это я глазом заметил и тут я начинаю подозревать, что ruby_prof мне выдает просто рандомные числа, | ||
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.select на первом месте с большим отрывом |
||
потому что за последние несколько раз метрики то пропадали, но давали странные значения. и у всех абсолютное время 0 | ||
- формирование сессий можно вынести так же в цикл чтения. заменил список sessions на хеш. тогда нам вообще не нужен класс user и список users_objects | ||
- как изменилась метрика? а я не понял, потому что она не изменилась, но теперь большой файл возможно обработать. надо мне пошаманить с профилировщиками | ||
единственное полезное на что мне указал ruby_prof это Date.parse, а то что Array.each занимает много времени мне ни о чем не говорит | ||
|
||
## Результаты | ||
В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. | ||
Удалось улучшить метрику системы с того, что большой файл обработать не получалось, до того, что получилось ¯\_(ツ)_/¯ | ||
|
||
*Какими ещё результами можете поделиться* | ||
|
||
## Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* | ||
|
||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавил тест, проверяющий выполняется ли программа менее чем за минуту. | ||
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. да, если сделать алгоритм линейным, то можно аппроксимировать и сказать, что тест будет ок в случае если он обрабатывает 1/100 данных за 0.3с. |
||
сначала думал что норм тест, а потом подождал - не норм. представил проект, в котором все тесты такие. надо подумать как уменьшить объем данных | ||
чисто для теста. но это попозже, сейчас я устал |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
require 'ruby-prof' | ||
require_relative 'task-1' | ||
|
||
RubyProf.measure_mode = RubyProf::WALL_TIME | ||
|
||
result = RubyProf.profile do | ||
work(path: 'data.txt', disable_gc: true) | ||
end | ||
|
||
printer4 = RubyProf::CallTreePrinter.new(result) | ||
printer4.print(:path => "ruby_prof_reports", :profile => 'callgrind') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
require 'ruby-prof' | ||
require_relative 'task-1' | ||
|
||
RubyProf.measure_mode = RubyProf::WALL_TIME | ||
|
||
result = RubyProf.profile do | ||
work(path: 'data.txt', disable_gc: true) | ||
end | ||
|
||
printer = RubyProf::CallStackPrinter.new(result) | ||
printer.print(File.open('ruby_prof_reports/callstack.html', 'w+')) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
require 'ruby-prof' | ||
require_relative 'task-1' | ||
|
||
RubyProf.measure_mode = RubyProf::WALL_TIME | ||
|
||
result = RubyProf.profile do | ||
work(path: 'data.txt', disable_gc: true) | ||
end | ||
printer = RubyProf::FlatPrinter.new(result) | ||
printer.print(File.open("ruby_prof_reports/flat.txt", "w+")) |
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.
На самом деле самое проблемное место Array.select, где N^2 проходов по массиву в поисках сессий