Skip to content

Commit 1baf3d6

Browse files
author
aleksey.ryabchikov
committed
last_loop
1 parent 438799d commit 1baf3d6

7 files changed

Lines changed: 125 additions & 128 deletions

File tree

case-study.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
Я решил исправить эту проблему, оптимизировав эту программу.
1313

1414
## Формирование метрики
15-
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: программа не должна потреблять больше нескольких мегабайт памяти при обработке файлов `data_2000.txt` затем `data_4000.txt`.
16-
(Учел ошибку первой домашней работы и сделал объем данных больше, для того чтобы проблемы были более очевидны)
15+
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: программа не должна потреблять больше **70Мб** памяти при обработке файла `data_large.txt` в течение всей своей работы.
16+
(Учел ошибку первой домашней работы и сделал объем данных больше, для того чтобы проблемы были более очевидны на частичных файлах от основного)
1717
Первичные измерения:
1818
1. `data_20000.txt`
1919
```
@@ -234,11 +234,48 @@ allocated memory by file
234234
```
235235
- Главная точка роста исправлена со 59 мб удалось получить 58 мб. Коммит и переход к следующей.
236236

237+
### Ваша находка №5
238+
- Использую data_40000.txt, профайлеры показывают, что точки роста есть, но они незначительные. Скорее всего их исправление не поможет уложиться в 70 мБ для полного файла.
239+
К тому же на данном этапе мы сохраняем весь файл целиком в файл, только на чтение `data_large.txt` и сохранение в `result.json` потребуется значительно больше памяти.
240+
Поэтому дальше попробую воспользоваться подсказкой из readme перепишу в "потоковом" стиле, то-есть чтение/запись по строкам. Это должно кратно сократить потребление памяти на больших объемах.
241+
242+
- Проблема в считывании всего файла и подготовки отчета, целиком.
243+
```
244+
```
245+
- как изменилась метрика
246+
```
247+
GC disabled MEMORY USAGE: 86 MB
248+
GС enabled MEMORY USAGE: 36 MB
249+
```
250+
- как изменился отчёт профилировщика.
251+
```
252+
MEMORY USAGE: 199 MB
253+
allocated memory by file
254+
-----------------------------------
255+
75.94 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb
256+
15.42 kB /home/alex/.asdf/installs/ruby/2.7.8/lib/ruby/2.7.0/set.rb
257+
40.00 B memory-profiler.rb
258+
259+
allocated memory by location
260+
-----------------------------------
261+
30.05 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:7
262+
12.89 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:9
263+
6.57 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:43
264+
4.74 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:58
265+
4.02 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:59
266+
3.10 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:64
267+
2.40 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:8
268+
2.11 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:39
269+
2.03 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:71
270+
1.79 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:42
271+
1.55 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:22
272+
1.40 MB /home/alex/documents/studies/rails-optimization-task2/task-2.rb:19
273+
```
274+
- Пришлось переписать весть код, перейти на построчное считывание данных и их запись. Так же запустил тест на файле `data_large.txt`, в итоге уложился в метрику с двухкратным запасом. Файл потребляет ~38 мБ.
275+
237276
## Результаты
238277
В результате проделанной оптимизации наконец удалось обработать файл с данными.
239-
Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет.
240-
241-
*Какими ещё результами можете поделиться*
278+
Удалось улучшить метрику системы с 131 MB при выполнении 20_000к строк и 226 MB при выполнении 40_000к строк в начале, до ~38 мБ на файле `data_large.txt` и уложиться в заданный бюджет.
242279

243280
## Защита от регрессии производительности
244-
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали*
281+
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы был написан performance тест для полного объема файла `data_large.txt`

memory-profiler.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
require 'memory_profiler'
44

55
report = MemoryProfiler.report do
6-
work('spec/fixtures/files/data_40000.txt', disable_gc: true)
6+
work('spec/fixtures/files/data_60000.txt', true)
77
end
88
report.pretty_print(scale_bytes: true)

ruby-prof-allocations.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RubyProf.measure_mode = RubyProf::ALLOCATIONS
55

66
result = RubyProf::Profile.profile do
7-
work("spec/fixtures/files/data_40000.txt", disable_gc: true)
7+
work("spec/fixtures/files/data_40000.txt", true)
88
end
99

1010
printer = RubyProf::FlatPrinter.new(result)

ruby-prof-memory.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
RubyProf.measure_mode = RubyProf::MEMORY
55

66
result = RubyProf::Profile.profile do
7-
work("spec/fixtures/files/data_40000.txt", disable_gc: true)
7+
work("spec/fixtures/files/data_40000.txt", true)
88
end
99

1010
printer = RubyProf::FlatPrinter.new(result)

spec/task-2_spec.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
let(:file) { './spec/fixtures/files/data.txt' }
99

