Skip to content

Commit 6f67219

Browse files
committed
chore: add 3 more steps
1 parent cdd62a0 commit 6f67219

File tree

5 files changed

+38
-31
lines changed

5 files changed

+38
-31
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ head -n N data_large.txt > dataN.txt # create smaller file from larger (take N f
9898
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`;
9999
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`;
100100
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`;
101-
- [ ] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
101+
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
102102
- [x] Построить дамп `stackprof` и проанализировать его с помощью `CLI`
103103
- [x] Построить дамп `stackprof` в `json` и проанализировать его с помощью `speedscope.app`
104104
- [x] Профилировать работающий процесс `rbspy`;

case-study.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,25 +68,37 @@
6868

6969
### Находка №5
7070
- `ruby-prof` в режиме `Graph` показывает, что точкой роста является `27.07% 16.32% 3.99 2.41 0.00 1.58 846230 <Class::Date>#parse`, это строка `user.sessions.map{|s| s['date']}.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 }`
71-
- вместо `Date.parse(d)` используем `Date.strptime(d, '%Y-%m-%d')` (заранее известен формат). Даты часто повторяются, используем мемоизацию для уже распаршенных дат.
72-
- время выполнения программы для 1кк входных данных сократилось с 10с до с 7.8c
71+
- поскольку строки уже находятся в формате iso8601, это позволяет использовать строки для сортировки не преобразуя их в даты: убираем парсинг дат, с последующим преобразованием в iso8601.
72+
- время выполнения программы для 1кк входных данных сократилось с 10с до с 7.5c
7373
- исправленная проблема перестала быть главной точкой роста.
7474

7575
### Находка №6
7676
- `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `7.20% (15.82%) Array#include? [846230 calls, 846230 total]`
7777
- вместо формирования уникальных браузеров через each, сделаем `uniqueBrowsers = sessions.map { |session| session['browser'] }.uniq`.
78-
- время выполнения программы для 1кк входных данных сократилось с 7.8 до с 7.3c
78+
- время выполнения программы для 1кк входных данных сократилось с 7.5 до с 6.9c
7979
- исправленная проблема перестала быть главной точкой роста.
8080

8181
### Находка №7
82-
- `ruby-prof` в режиме `CallStack` показывает, что точкой роста является `2.22% (14.64%) String#upcase [846230 calls, 2331849 total]` в контексте `45.05% (45.05%) Object#collect_stats_from_users`
83-
- вместо фомирования `upcase` версий браузеров трижды для каждого юзера, сделаем это единожды в начале итерации: `upcased_browsers = user.sessions.map{|s| s['browser'].upcase }` и далее будем переиспользовать этот результат.
84-
- время выполнения программы для 1кк входных данных сократилось с 7.3 до с 6.9c
82+
- `ruby-prof` в режиме `CallGrind` показывает, что точкой роста является `Object::collect_stats_from_users`-> `Array::map`->`String::upcase`
83+
- поскольку используется только `upcase` версия браузера, при парсинге сессия сразу записываем `upcase` версию. Поскольку не так много видов браузеров относительно общего количества сессий, используем мемоизацию.
84+
- время выполнения программы для 1кк входных данных сократилось с 6.9 до с 6.4c
85+
- исправленная проблема перестала быть главной точкой роста.
86+
87+
### Находка №8
88+
- `ruby-prof` в режиме `CallGrind` показывает, что точкой роста является `Array::each`->`Array::each`->`Object::parse_session`->`String::split`
89+
- делаем `split` только единожды для каждой строчки, в `parse_user`, `parse_session` передаем уже массив, а не строку
90+
- время выполнения программы для 1кк входных данных сократилось с 6.4 до с 5.4c
91+
- исправленная проблема перестала быть главной точкой роста.
92+
93+
### Находка №9
94+
- `ruby-prof` в режиме `CallGrind` показывает, что точкой роста является `Object::collect_stats_from_users`->`Array::each`->`Array::map`->`String::to_i`
95+
- поскольку используется только целочисленное значение `time`, делаем преобразование `to_i` один раз в `parse_session`, а не дважды в `collect_stats_from_users`.
96+
- время выполнения программы для 1кк входных данных сократилось с 5.4 до с 5c
8597
- исправленная проблема перестала быть главной точкой роста.
8698

8799
## Результаты
88100
В результате проделанной оптимизации наконец удалось обработать файл с данными.
89-
Удалось улучшить метрику системы с 4.7 дней до 13 секунд и уложиться в заданный бюджет.
101+
Удалось улучшить метрику системы с 4.7 дней до 24 секунд и уложиться в заданный бюджет.
90102

