Skip to content

Commit 0a83625

Browse files
author
aleksey.ryabchikov
committed
last loop
1 parent 48d69d4 commit 0a83625

File tree

6 files changed

+60113
-31
lines changed

6 files changed

+60113
-31
lines changed

Gemfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
source 'https://rubygems.org'
4+
5+
gem 'ruby-prof'
6+
gem 'rspec-benchmark'
7+
gem 'ruby-progressbar'
8+
gem 'stackprof'
9+
gem 'pry'
10+
gem 'minitest'

Gemfile.lock

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
benchmark-malloc (0.2.0)
5+
benchmark-perf (0.6.0)
6+
benchmark-trend (0.4.0)
7+
coderay (1.1.3)
8+
diff-lcs (1.5.1)
9+
method_source (1.1.0)
10+
minitest (5.25.4)
11+
pry (0.14.2)
12+
coderay (~> 1.1)
13+
method_source (~> 1.0)
14+
rspec (3.13.0)
15+
rspec-core (~> 3.13.0)
16+
rspec-expectations (~> 3.13.0)
17+
rspec-mocks (~> 3.13.0)
18+
rspec-benchmark (0.6.0)
19+
benchmark-malloc (~> 0.2)
20+
benchmark-perf (~> 0.6)
21+
benchmark-trend (~> 0.4)
22+
rspec (>= 3.0)
23+
rspec-core (3.13.2)
24+
rspec-support (~> 3.13.0)
25+
rspec-expectations (3.13.3)
26+
diff-lcs (>= 1.2.0, < 2.0)
27+
rspec-support (~> 3.13.0)
28+
rspec-mocks (3.13.2)
29+
diff-lcs (>= 1.2.0, < 2.0)
30+
rspec-support (~> 3.13.0)
31+
rspec-support (3.13.2)
32+
ruby-prof (1.6.3)
33+
ruby-progressbar (1.13.0)
34+
stackprof (0.2.27)
35+
36+
PLATFORMS
37+
x86_64-linux
38+
39+
DEPENDENCIES
40+
minitest
41+
pry
42+
rspec-benchmark
43+
ruby-prof
44+
ruby-progressbar
45+
stackprof
46+
47+
BUNDLED WITH
48+
2.4.13

case-study.md

+40-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации.
1919

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

2323
Вот как я построил `feedback_loop`:
2424
Измерив время выполнения программы на разном объеме дынных, алгоритмическая сложность получилась O(n^2), т.е. время выполнения в зависимости от объема данных возрастает квадратично.
@@ -29,7 +29,7 @@
2929
- С помощью профилировщика найти главную точку роста (Профилируем с выключенным GC предварительно прогрев кеши)
3030
- Внести оптимизационные правки
3131
- С помощью профилировщика проверить есть ли улучшения
32-
- Запустить тест, проверить если улучшения есть, если да то закоммитить.
32+
- Запустить тест, проверить, если улучшения есть тогда закоммитить.
3333

3434
## Вникаем в детали системы, чтобы найти главные точки роста
3535
Для того, чтобы найти "точки роста" для оптимизации я воспользовался rbspy (удобно, потому что встроен в rubymine), stackprof и ruby-prof в разных режимах отчетов
@@ -46,6 +46,7 @@
4646
- Заменил перебор всех сессий на хэш с группированные данных по user_id. В данном конкретном месте алгоритмическая сложность с O(n) изменилась на O(1)
4747
- Метрика кратно уменьшилась при прогоне теста на перфоманс с средних 5 сек. до 0.4 сек. Это и было самым узким местом программы по всей видимости.
4848
- Повторный запуск профилировщика показал, что вместо 89,15% теперь это место занимает 0.3%.
49+
- В отчетах профилировщика эта точка роста перестала быть главной.
4950