1010
let(:expected_result) do
11-
'{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}}' + "\n"
11+
# '{"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49","usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}}}' + "\n"
12+
'{"usersStats":{"Leida Cira":{"sessionsCount":6,"totalTime":"455 min.","longestSession":"118 min.","browsers":"FIREFOX 12, INTERNET EXPLORER 28, INTERNET EXPLORER 28, INTERNET EXPLORER 35, SAFARI 29, SAFARI 39","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-09-27","2017-03-28","2017-02-27","2016-10-23","2016-09-15","2016-09-01"]},"Palmer Katrina":{"sessionsCount":5,"totalTime":"218 min.","longestSession":"116 min.","browsers":"CHROME 13, CHROME 6, FIREFOX 32, INTERNET EXPLORER 10, SAFARI 17","usedIE":true,"alwaysUsedChrome":false,"dates":["2017-04-29","2016-12-28","2016-12-20","2016-11-11","2016-10-21"]},"Gregory Santos":{"sessionsCount":4,"totalTime":"192 min.","longestSession":"85 min.","browsers":"CHROME 20, CHROME 35, FIREFOX 47, SAFARI 49","usedIE":false,"alwaysUsedChrome":false,"dates":["2018-09-21","2018-02-02","2017-05-22","2016-11-25"]}},"totalUsers":3,"uniqueBrowsersCount":14,"totalSessions":15,"allBrowsers":"CHROME 13,CHROME 20,CHROME 35,CHROME 6,FIREFOX 12,FIREFOX 32,FIREFOX 47,INTERNET EXPLORER 10,INTERNET EXPLORER 28,INTERNET EXPLORER 35,SAFARI 17,SAFARI 29,SAFARI 39,SAFARI 49"}'
1213
end
1314

1415
it 'returns equal' do
@@ -18,11 +19,11 @@
1819
end
1920

2021
describe 'Memory usage' do
21-
let(:data_file_path) { "./spec/fixtures/files/data_20000.txt" }
22+
let(:data_file_path) { "./data_large.txt" }
2223

23-
it 'performs success with data_20000.txt' do
24+
it 'performs success with data_large.txt' do
2425
work(data_file_path)
25-
expect((`ps -o rss= -p #{Process.pid}`.to_i / 1024)).to be < 59
26+
expect((`ps -o rss= -p #{Process.pid}`.to_i / 1024)).to be < 38
2627
end
2728
end
2829
end

stackprof.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
require 'stackprof'
33

44
StackProf.run(mode: :object, out: 'ruby_prof_reports/stackprof.dump', row: true) do
5-
work('./spec/fixtures/files/data_20000.txt', disable_gc: true)
5+
work('./spec/fixtures/files/data_20000.txt', true)
66
end

task-2.rb

Lines changed: 73 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,92 @@
1-
# Deoptimized version of homework task
1+
# frozen_string_literal: true
22

33
require 'json'
4-
require 'pry'
5-
require 'date'
6-
require 'minitest/autorun'
4+
require 'set'
75

8-
class User
9-
attr_reader :attributes, :sessions
10-
11-
def initialize(attributes:, sessions:)
12-
@attributes = attributes
13-
@sessions = sessions
14-
end
6+
def parse_line(line)
7+
type, id, *params = line.split(',')
8+
[type, id, *params]
159
end
1610

17-
def parse_user(id, first_name, last_name, age)
11+
def initialize_user(params)
1812
{
19-
'id' => id,
20-
'first_name' => first_name,
21-
'last_name' => last_name,
22-
'age' => age,
13+
user_key: "#{params[0]} #{params[1]}",
14+
sessionsCount: 0,
15+
totalTime: 0,
16+
longestSession: 0,
17+
usedIE: false,
18+
alwaysUsedChrome: true,
19+
dates: [],
20+
browsers: []
2321
}
2422
end
2523

26-
def parse_session(user_id, session_id, browser, time, date)
27-
{
28-
'user_id' => user_id,
29-
'session_id' => session_id,
30-
'browser' => browser,
31-
'time' => time,
32-
'date' => date,
33-
}
24+
def update_user_with_session(user, browser, time, date)
25+
user[:browsers] << browser
26+
user[:usedIE] ||= browser.include?('INTERNET EXPLORER')
27+
user[:alwaysUsedChrome] &&= browser.include?('CHROME')
28+
user[:totalTime] += time.to_i
29+
user[:sessionsCount] += 1
30+
user[:longestSession] = [user[:longestSession], time.to_i].max
31+
user[:dates] << date
3432
end
3533

36-
def collect_stats_from_users(report, users_objects, &block)
37-
users_objects.each do |user|
38-
user_key = "#{user.attributes['first_name']}" + ' ' + "#{user.attributes['last_name']}"
39-
report['usersStats'][user_key] ||= {}
40-
report['usersStats'][user_key] = report['usersStats'][user_key].merge(block.call(user))
41-
end
34+
def build_user_stat(user)
35+
{
36+
sessionsCount: user[:sessionsCount],
37+
totalTime: "#{user[:totalTime]} min.",
38+
longestSession: "#{user[:longestSession]} min.",
39+
browsers: user[:browsers].sort.join(', '),
40+
usedIE: user[:usedIE],
41+
alwaysUsedChrome: user[:alwaysUsedChrome],
42+
dates: user[:dates].sort.reverse
43+
}.to_json
4244
end
4345

