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
Binary file added .DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
data_large.txt
data.txt
result.json
# spec/fixtures/**/*
profiling/ruby_prof_reports/**/*
profiling/stackprof_reports/**/*
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0.6
55 changes: 0 additions & 55 deletions case-study-template.md

This file was deleted.

134 changes: 134 additions & 0 deletions case-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Case-study оптимизации

## Актуальная проблема
В нашем проекте возникла серьёзная проблема.

Необходимо было обработать файл с данными, чуть больше ста мегабайт.

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

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

Я решил исправить эту проблему, оптимизировав эту программу.

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

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

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

Вот как я построил `feedback_loop`:
- замеряю метрику;
- вношу изменение;
- убеждаюсь, что тест на корректность проходит;
- если при этом метрика улучшилась, делаю коммит.

## Вникаем в детали системы, чтобы найти главные точки роста
Для того, чтобы найти "точки роста" для оптимизации я воспользовался:
- гем memory_profiler
- отчёт ruby-prof в режиме Flat;
- отчёт ruby-prof в режиме Graph;
- отчёт ruby-prof в режиме CallStack;
- отчёт ruby-prof в режиме CallTree c визуализацией в QCachegrind;
- текстовый отчёт stackprof;
- отчёт flamegraph с помощью stackprof и визуализировать его в speedscope.app;
- valgrind massif visualier;


### Ваша находка №1

Кол-во потребляемой памяти пугающе велико. Не сразу понял, что без `GC.start` не обойтись.

После каждой строки добавил конструкцию вида:
```
if force_gc && memory_usage > 50
GC.start(full_mark: true, immediate_sweep: true)

end
```
На время оптимизации отключаем.

### Ваша находка №2
- какой отчёт показал главную точку роста: memory_profiler

Нам нужно смотреть на строчки.

allocated memory by class
-----------------------------------
191.42 MB String
85.94 MB Array
40.62 MB Hash

- как вы решили её оптимизировать

Есть проблема, что: наши ключи хэшей строковые - их нужно преобразовать в `Symbol`, что они не дублировались в памяти + вынес запятую в константы что мог и зафризил.

- как изменилась метрика

1066 MB -> 750 MB

Хорошее начало!

- как изменился отчёт профилировщика

Ушли из отчета / стало меньше.

### Ваша находка №3
- какой отчёт показал главную точку роста memory_profiler
Большое кол-во повторений браузеров.
- как вы решили её оптимизировать
Вынесем их в класс.
- как изменилась метрика

теперь я смотрю 10к с GC (ограничение в 40MB, при достижении увеличиваем в 1.1)
59 MB -> 50 MB

- как изменился отчёт профилировщика

повторения браузеров ушла, нагрузка на `String` сильно снизилась:

### Ваша находка №4
- какой отчёт показал главную точку роста memory_profiler

В отчете продолжают болтаться странные строковые значения из старых прогонов. Похоже, я выбрал плохую стратегию запуска `GC` - и это `leak`.

- как вы решили её оптимизировать

Я стану запускать `GC` после прогона пачки сессий юзера.

- как изменилась метрика

50 MB -> 34 MB

- как изменился отчёт профилировщика

"Застрявшие" значения строк ушли из отчета.

### Ваша находка №5
- какой отчёт показал главную точку роста
По-моему, отчеты перестали выдавать полезную информацию для определения точек роста.
https://gist.github.com/peresvetjke/f21be71525364f90a63ba61bdf73b27d
То есть у меня нет возможности понять, что именно плохо - я могу тольо понять, насколько плохо - с помощью massif либо других отчетов о кол-ве памяти в отчетах.

- как вы решили её оптимизировать

- как изменилась метрика
- как изменился отчёт профилировщика

### Ваша находка №X
- какой отчёт показал главную точку роста
- как вы решили её оптимизировать
- как изменилась метрика
- как изменился отчёт профилировщика

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

*Какими ещё результами можете поделиться*