91103
## Защита от регрессии производительности
92104
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы добавил два теста: прогон на полных данных до 15 секунд, проверка на линейную асимптотику

task-1_spec.rb renamed to spec/task-1_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
require 'rspec'
44
require 'rspec-benchmark'
5-
require_relative 'task-1'
5+
require_relative '../task-1'
66

77
RSpec.configure do |config|
88
config.include RSpec::Benchmark::Matchers

task-1.rb

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ def initialize(attributes:, sessions:)
1313
end
1414
end
1515

16-
def parse_user(user)
17-
fields = user.split(',')
16+
def parse_user(fields)
1817
parsed_result = {
1918
'id' => fields[1],
2019
'first_name' => fields[2],
@@ -23,13 +22,12 @@ def parse_user(user)
2322
}
2423
end
2524

26-
def parse_session(session)
27-
fields = session.split(',')
25+
def parse_session(fields)
2826
parsed_result = {
2927
'user_id' => fields[1],
3028
'session_id' => fields[2],
3129
'browser' => fields[3],
32-
'time' => fields[4],
30+
'time' => fields[4].to_i,
3331
'date' => fields[5],
3432
}
3533
end
@@ -47,11 +45,16 @@ def work
4745

4846
users = []
4947
sessions = []
48+
upcased_browser ||= {}
5049

5150
file_lines.each do |line|
5251
cols = line.split(',')
53-
users << parse_user(line) if cols[0] == 'user'
54-
sessions << parse_session(line) if cols[0] == 'session'
52+
users << parse_user(cols) if cols[0] == 'user'
53+
54+
if cols[0] == 'session'
55+
cols[3] = upcased_browser[cols[3]] || (upcased_browser[cols[3]] = cols[3].upcase)
56+
sessions << parse_session(cols)
57+
end
5558
end
5659

5760
# Отчёт в json
@@ -83,7 +86,6 @@ def work
8386
report['allBrowsers'] =
8487
sessions
8588
.map { |s| s['browser'] }
86-
.map { |b| b.upcase }
8789
.sort
8890
.uniq
8991
.join(',')
@@ -102,30 +104,23 @@ def work
102104
report['usersStats'] = {}
103105

104106
collect_stats_from_users(report, users_objects) do |user|
105-
upcased_browsers = user.sessions.map{|s| s['browser'].upcase }
106-
107107
{
108108
# Собираем количество сессий по пользователям
109109
'sessionsCount' => user.sessions.count,
110110
# Собираем количество времени по пользователям
111-
'totalTime' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.sum.to_s + ' min.',
111+
'totalTime' => user.sessions.map {|s| s['time']}.sum.to_s + ' min.',
112112
# Выбираем самую длинную сессию пользователя
113-
'longestSession' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.max.to_s + ' min.',
113+
'longestSession' => user.sessions.map {|s| s['time']}.max.to_s + ' min.',
114114
# Браузеры пользователя через запятую
115-
'browsers' => upcased_browsers.sort.join(', '),
115+
'browsers' => user.sessions.map {|s| s['browser']}.sort.join(', '),
116116
# Хоть раз использовал IE?
117-
'usedIE' => upcased_browsers.any? { |b| b =~ /INTERNET EXPLORER/ },
117+
'usedIE' => user.sessions.map{|s| s['browser']}.any? { |b| b =~ /INTERNET EXPLORER/ },
118118
# Всегда использовал только Chrome?
119-
'alwaysUsedChrome' => upcased_browsers.all? { |b| b =~ /CHROME/ },
119+
'alwaysUsedChrome' => user.sessions.map{|s| s['browser']}.all? { |b| b =~ /CHROME/ },
120120
# Даты сессий через запятую в обратном порядке в формате iso8601
121-
'dates' => user.sessions.map{|s| s['date']}.map {|d| parse_date(d)}.sort.reverse.map { |d| d.iso8601 },
121+
'dates' => user.sessions.map{|s| s['date']}.sort { |d1, d2| d2 <=> d1 },
122122
}
123123
end
124124

125125
File.write('result.json', "#{report.to_json}\n")
126126
end
127-
128-
def parse_date(date)
129-
@dates ||= {}
130-
@dates[date] || (@dates[date] = Date.strptime(date, '%Y-%m-%d'))
131-
end

task-1_test.rb renamed to test/task-1_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require_relative 'task-1'
1+
require_relative '../task-1'
22
require 'minitest/autorun'
33

44

0 commit comments

Comments
 (0)