Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
data*
result.json
tmp/
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
source 'https://rubygems.org'

#ruby '2.6.2'

gem 'ruby-prof'
gem 'oj'
15 changes: 15 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
oj (3.7.11)
ruby-prof (0.17.0)

PLATFORMS
ruby

DEPENDENCIES
oj
ruby-prof

BUNDLED WITH
1.17.2
112 changes: 82 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,82 @@
### Note
*Для работы скрипта требуется Ruby 2.4+*

# Задание №2

## Описание
В этом задании надо дооптимизировать задание 1й недели с использованием инструментов профилирования `CPU` и разъяснений, сделанных на 2й лекции.

### Что нужно сделать
- Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`;
- Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
- Профилировать работающий процесс `rbspy`;
- Построить и проанализировать отчёт `flamegraph` с помощью `rbspy`;
- Добавить в программу `ProgressBar`;
- Научиться пользоваться `Valgrind massif` с `massif-visualizer`. Построить профиль использования памяти для итоговой версии вашей программы и добавить скриншот в `PR`;
- Написать хотя бы самый простой тест на производительность: `assert`, что время выполнения скрипта меньше значения, которое чуть больше реального, чтобы не давать ложных срабатываний;

### Главное
Нужно потренироваться методично работать по схеме с фидбек-лупом:
- построили отчёт каким-то из профилировщиков
- осознали его
- поняли, какая самая большая точка роста
- внесли минимальные изменения, чтобы использовать только эту точку роста
- перестроили отчёт, убедились, что проблема решена
- вычислили метрику - оценили, как изменение повлияло на метрику
- записали полученные результаты
- закоммитились
- перешли к следующей итерации
# Задание №1
В файле `task-1.rb` находится ruby-программа, которая выполняет обработку данных из файла.

В файл встроен тест, который показывает, как программа должна работать.

С помощью этой программы нужно обработать файл данных `data_large.txt`.

**Проблема в том, что это происходит слишком долго, дождаться пока никому не удавалось.**


## Задача
- Оптимизировать эту программу, выстроив процесс согласно "общему фреймворку оптимизации" из первой лекции;
- Профилировать программу с помощью инструментов, с которыми мы познакомились в первой лекции;
- Добиться того, чтобы программа корректно обработала файл `data_large.txt`;
- Написать кейс-стади о вашей оптимизации по шаблону `case-study-template.md`.

## Сдача задания
Для сдачи задания нужно сделать `PR` в этот репозиторий.

В `PR`
- должны быть внесены оптимизации в `task-1.rb`;
- должен быть файл `case-study.md` с описанием проделанной оптимизации;


# Комментарии

## Какую пользу нужно получить от этого задания
Задание моделирует такую ситуацию: вы получили неффективную систему, в которой код и производительность оставляет желать лучшего. При этом актуальной проблемой является именно производительность.
Вам нужно оптимизировать эту систему.

С какими искушениями вы сталкиваететь:
- вы “с ходу” видите проблемы в коде и у вас возникает импульс потратить время на их рефакторинг;
- вы “с ходу” замечаете какие-то непроизводительные идиомы, и у вас возникает соблазн их сразу исправить;

Эти искушения типичны и часто возникают в моделируемой ситуации.

Их риски:
- перед рефакторингом “очевидных” косяков не написать тестов и незаметно внести регрессию;
- потратить время на рефакторинг, хотя время было только на оптимизацию;
- исправить все очевидные на глаз проблемы производительности, не получить заметного результата, решить что наверное просто Ruby слишком медленный для этой задачи

## Советы
- Найдите объём данных, на которых программа отрабатывает достаточно быстро - это позволит вам выстроить фидбек-луп; если улучшите метрику для части данных, то улучшите и для полного объёма данных;
- Попробуйте прикинуть ассимтотику роста времени работы в зависимости от объёма входных данных (попробуйте объём x, 2x, 4x, 8x)
- Оцените, как долго программа будет обрабатывать полный обём данных
- Оцените, сколько времени занимает работа GC

Возможно, что оптимизаций только памяти вам не хватит, чтобы довести производительность системы до приемлемой.

Если вы придёте к такому выводу, то возможны такие варианты:
- попробуйте использовать какой-нибудь из рассмотренных нами профилировщиков в режиме профилирования CPU (wall), найти точки роста и оптимизировать их;
- подойдёт и вариант, если вы с помощью профилирования найдёте несколько главных точек роста, оптимизируете их, покажете, что дальнейшие оптимизации только памяти не приведут к желаемому результату, и опишете это в case-study.

## Что можно делать
- рефакторить код
- рефакторить/дописывать тесты
- разбивать скрипт на несколько файлов

## Что нужно делать
- исследовать предложенную вам на рассмотрение систему
- построить фидбек-луп, который позволит вам быстро тестировать гипотезы и измерять их эффект
- применить инструменты профилирования памяти, интроспекции GC, чтобы найти самые горячие проблемы по памяти, оценить кол-во времени которое уходит на сборку мусора
- выписывать в case-study несколько пунктов: каким профилировщиком вы нашли точку роста, как её оптимизировали, какой получили прирост метрики;

## Что не нужно делать
- переписывать с нуля
- забивать на выстраивание фидбек-лупа
- вносить оптимизации по наитию, без профилировщика и без оценки эффективности

## Основная польза задания
Главная польза этого задания - попрактиковаться в применении грамотного подхода к оптимизации, почуствовать этот процесс:
- как взяли незнакомую систему и исследовали её
- как выстроили фидбек луп
- как с помощью профилировщиков нашли что именно даст вам наибольший эффект
- как быстро протестировали гипотезу, получили измеримый результат и зафиксировали его
- как в итоге написали небольшой отчёт об успешных шагах этого процесса

## PS
Обсудим с Виталием, возможно уделю время на 2й лекции разбору подобной ситуации, а заданием 2й недели будет дооптимизировать эту программу, уже используя инструментарий оптимизации CPU.

## PPS
Делайте задание, изучайте предложенную систему, смотрите на неё под разными углами с помощью разных инстурментов. Оптимизируйте. Попрактикуйтесь в этой работе
61 changes: 45 additions & 16 deletions case-study-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,69 @@

У нас уже была программа на `ruby`, которая умела делать нужную обработку.

Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.

Я решил исправить эту проблему, оптимизировав эту программу.
Она успешно справлялась с большими файлами за 91-92 секунды, но мы решили ее дооптимизировать.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


## Формирование метрики
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика*
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: замер времени работы скрипта и количевсто используемой памяти.

## Гарантия корректности работы оптимизированной программы
Программа поставлялась с тестом. Выполнение этого теста позволяет не допустить изменения логики программы при оптимизации.

## Feedback-Loop
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось*
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за 7 секунд обработки файла.

Вот как я построил `feedback_loop`: *как вы построили feedback_loop*
Вот как я построил `feedback_loop`:
1. Создал shell скрипт с тремя запусками для 50k, 100k и 200k строками из data_large файла.
2. Добавил профилеровщик памяти
3. Вносил изменения в исходный код
4. Проверял скорость работы обработки файла
5. Если скорость не менялась, переходил к шагу 3. Если ускорялось, к шагу 6.
6. Переходим к следующей точке роста.

## Вникаем в детали системы, чтобы найти 20% точек роста
Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались*
Для того, чтобы найти "точки роста" для оптимизации я воспользовался ruby-prof.

Вот какие проблемы удалось найти и решить

### Ваша находка №1
О вашей находке №1
Используя Callstack отчет, обнаружил, что код из задания 1 имеет точку роста(12% всего времени) в методе include? для сбора уникальных браузеров.

После рефакторинга, точка роста упала до менее 2% от всего времени.
Время работы всего срипта упало до 86 секунд.
Количество выделяемой памяти практически не поменялось.

### Ваша находка №2
О вашей находке №2
Следующая точка роста была в Hash#to_json(около 14%) с Callstack отчетом:
14.02% (14.02%) JSON::Ext::Generator::GeneratorMethods::Hash#to_json [1 calls, 1 total]

### Ваша находка №X
О вашей находке №X
Использование гема oj помогло ужать генерацию json до ~3%:
2.62% (2.62%) <Module::Oj>#dump [1 calls, 1 total]
Время работы всего срипта упало до 76-78 секунд.

## Результаты
В результате проделанной оптимизации наконец удалось обработать файл с данными.
Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце*
### Ваша находка №3
Собрал flamegraph из rbspy. Проанализировал и понял, что callstask отчет от ruby-prof показывает тоже самое.
Но информации чуть меньше от rbspy и ruby 2.6.x не поддерживается в rbspy.

### Ваша находка №4
Около 47% всего времени занимал сбор статистики в TaskClass#collect_stats_from_users.
Повторяющиеся или похожие map методы были вынесены в общие методы, что помогло сократить все время с 47% до 33%.
Время работы всего срипта упало до 60-65 секунд.

### Ваша находка №5
При переходе на другую систему с 8 ядрами с 16 RAM. Скорость обработки файла упала сама собой до 45 секунд...
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хех

Возможно ноутбук с 4 ядрами и 8 RAM был подгружен другими процессами...

*Какими ещё результами можете поделиться*
### Ваша находка №6
Upcase вызывался достаточно часто в TaskClass#parse_session:
3.87% (29.93%) String#upcase

Было решено зарефакторить этот метод. После рефакторинга скорость оботки файла упала до 39 секунд.

### Ваша находка №7
Самую жирную точку роста в split(',') не удалось решить. split занимал 23% всего времени на конечном файле после всех изменений перечисленных выше.

## Результаты
В результате проделанной оптимизации удалось сократить время обработки файла с ~90 секунд до ~39 секунд.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


## Защита от регресса производительности
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы сделано *то, что вы для этого сделали*
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы изменен старый юнит тест, чтобы не использовать весь файл с данными, а только маленький файл для ускорения прогона тестов. Но с погрешностью в еще 20% минуты с учетом загруженность или других факторов на системы, которые могут повлиять на скорость обработки данных.
6 changes: 6 additions & 0 deletions collect_stats.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
echo '--- 50k records ---'
ruby task-2.rb data_large_50k.txt
echo '--- 100k records ---'
ruby task-2.rb data_large_100k.txt
echo '--- 200k records ---'
ruby task-2.rb data_large_200k.txt
2 changes: 1 addition & 1 deletion result.json

Large diffs are not rendered by default.

Loading