44-
def work(file_path, disable_gc: {})
46+
def work(file_path, disable_gc = false)
4547
GC.disable if disable_gc
4648

47-
file_lines = File.read(file_path).split("\n")
48-
49-
users = []
50-
sessions = []
51-
52-
file_lines.each do |line|
53-
type, id, param_1, param_2, param_3, param_4 = line.split(',')
54-
users << parse_user(id, param_1, param_2, param_3) if type == 'user'
55-
sessions << parse_session(id, param_1, param_2, param_3, param_4) if type == 'session'
56-
end
57-
58-
# Отчёт в json
59-
# - Сколько всего юзеров +
60-
# - Сколько всего уникальных браузеров +
61-
# - Сколько всего сессий +
62-
# - Перечислить уникальные браузеры в алфавитном порядке через запятую и капсом +
63-
#
64-
# - По каждому пользователю
65-
# - сколько всего сессий +
66-
# - сколько всего времени +
67-
# - самая длинная сессия +
68-
# - браузеры через запятую +
69-
# - Хоть раз использовал IE? +
70-
# - Всегда использовал только Хром? +
71-
# - даты сессий в порядке убывания через запятую +
72-
73-
report = {}
74-
75-
report[:totalUsers] = users.count
76-
77-
# Подсчёт количества уникальных браузеров
78-
uniqueBrowsers = []
79-
sessions.each do |session|
80-
browser = session['browser']
81-
uniqueBrowsers << browser if uniqueBrowsers.all? { |b| b != browser }
82-
end
83-
84-
report['uniqueBrowsersCount'] = uniqueBrowsers.count
85-
86-
report['totalSessions'] = sessions.count
87-
88-
report['allBrowsers'] =
89-
sessions
90-
.map { |s| s['browser'] }
91-
.map { |b| b.upcase }
92-
.sort
93-
.uniq
94-
.join(',')
95-
96-
# Статистика по пользователям
97-
98-
user_sessions = sessions.group_by { |session| session['user_id'] }
99-
users_objects = users.map do |user|
100-
User.new(attributes: user, sessions: user_sessions[user['id']] || [])
101-
end
102-
103-
report['usersStats'] = {}
104-
105-
users_objects.each do |user|
106-
user_key = "#{user.attributes['first_name']} #{user.attributes['last_name']}"
107-
108-
# Подготовим данные для сессий
109-
sessions = user.sessions
110-
times = sessions.map { |s| s['time'].to_i }
111-
browsers = sessions.map { |s| s['browser'].upcase }
112-
113-
report['usersStats'][user_key] = {
114-
# Количество сессий
115-
'sessionsCount' => sessions.count,
116-
# Общее время
117-
'totalTime' => "#{times.sum} min.",
118-
# Самая длинная сессия
119-
'longestSession' => "#{times.max} min.",
120-
# Браузеры через запятую
121-
'browsers' => browsers.sort.join(', '),
122-
# Хоть раз использовал IE?
123-
'usedIE' => browsers.any? { |b| b.include?('INTERNET EXPLORER') },
124-
# Всегда использовал только Chrome?
125-
'alwaysUsedChrome' => browsers.all? { |b| b.include?('CHROME') },
126-
# Даты сессий через запятую в обратном порядке в формате iso8601
127-
'dates' => user.sessions.map { |s| s['date'] }.sort.reverse
128-
}
49+
unique_browsers = Set.new
50+
total_users = 0
51+
total_sessions = 0
52+
current_user = nil
53+
is_first_user = true
54+
55+
File.open('spec/fixtures/files/result.json', 'w') do |result|
56+
result.write('{"usersStats":{')
57+
58+
File.foreach(file_path, chomp: true) do |line|
59+
type, _id, *params = parse_line(line)
60+
61+
if type == 'user'
62+
if current_user
63+
result.write(',') unless is_first_user
64+
result.write("\"#{current_user[:user_key]}\":#{build_user_stat(current_user)}")
65+
is_first_user = false
66+
end
67+
68+
current_user = initialize_user(params)
69+
total_users += 1
70+
elsif type == 'session'
71+
browser = params[1].upcase
72+
update_user_with_session(current_user, browser, params[2], params[3])
73+
unique_browsers.add(browser)
74+
total_sessions += 1
75+
end
76+
end
77+
78+
if current_user
79+
result.write(',') unless is_first_user
80+
result.write("\"#{current_user[:user_key]}\":#{build_user_stat(current_user)}")
81+
end
82+
83+
result.write('},')
84+
result.write("\"totalUsers\":#{total_users},")
85+
result.write("\"uniqueBrowsersCount\":#{unique_browsers.count},")
86+
result.write("\"totalSessions\":#{total_sessions},")
87+
result.write("\"allBrowsers\":\"#{unique_browsers.sort.join(',')}\"")
88+
result.write('}')
12989
end
13090

131-
File.write('spec/fixtures/files/result.json', "#{report.to_json}\n")
13291
puts "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024)
13392
end

0 commit comments

Comments
 (0)