5051
### Ваша находка №2
5152
- Профилировщики указали на следующую точку роста:
@@ -56,6 +57,7 @@
5657
- Заменил `sessions = sessions + [parse_session(line)] if cols[0] == 'session'` на `sessions = sessions << parse_session(line) if cols[0] == 'session'`
5758
Известная проблема в ruby. Оператор << позволяет не создавать новую переменную каждый раз, а писать все в существующую.
5859
- При прогоне теста, среднее значение метрики упало с 0.4 сек. до 0.25 сек.
60+
- В отчетах профилировщика эта точка роста перестала быть главной.
5961

6062
### Ваша находка №3
6163
- Ради интереса прогнал тесты с разным объемом данных, 20к, 40к, 80к и так далее. В том числе и на полном файле.
@@ -67,15 +69,48 @@
6769
```
6870
- Оптимизировал блок each, заменил '+' на Set.
6971
- При прогоне теста, среднее значение метрики упало с 0.25 сек. до 0.18 сек.
72+
- В отчетах профилировщика эта точка роста перестала быть главной.
7073

7174
### Ваша находка №4
72-
- Попробовал увеличить кол-во данных, чтобы проще было увидеть проблематику до 100_000 строк.
73-
- По отчетам нашел новую точку роста, это метод `collect_stats_from_users`, он аффектит на два проблемных места сразу
75+
- По отчетам профилировщиков нашел новую точку роста, это метод `def collect_stats_from_users`
7476
```
7577
%Total %Self Total Self Wait Child Calls Name
7678
89.28% 23.89% 1.43 0.38 0.00 1.05 10 Array#each
7779
```
78-
- Оптимизировал, а именно избавился от collect_stats_from_users и начал подготавливать данные за один проход.
80+
- Оптимизировал, а именно избавился от collect_stats_from_users и начал подготавливать данные для отчета за один проход.
7981
- При прогоне теста, среднее значение метрики упало с 0.18 сек. до 0.14 сек. на 20_000 строк.
82+
- В отчетах профилировщика эта точка роста перестала быть главной.
8083

84+
### Ваша находка №5
85+
- stackprof показал на точку роста на строке с парсингом даты `dates = sessions.map { |s| Date.parse(s['date']) }`
8186

87+
```
88+
%Total %Self Total Self Wait Child Calls Name
89+
28.57% 16.02% 0.09 0.05 0.00 0.04 16954 <Class::Date>#parse
90+
```
91+
- Поправил, Date.parse лишняя обработка.
92+
- Среднее значение метрики упало с 0.14 до 0.2 сек.
93+
- В отчетах профилировщика эта точка роста перестала быть главной.
94+
95+
### Ваша находка №6
96+
- Уже правктически выполняется бюджет прогона файла data_large.txt, но профилировщик подсветил еще одну точку роста:
97+
```
98+
%Total %Self Total Self Wait Child Calls Name
99+
30.73% 30.73% 0.06 0.06 0.00 0.00 40001 String#split
100+
```
101+
- Выпилил лишние String.split
102+
- Так как выполнение теста на 20_000 строк очень мало, увеличил файл до 60_000 строк.
103+
Среднее значение прогода 60_000 строк = 0.3 сек.
104+
- Среднее значение прогона всего файла с ~30 сек. упало до 27 сек.
105+
- В отчетах профилировщика эта точка роста перестала быть главной.
106+
- Вижу еще несколько точек роста, но решил на этом остановится =)
107+
108+
## Результаты
109+
В результате проделанной оптимизации наконец удалось обработать файл с данными.
110+
Удалось улучшить метрику системы на 20_000 строк с 5 сек. до 0.1 сек., на 60_000 строк с 72 сек. до 0.3 сек,
111+
на полном объеме данных уменьшить время выполнения до 27 сек. и уложиться в заданный бюджет.
112+
Так же был переписан тест с minitest на rspec и вынесен в отдельный файл.
113+
114+
## Защита от регрессии производительности
115+
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы было написано два performance теста, один для проверки на 60_000 строках,
116+
второй на полном объеме файла data_large.txt

0 commit comments

Comments
 (0)