Skip to content

Commit 8d85f42

Browse files
committed
Fix eleventh perfomance issues
1 parent 0e72eec commit 8d85f42

File tree

6 files changed

+45
-46
lines changed

6 files changed

+45
-46
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ gem 'stackprof'
1111
gem 'fasterer'
1212
gem 'rubocop-performance'
1313
gem 'memory_profiler'
14+
gem 'oj'

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ GEM
1515
jaro_winkler (1.5.3)
1616
memory_profiler (0.9.14)
1717
method_source (0.9.2)
18+
oj (3.8.1)
1819
parallel (1.17.0)
1920
parser (2.6.3.0)
2021
ast (~> 2.4.0)
@@ -66,6 +67,7 @@ PLATFORMS
6667
DEPENDENCIES
6768
fasterer
6869
memory_profiler
70+
oj
6971
pry-byebug
7072
rspec
7173
rspec-benchmark

case-study.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@ end
7474
Данные для `report['allBrowsers']` брались путём дополнительной итерация, удалил дополнительную итерацию, использовал данные из `uniqueBrowsers`, сократил время c 0.731 до 0.716
7575

7676
### Находка №9
77-
Перебрал схему, понял, что входные данные приходят в определённом порядке, можно на этом сыграть и проводить меньше итерация и довести до линейной зависимости. В результате сократил время работы с 0.716 до 0.140
77+
Перебрал схему, понял, что входные данные приходят в определённом порядке, можно на этом сыграть и проводить меньше итерация и довести до линейной зависимости. В результате сократил время работы с 0.716 до 0.136
7878

79+
### Находка №10
80+
Бесполезный Date.strftime, заменён на сhomp. Заменён each + push, на map. `to_json` -медленный, заменён на `oj`.В результате сокращено время работы с 0.136 до 0.064
7981

8082
## Результаты
8183
В результате проделанной оптимизации наконец удалось обработать файл с данными.
82-
Удалось улучшить метрику системы с бесконечно долго выполнения на ожидамые 30 сек и уложиться в заданный бюджет.
84+
Удалось улучшить метрику системы с бесконечно долго выполнения до 33 секунд
8385

8486
Было весело :)
8587

lib/task.rb

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,25 @@ def initialize(result_file_path: nil, data_file_path: nil, dasable_gc: true)
77

88
def parse_user(fields)
99
{
10-
'id' => fields[1],
11-
'first_name' => fields[2],
12-
'last_name' => fields[3],
13-
'age' => fields[4],
10+
id: fields[1],
11+
full_name: "#{fields[2]} #{fields[3]}"
1412
}
1513
end
1614

1715
def parse_session(fields)
1816
{
19-
'user_id' => fields[1],
20-
'session_id' => fields[2],
21-
'browser' => fields[3].upcase,
22-
'time' => fields[4],
23-
'date' => fields[5],
17+
user_id: fields[1],
18+
session_id: fields[2],
19+
browser: fields[3].upcase,
20+
time: fields[4].to_i,
21+
date: fields[5].chomp,
2422
}
2523
end
2624

2725
def collect_stats_from_user(report, user)
28-
user_key = "#{user.attributes['first_name']} #{user.attributes['last_name']}"
29-
report['usersStats'][user_key] ||= {}
30-
report['usersStats'][user_key] = report['usersStats'][user_key].merge(yield(user))
26+
user_key =user.attributes[:full_name]
27+
report[:usersStats][user_key] ||= {}
28+
report[:usersStats][user_key] = report[:usersStats][user_key].merge(yield(user))
3129
end
3230

3331
def work
@@ -44,7 +42,7 @@ def work
4442

4543
if cols[0] == 'session'
4644
session = parse_session(cols)
47-
uniqueBrowsers << session['browser']
45+
uniqueBrowsers << session[:browser]
4846
sessions << session
4947
@user.sessions << session
5048
end
@@ -55,18 +53,17 @@ def work
5553
report[:totalUsers] = user_objects.count
5654
progress_bar = ProgressBar.create(total: user_objects.count, format: '%a, %J, %E %B')
5755

58-
# Подсчёт количества уникальных браузеров
59-
report['uniqueBrowsersCount'] = uniqueBrowsers.count
60-
report['totalSessions'] = sessions.count
61-
report['allBrowsers'] = uniqueBrowsers.sort.join(',')
62-
report['usersStats'] = {}
56+
report[:uniqueBrowsersCount] = uniqueBrowsers.count
57+
report[:totalSessions] = sessions.count
58+
report[:allBrowsers] = uniqueBrowsers.sort.join(',')
59+
report[:usersStats] = {}
6360