## Защита от регрессии производительности
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали*
41 changes: 41 additions & 0 deletions graphviz.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
digraph profile {
Legend [shape=box,fontsize=24,shape=plaintext,label="
Total samples: 61\lShowing top 120 nodes\lDropped nodes with < 1 samples\l"];
"140555456958560" [size=2.5] [fontsize=10.0] [penwidth="2.5"] [shape=box] [label="StackProf.run\n0 (0.0%)\rof 61 (100.0%)\r"];
"140555388960040" [size=2.5] [fontsize=10.0] [penwidth="2.5"] [shape=box] [label="<main>\n0 (0.0%)\rof 61 (100.0%)\r"];
"140555472558160" [size=2.5] [fontsize=10.0] [penwidth="2.5"] [shape=box] [label="<main>\n0 (0.0%)\rof 61 (100.0%)\r"];
"140555472557800" [size=2.5] [fontsize=11.217391304347826] [penwidth="2.5"] [shape=box] [label="block in <main>\n1 (1.6%)\rof 61 (100.0%)\r"];
"140555464000240" [size=2.4672131147540983] [fontsize=38.0] [penwidth="2.4672131147540983"] [shape=box] [label="Object#work\n23 (37.7%)\rof 60 (98.4%)\r"];
"140555456955840" [size=0.959016393442623] [fontsize=27.043478260869566] [penwidth="0.959016393442623"] [shape=box] [label="Kernel#`\n14 (23.0%)\r"];
"140555389090160" [size=0.6639344262295082] [fontsize=16.08695652173913] [penwidth="0.6639344262295082"] [shape=box] [label="IO.write\n5 (8.2%)\r"];
"140555389090280" [size=0.6311475409836066] [fontsize=14.869565217391305] [penwidth="0.6311475409836066"] [shape=box] [label="IO.read\n4 (6.6%)\r"];
"140555456935800" [size=0.5983606557377049] [fontsize=11.217391304347826] [penwidth="0.5983606557377049"] [shape=box] [label="Kernel#puts\n1 (1.6%)\rof 3 (4.9%)\r"];
"140555389147760" [size=0.5655737704918032] [fontsize=12.434782608695652] [penwidth="0.5655737704918032"] [shape=box] [label="Array#map\n2 (3.3%)\r"];
"140555389089360" [size=0.5655737704918032] [fontsize=11.217391304347826] [penwidth="0.5655737704918032"] [shape=box] [label="IO#puts\n1 (1.6%)\rof 2 (3.3%)\r"];
"140555389248920" [size=0.5655737704918032] [fontsize=12.434782608695652] [penwidth="0.5655737704918032"] [shape=box] [label="String#%\n2 (3.3%)\r"];
"140555456937320" [size=0.5655737704918032] [fontsize=12.434782608695652] [penwidth="0.5655737704918032"] [shape=box] [label="JSON::Ext::Generator::GeneratorMethods::Hash#to_json\n2 (3.3%)\r"];
"140555389088000" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="IO#write\n1 (1.6%)\r"];
"140555389245720" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="String#split\n1 (1.6%)\r"];
"140555389148080" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="Array#sort\n1 (1.6%)\r"];
"140555389146000" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="Array#uniq\n1 (1.6%)\r"];
"140555389148360" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="Array#join\n1 (1.6%)\r"];
"140555389207440" [size=0.5327868852459017] [fontsize=11.217391304347826] [penwidth="0.5327868852459017"] [shape=box] [label="Integer#to_s\n1 (1.6%)\r"];
"140555456958560" -> "140555472557800" [label="61"] [weight="61"] [penwidth="2.5"];
"140555388960040" -> "140555472558160" [label="61"] [weight="61"] [penwidth="2.5"];
"140555472558160" -> "140555456958560" [label="61"] [weight="61"] [penwidth="2.5"];
"140555472557800" -> "140555464000240" [label="60"] [weight="60"] [penwidth="2.4672131147540983"];
"140555464000240" -> "140555389090280" [label="4"] [weight="4"] [penwidth="0.6311475409836066"];
"140555464000240" -> "140555389245720" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
"140555464000240" -> "140555389147760" [label="2"] [weight="2"] [penwidth="0.5655737704918032"];
"140555464000240" -> "140555389148080" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
"140555464000240" -> "140555389146000" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
"140555464000240" -> "140555389148360" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
"140555464000240" -> "140555456937320" [label="2"] [weight="2"] [penwidth="0.5655737704918032"];
"140555464000240" -> "140555389090160" [label="5"] [weight="5"] [penwidth="0.6639344262295082"];
"140555464000240" -> "140555389207440" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
"140555464000240" -> "140555456955840" [label="14"] [weight="14"] [penwidth="0.959016393442623"];
"140555464000240" -> "140555389248920" [label="2"] [weight="2"] [penwidth="0.5655737704918032"];
"140555464000240" -> "140555456935800" [label="3"] [weight="3"] [penwidth="0.5983606557377049"];
"140555456935800" -> "140555389089360" [label="2"] [weight="2"] [penwidth="0.5655737704918032"];
"140555389089360" -> "140555389088000" [label="1"] [weight="1"] [penwidth="0.5327868852459017"];
}
Binary file added graphviz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading