-
Notifications
You must be signed in to change notification settings - Fork 140
Домашняя работа 2 недели #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
source "https://rubygems.org" | ||
gem 'memory_profiler' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
GEM | ||
remote: https://rubygems.org/ | ||
specs: | ||
memory_profiler (1.0.2) | ||
|
||
PLATFORMS | ||
x86_64-darwin-20 | ||
|
||
DEPENDENCIES | ||
memory_profiler | ||
|
||
BUNDLED WITH | ||
2.4.10 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
require 'benchmark' | ||
require_relative 'task-2_with_file.rb' | ||
|
||
time = Benchmark.realtime do | ||
work('data_large.txt') | ||
end | ||
puts "Finish in #{time.round(2)}" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Case-study оптимизации | ||
|
||
## Актуальная проблема | ||
В нашем проекте возникла серьёзная проблема. | ||
|
||
Необходимо было обработать файл с данными, чуть больше ста мегабайт. | ||
|
||
У нас уже была программа на `ruby`, которая умела делать нужную обработку. | ||
|
||
Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время. | ||
|
||
Я решил исправить эту проблему, оптимизировав эту программу. | ||
|
||
## Формирование метрики | ||
Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: проверка использования памяти на файле с меньшим количество строк (40000 строк). | ||
|
||
## Гарантия корректности работы оптимизированной программы | ||
Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. | ||
|
||
## Feedback-Loop | ||
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* | ||
|
||
Вот как я построил `feedback_loop`: *как вы построили feedback_loop* | ||
решил использовать файл с 40000 строк для оптимизации | ||
и последовательно оптимизировать "точки роста" | ||
|
||
## Вникаем в детали системы, чтобы найти главные точки роста | ||
Для того, чтобы найти "точки роста" для оптимизации я воспользовался гемом memory_profiler | ||
|
||
Вот какие проблемы удалось найти и решить | ||
|
||
### Ваша находка №1 | ||
- memory_profiler показал, что формирование массива sessions занимает 4.59 Gb | ||
- как вы решили её оптимизировать - решил писать напрямую в sessions без создания промежуточных массивов | ||
- как изменилась метрика: память уменьшилась с 6.68 GB до 2.09 GB | ||
- как изменился отчёт профилировщика - формирование массива session теперь не занимает памяти больше всего. | ||
|
||
### Ваша находка №2 | ||
- memory_profiler показал, что формирование массива user_sessions занимает 1.66 GB | ||
- как вы решили её оптимизировать - решил переделать формирование массива users_objects | ||
- как изменилась метрика: память уменьшилась с 2.09 GB до 278.46 MB | ||
- как изменился отчёт профилировщика - формирование массива session теперь не занимает памяти больше всего. | ||
|
||
### Ваша находка №3 | ||
- memory_profiler показал, что формирование массива users занимает 152.96 MB | ||
- как вы решили её оптимизировать - решил писать напрямую в users без создания промежуточных массивов | ||
- как изменилась метрика: память уменьшилась с 278.46 MB до 127.15 MB MB | ||
- как изменился отчёт профилировщика - формирование массива users теперь не занимает памяти больше всего. | ||
|
||
### Ваша находка №4 | ||
- memory_profiler показал, что получение "Даты сессий через запятую в обратном порядке в формате" занимает 30.83 MB MB | ||
- как вы решили её оптимизировать - решил упростить получение дат сессий | ||
- как изменилась метрика: память уменьшилась с 127.15 MB MB до 98.00 MB | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. сорри за духоту, но тут лучше уточнить формулировки; "память уменьшилась" звучит не очень конкретно |
||
- как изменился отчёт профилировщика - получение "Даты сессий через запятую в обратном порядке в формате" теперь не занимает памяти больше всего. | ||
|
||
### Ваша находка №5 | ||
- memory_profiler показал, что split строк файла занимает 18.95 MB | ||
- как вы решили её оптимизировать - было решено переделать и написать в "потоковом" стиле | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. хорошо, что сделали несколько итераций до этого, польза++ |
||
- как изменилась метрика: память уменьшилась с 98.00 MB до 15 MB | ||
- как изменился отчёт профилировщика - обработка строк большого файла теперь занимает 15 MB и 27,47 секунд. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. самый кайф, что на тех же 15МБ можно любой объём данных перелопатить |
||
|
||
## Результаты | ||
В результате проделанной оптимизации наконец удалось обработать файл с данными. | ||
Удалось улучшить метрику системы с 4.59 Gb (на файле в 40000 строк) до 15 MB (на большом файле) и уложиться в заданный бюджет. | ||
|
||
*Какими ещё результами можете поделиться* | ||
|
||
## Защита от регрессии производительности | ||
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'json' | ||
require 'oj' | ||
require 'set' | ||
|
||
class User | ||
attr_accessor :session_stats, :sessions, :first_name, :last_name | ||
|
||
def initialize(id, first_name, last_name, age) | ||
@id = id | ||
@first_name = first_name | ||
@last_name = last_name | ||
@age = age | ||
@sessions = [] | ||
@session_stats = {} | ||
end | ||
end | ||
|
||
def parse_session(fields) | ||
{ | ||
'user_id' => fields[0], | ||
'session_id' => fields[1], | ||
'browser' => fields[2], | ||
'time' => fields[3], | ||
'date' => fields[4] | ||
} | ||
end | ||
|
||
def collect_stats_from_user(user) | ||
return {} unless user | ||
|
||
stats = { | ||
'sessionsCount' => user.sessions.count, | ||
'totalTime' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.sum.to_s + ' min.', | ||
'longestSession' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.max.to_s + ' min.', | ||
'browsers' => user.sessions.map {|s| s['browser']}, | ||
'dates' => user.sessions.map { |s| s['date'] }.sort.reverse | ||
} | ||
|
||
stats['usedIE'] = stats['browsers'].any? { |b| b =~ /INTERNET EXPLORER/ } | ||
stats['alwaysUsedChrome'] = stats['browsers'].all? { |b| b =~ /CHROME/ } | ||
stats['browsers'] = stats['browsers'].sort.join(', ') | ||
stats['dates'].sort!.reverse! | ||
stats | ||
end | ||
|
||
def write_user(user, stream_writer) | ||
stream_writer.push_key("#{user.first_name} #{user.last_name}") | ||
stream_writer.push_object | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. лайк за stream_writer |
||
user.session_stats.each { |key, value| stream_writer.push_value(value, key.to_s) } | ||
stream_writer.pop | ||
end | ||
|
||
def work(file_name) | ||
total_users = 0 | ||
total_sessions = 0 | ||
unique_browsers = Set.new | ||
user = nil | ||
|
||
result_file = File.open('result.json', 'w') | ||
|
||
stream_writer = Oj::StreamWriter.new(result_file) | ||
stream_writer.push_object | ||
stream_writer.push_key('usersStats') | ||
stream_writer.push_object | ||
|
||
File.foreach(file_name) do |line| | ||
type, *info = line.strip!.split(',') | ||
if type == 'user' | ||
total_users += 1 | ||
user.session_stats = collect_stats_from_user(user) if user | ||
write_user(user, stream_writer) if user | ||
user = User.new(*info) | ||
end | ||
|
||
if type == 'session' | ||
total_sessions += 1 | ||
session = parse_session(info) | ||
user.sessions << session | ||
unique_browsers << session['browser'].upcase! | ||
end | ||
end | ||
|
||
user.session_stats = collect_stats_from_user(user) if user | ||
write_user(user, stream_writer) if user | ||
|
||
stream_writer.pop | ||
|
||
stream_writer.push_value(total_users, 'totalUsers') | ||
stream_writer.push_value(unique_browsers.count, 'uniqueBrowsersCount') | ||
stream_writer.push_value(total_sessions, 'totalSessions') | ||
stream_writer.push_value(unique_browsers.sort.join(','), 'allBrowsers') | ||
|
||
stream_writer.pop_all | ||
result_file.close | ||
|
||
puts "MEMORY USAGE: %d MB" % (`ps -o rss= -p #{Process.pid}`.to_i / 1024) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сорри за духоту, но это не метрика; метрика это число какое-то, которое характеризует вашу систему