6461
user_objects.each do |user_object|
6562
prepare_stats(report, user_object)
6663
progress_bar.increment
6764
end
6865

69-
File.write(result_file_path, "#{report.to_json}\n")
66+
File.write(result_file_path, "#{Oj.dump(report, mode: :compat)}\n")
7067
end
7168

7269
private
@@ -75,29 +72,18 @@ def work
7572

7673
def prepare_stats(report, user_object)
7774
collect_stats_from_user(report, user_object) do |user|
78-
user_times, user_browsers, user_dates = [], [], []
79-
80-
user.sessions.each do |session|
81-
user_times += [session['time'].to_i]
82-
user_browsers += [session['browser']]
83-
user_dates += [Date.strptime(session['date'], '%F').iso8601]
84-
end
75+
user_times = user.sessions.map { |session| session[:time] }
76+
user_browsers = user.sessions.map { |session| session[:browser] }
77+
user_dates = user.sessions.map { |session| session[:date] }
8578

8679
{
87-
# Собираем количество сессий по пользователям
88-
'sessionsCount' => user.sessions.count,
89-
# Собираем количество времени по пользователям
90-
'totalTime' => "#{user_times.sum} min.",
91-
# Выбираем самую длинную сессию пользователя
92-
'longestSession' => "#{user_times.max} min.",
93-
# Браузеры пользователя через запятую
94-
'browsers' => user_browsers.sort.join(', '),
95-
# Хоть раз использовал IE?
96-
'usedIE' => user_browsers.any? { |b| b.match? /INTERNET EXPLORER/ },
97-
# Всегда использовал только Chrome?
98-
'alwaysUsedChrome' => user_browsers.all? { |b| b.match? /CHROME/ },
99-
# Даты сессий через запятую в обратном порядке в формате iso8601
100-
'dates' => user_dates.sort.reverse
80+
sessionsCount: user.sessions.count,
81+
totalTime: "#{user_times.sum} min.",
82+
longestSession: "#{user_times.max} min.",
83+
browsers: user_browsers.sort.join(', '),
84+
usedIE: user_browsers.any? { |b| b.match? /INTERNET EXPLORER/ },
85+
alwaysUsedChrome: user_browsers.all? { |b| b.match? /CHROME/ },
86+
dates: user_dates.sort { |a, b| b <=> a }
10187
}
10288
end
10389
end

profilers/1_ruby_spy.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
require_relative '../config/environment'
1010

11-
Task.new(dasable_gc: false).work
11+
p Benchmark.measure { Task.new(dasable_gc: false).work }
12+
1213

1314
result_file_path = 'data/result.json'
1415
File.delete(result_file_path) if File.exist?(result_file_path)

spec/task_spec.rb

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,21 @@
2121
end
2222

2323
describe "performnce test" do
24+
before do
25+
allow(ProgressBar).to receive(:create).and_return(double(increment: true))
26+
end
2427
context "when 20k rows" do
2528
let(:data_file_path) { 'spec/fixtures/data_20k.txt' }
2629

2730
it 'executes faster than 0.2 seconds' do
28-
expect { task.work }.to perform_under(0.2).sec.warmup(2).times.sample(10).times
31+
expect { task.work }.to perform_under(0.1).sec.warmup(2).times.sample(10).times
2932
end
3033
end
3134

3235
context "when 18 rows" do
3336
it "executes at least 5_300 times in second" do
3437
allow(File).to receive(:write).and_return(true)
35-
expect { task.work }.to perform_at_least(5_300).within(1).warmup(0.2).ips
38+
expect { task.work }.to perform_at_least(6_000).within(1).warmup(0.2).ips
3639
end
3740
end
3841
end
@@ -86,5 +89,9 @@
8689
# 20_000 ~ 0.716
8790

8891
# После одинацатого исправления
89-
# 10_000 ~ 0.75
92+
# 10_000 ~ 0.075
9093
# 20_000 ~ 0.136
94+
95+
# После двенадцатого исправления
96+
# 10_000 ~ 0.034
97+
# 20_000 ~ 0.064

0 commit comments

Comments
 (0)