-
Notifications
You must be signed in to change notification settings - Fork 24
Shelestov/homework 1 #9
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
base: master
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -12,35 +12,132 @@ | |
| Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
|
||
| ## Формирование метрики | ||
| Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика* | ||
| Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: | ||
|
|
||
| Я решил произвести замеры использованной памяти (инструмент — ruby-prof). | ||
| Время выполнения скрипт (на следующем наборе входных данных: [1, 10, 100, 1_000, 10_000, 100_000] строк). | ||
| Количество созданных объектов (сфокусировался на классах, символах, массивах и строках). | ||
|
|
||
| ## Гарантия корректности работы оптимизированной программы | ||
| Программа поставлялась с тестом. Выполнение этого теста позволяет не допустить изменения логики программы при оптимизации. | ||
|
|
||
| ## Feedback-Loop | ||
| Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* | ||
|
|
||
| Вот как я построил `feedback_loop`: *как вы построили feedback_loop* | ||
| Вот как я построил `feedback_loop`: | ||
|
|
||
| Я немного изменил код, чтобы считывать не весь большой файл целиком, а только первые его x линий: | ||
| ```ruby | ||
| def work(lines = nil) | ||
| file_name = lines ? 'data_large.txt' : 'data.txt' | ||
| file_lines = lines ? File.read(file_name).split("\n", lines) : File.read(file_name).split("\n") | ||
| ... | ||
| ``` | ||
| Это позволило мне проводить замеры не только на тестовых данных, но и на большом объеме данных (например, на 10_000 строках). | ||
| Для сбора метрики и прогона программы, я добавил еще несколько тестов. | ||
|
|
||
| ## Вникаем в детали системы, чтобы найти 20% точек роста | ||
| Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* | ||
| Для того, чтобы найти "точки роста" для оптимизации я воспользовался: глазами, опытом, и результатами снятия метрик. | ||
|
|
||
| Вот какие проблемы удалось найти и решить | ||
|
|
||
| ### Ваша находка №1 | ||
| О вашей находке №1 | ||
| Замеры времени выполнения программы показали, что независимо от того, сколько мы позже хотим обработать строк, | ||
| загрузка большого файла целиком занимает в среднем 6,5 секунд на моей машине. | ||
|
|
||
| Решил начать с того, чтобы грузить файл не целиком, а ровно столько его строк, сколько нам понадобиться. | ||
| Как минимум это позволит быстрее проводить замеры при дальнейшей оптимизации. | ||
|
|
||
| В итоге теперь файл считывается в память не целиком, а только нужно количество первых его строк. | ||
| Что сократило время "разогрева" с 6 секунд до 0.000200. | ||
|
|
||
| ### Ваша находка №2 | ||
| О вашей находке №2 | ||
| Погонял замеры времени выполнения скрипта с таким набором данных: `[1, 10, 100, 1_000, 10_000, 100_000]`. | ||
| Получил интересный результат (в секундах): | ||
| ```bash | ||
| bench_work 0.000915 0.001853 0.001687 0.040583 1.515984 315.730440 | ||
| ``` | ||
| Глядя на радикальный прирост тормозов на 100_000 строк, можно сделать вывод, что дело не в количество строк (прирост строк всего лишь 10 кратный), | ||
| а в использовании памяти и неэффективных алгоритмах. | ||
|
|
||
| Подобрал значение, которое выполняется в среднем за 6 секунд (20_000 строк), | ||
| и решил взять этот параметр за исходную точку для дальнейших замеров памяти и производительности. | ||
|
|
||
| Результаты профилирования показали, что самое узкое место на данный момент приходится на операцию Array#select. | ||
| Общее кол-во памяти, отведенной на эти операции, составило 414 Mb (из 470 всего). Когда количество вызовов этой функции всего 3,046 (по количеству users в первых 20_000 строк). | ||
| То есть, не самая частая операция отъедает почти 89% памяти. | ||
|
Owner
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. Скорее всего, это была не память, а время |
||
|
|
||
| Путем рефакторинга удалось получить следующие показатели (сокращение времени на 100_000 строках более чем в 157 раз): | ||
|
|
||
| ```bash | ||
| bench_work 0.001317 0.000652 0.001504 0.019504 0.231620 2.087732 | ||
| ``` | ||
|
|
||
| На 20_000 строчках памяти же теперь используется всего ~17 Mb против 466 Mb ранее, а времени 0,3 секунды, против 6. | ||
|
|
||
| ### Ваша находка №3 | ||
| Следующий метод на очереди, который съедает почти 70% памяти (или 13 Mb), оказался Array#each в методе `collect_stats_from_users` | ||
| Там куча `Array#map`, повторных проходов по массиву пользователей, и все такое. Вынес все это в виде методов модели User, закэшировал значения. | ||
| Путем всяких прочих хитрых рефакторингов удалось снизить расход памяти на этих операциях до 7 Mb. Что почти в два раза меньше. Время тоже сократилось в два раза. | ||
| Но повторные запуски метрики показали, что рефакторинг привел к большому росту расхода памяти на постройку хэша и последующую его конвертацию в json для записи на диск. | ||
| Но это уже друга история. | ||
|
|
||
| P.S. Кстати, нашел бажок. В тестах этого кейса нет. Если у пользователя вообще нет ни одной сессии, то он попадает в статистику как: | ||
|
Owner
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. 👍 |
||
| ```json | ||
| ... | ||
| "usedIE": false, | ||
| "alwaysUsedChrome": true, | ||
| ... | ||
| ``` | ||
|
|
||
| ### Ваша находка №4 | ||
| После всех манипуляций почему-то вырос расход памяти на конвертацию финального хэша в json. Аж до 17 Mb. | ||
|
|
||
| Методом тыка обнаружил, что все дело было в моей "оптимизации" даты. Я ее стал хранить в хэше не в виде строк, а виде объекта Date. | ||
| Видимо потом #to_json не умело как-то ее конвертил, отчего отожрал памяти. | ||
|
|
||
| ### Ваша находка №X | ||
| О вашей находке №X | ||
| Вернул обратно строку, теперь весь стек с 20_000 строками укладывается в ~12 Mb. | ||
| Есть еще куда оптимизировать. | ||
|
|
||
| ### Ваша находка №5 | ||
| При помощи утилиты профилирования и бенчмарка удалось найти очередное бутылочное горлышко. А точнее, сразу два. | ||
| Первое — парсинг даты в объект. | ||
| Второе — String#split создавал дополнительные объекты массивов, ел нехило памяти. | ||
|
|
||
| С первым решилось просто — убрал парсинг (исходим из того, что формат даты неизменный), сортировал строку. | ||
| Со вторым пришлось переписать split используя блок. Выглядит коряво, совсем не руби-стайл. Но зато эффективнее. | ||
|
|
||
| ## Результаты | ||
| В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
| Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* | ||
| Удалось улучшить метрику системы: | ||
|
|
||
| Время выполнение изначальное, с параметрами `[1, 10, 100, 1_000, 10_000, 100_000]`: | ||
|
|
||
| ```bash | ||
| bench_work 0.000915 0.001853 0.001687 0.040583 1.515984 315.730440 | ||
| ``` | ||
|
|
||
| Стало: | ||
| ```bash | ||
| bench_work 0.000581 0.000303 0.000716 0.007148 0.068577 0.648381 | ||
| ``` | ||
|
|
||
| Ускорение более чем в 487 раз! | ||
|
Owner
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. 👍 |
||
|
|
||
| *Какими ещё результами можете поделиться* | ||
| Изначальный расход памяти с кол-вом строк 20_000 был 470 Mb. | ||
| Стал: 7.1 Mb. | ||
|
|
||
| Итого уменьшение расхода памяти в 67 раз. | ||
|
|
||
| Весь файл целиком теперь выполняется за 29 секунд (ранее это занимало бесконечно много). | ||
| Память расходуется в районе ~2 Gb. (Если я правильно понял эту цифру в отчете профилировщика — 2 042 553) | ||
|
|
||
| Код все еще не самый красивый, есть что поDRYить, как разбить на классы, и где пописать больше тестов. Но вроде как урок | ||
| был не про это, поэтому можно пока пропустить и оставить как есть. | ||
|
Owner
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. 👍 |
||
|
|
||
| ## Защита от регресса производительности | ||
| Для защиты от потери достигнутого прогресса при дальнейших изменениях программы сделано *то, что вы для этого сделали* | ||
| Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавил тест на O(N) бенчамарк. | ||
|
|
||
| Как сделать тест на используемую память пока не нашел. | ||
|
|
||
|
|
||
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.
👍