You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Необходимо было обработать файл с данными, чуть больше ста мегабайт.
7
+
8
+
У нас уже была программа на `ruby`, которая умела делать нужную обработку.
9
+
10
+
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.
11
+
12
+
Я решил исправить эту проблему, оптимизировав эту программу.
13
+
14
+
## Формирование метрики
15
+
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *время выполнения в секундах*
16
+
17
+
## Гарантия корректности работы оптимизированной программы
18
+
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации.
19
+
20
+
## Feedback-Loop
21
+
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *29 секунд*
22
+
23
+
Вот как я построил `feedback_loop`:
24
+
1) Использовал профилировщик `RubyProf Flat` с выключенным GC (по началу на объеме в 50к строк)
25
+
2) Находил главную точку роста и начинал править ровно с этого места
26
+
3) Перезапускал профилировщик повторно и смотрел на полученный результат + непосредственно сам скрипт с включенным GC
27
+
4) Увеличивал объем данных и повторно начинал с пункта 1)
28
+
29
+
## Вникаем в детали системы, чтобы найти главные точки роста
30
+
Для того, чтобы найти "точки роста" для оптимизации я воспользовался `RubyProf Flat`
- На объеме данных в 50к строк скрипт работал +- 2 секунды
44
+
-`Array#select` упал с 80% до 0%
45
+
46
+
### Ваша находка №2
47
+
```
48
+
file_lines.each do |line|
49
+
cols = line.split(',')
50
+
users = users + [parse_user(line)] if cols[0] == 'user'
51
+
sessions = sessions + [parse_session(line)] if cols[0] == 'session'
52
+
end
53
+
```
54
+
- На полном объёме данных выжирает всю оперативу, процесс убивается системой (видно в syslog). Переписал всю логику на использование массива объектов классов User и Session вместо хранения в виде массива строк
55
+
- Программа перестала убиваться системой
56
+
57
+
### Ваша находка №3
58
+
-`RubyProf Flat` показывает большой % использования `Array#each`
59
+
- Видно, что логика для построения статистики по каждому пользователю написана криво с использованием огромного кол-ва переборов. Нет смысла каждый раз итерироваться по всему массиву при построении отдельной статистики (т.е нет смысла начинать итерацию с нуля при построении sessionsCount, totalTime, longestSession, browsers, usedIE, alwaysUsedChrome и dates - можно строить все нужные статистики на текущей итерации)
60
+
- После рефакторинга `Array#each` упал до 13.36%
61
+
62
+
### Ваша находка №4
63
+
-`RubyProf Flat` показывает большой % использования `Array#map`
64
+
- Некоторые метрики по пользователям (totalTime + longestSession, browsers + usedIE + alwaysUsedChrome) используют одинаковые куски кода с map. Можно использовать мемоизацию. Также заметно бросается в глаза двойной вызов `map` - в некоторых местах достаточно его вызывать лишь один раз
65
+
- После рефакторинга `Array#map` до 10.18% соответственно
66
+
67
+
### Ваша находка №5
68
+
-`RubyProf Flat` показывает большой % использования `Date#parse`
69
+
- Обратив внимание на структуру данных, видно, что конструкции типа `Date#parse` можно опустить вообще - использование `.sort.reverse' более чем достаточно
70
+
- После рефакторинга `Date#parse` упал до 0% соответственно
71
+
72
+
## Результаты
73
+
В результате проделанной оптимизации наконец удалось обработать файл с данными.
74
+
Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет.
75
+
76
+
## Защита от регрессии производительности
77
+
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы написал скрипт с использованием `benchmark/ips` для замера производительности. Итоговый результат:
0 commit comments