diff --git a/case-study-template.md b/case-study-template.md index b572c8ce..9dfb914e 100644 --- a/case-study-template.md +++ b/case-study-template.md @@ -12,35 +12,27 @@ Я решил исправить эту проблему, оптимизировав эту программу. ## Формирование метрики -Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: *тут ваша метрика* +Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: замеряить время выполнения после каждого итератора ## Гарантия корректности работы оптимизированной программы Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации. ## Feedback-Loop -Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за *время, которое у вас получилось* - -Вот как я построил `feedback_loop`: *как вы построили feedback_loop* +Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за 57с, 40c, 34c, 23c, 19с, 11.703119с ## Вникаем в детали системы, чтобы найти главные точки роста -Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались* - -Вот какие проблемы удалось найти и решить - -### Ваша находка №1 -О вашей находке №1 - -### Ваша находка №2 -О вашей находке №2 - -### Ваша находка №X -О вашей находке №X - +Для того, чтобы найти "точки роста" для оптимизации я воспользовался встроенным в рубимайн профилировщиком rbspy + +Вот какие проблемы удалось найти и решить: +- Убрал поиск по айди пользователя, без надобности многократно проходивший по всему массиву +- Переписал все за один проход, избавившись от многих ненужных лишних итераций +- Выкинул все неиспользуемое чтоб кушало меньше памяти +- Заменил строки в хэшах на ключи, для оптимальнейшей работы с памятью +- Отключил гарбаджколлектор +- Убрал парсер дат за ненадобностью +- Избавился от прожорливых регулярок +- Заменил json на multijson +- прилично сократить число строк кода и убрав документацию, сделав его самодокументируемым ## Результаты В результате проделанной оптимизации наконец удалось обработать файл с данными. -Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет. - -*Какими ещё результами можете поделиться* - -## Защита от регрессии производительности -Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали* +Удалось улучшить метрику системы с 57 до 11.703119 секунды и уложиться в заданный бюджет. diff --git a/task-1.rb b/task-1.rb index 778672df..2494d17b 100644 --- a/task-1.rb +++ b/task-1.rb @@ -1,152 +1,65 @@ -# Deoptimized version of homework task - -require 'json' -require 'pry' -require 'date' +require 'multi_json' require 'minitest/autorun' -class User - attr_reader :attributes, :sessions - - def initialize(attributes:, sessions:) - @attributes = attributes - @sessions = sessions +def user_stats(sessions) + sessions_time = [] + sessions_browser = [] + sessions_date = [] + sessions.each do |s| + sessions_time << s[:time] + sessions_browser << s[:browser] + sessions_date << s[:date] end -end - -def parse_user(user) - fields = user.split(',') - parsed_result = { - 'id' => fields[1], - 'first_name' => fields[2], - 'last_name' => fields[3], - 'age' => fields[4], - } -end - -def parse_session(session) - fields = session.split(',') - parsed_result = { - 'user_id' => fields[1], - 'session_id' => fields[2], - 'browser' => fields[3], - 'time' => fields[4], - 'date' => fields[5], + { + sessionsCount: sessions.count, + totalTime: "#{sessions_time.sum} min.", + longestSession: "#{sessions_time.max} min.", + browsers: sessions_browser.sort.join(', '), + usedIE: sessions_browser.any? { |b| b[0] == 'I' }, + alwaysUsedChrome: sessions_browser.all? { |b| b[0] == 'C' }, + dates: sessions_date.sort.reverse } end -def collect_stats_from_users(report, users_objects, &block) - users_objects.each do |user| - user_key = "#{user.attributes['first_name']}" + ' ' + "#{user.attributes['last_name']}" - report['usersStats'][user_key] ||= {} - report['usersStats'][user_key] = report['usersStats'][user_key].merge(block.call(user)) +def work(file_name, gc = true) + gc ? GC.enable : GC.disable + time = Time.now + users_count = 0 + browsers = [] + total_sessions = 0 + users_stats = {} + File.read(file_name).split('user,')[1..].each do |raw_line| + user_sessions = raw_line.split("\n") + user = user_sessions[0].split(',') + sessions = user_sessions[1..].map do |raw| + session = raw.split(',') + total_sessions += 1 + browser = session[3].upcase + browsers << browser # unless browsers.include?(browser) + { browser: browser, time: session[4].to_i, date: session[5] } + end + user_name = "#{user[1]} #{user[2]}" + users_stats[user_name] = user_stats(sessions) + users_count += 1 end + uniq_browsers = browsers.uniq + report = { totalUsers: users_count, + uniqueBrowsersCount: uniq_browsers.count, + totalSessions: total_sessions, + allBrowsers: uniq_browsers.sort.join(','), + usersStats: users_stats } + File.write('result.json', "#{MultiJson.dump(report)}\n") + puts "===== #{file_name} ===== GC: #{gc ? 'enabled' : 'disabled' } TIME: #{Time.now - time}" # ===== data_large.txt ===== GC: disabled TIME: # 11.703119 end -def work - file_lines = File.read('data.txt').split("\n") - - users = [] - sessions = [] - - file_lines.each do |line| - cols = line.split(',') - users = users + [parse_user(line)] if cols[0] == 'user' - sessions = sessions + [parse_session(line)] if cols[0] == 'session' - end - - # Отчёт в json - # - Сколько всего юзеров + - # - Сколько всего уникальных браузеров + - # - Сколько всего сессий + - # - Перечислить уникальные браузеры в алфавитном порядке через запятую и капсом + - # - # - По каждому пользователю - # - сколько всего сессий + - # - сколько всего времени + - # - самая длинная сессия + - # - браузеры через запятую + - # - Хоть раз использовал IE? + - # - Всегда использовал только Хром? + - # - даты сессий в порядке убывания через запятую + - - report = {} - - report[:totalUsers] = users.count - - # Подсчёт количества уникальных браузеров - uniqueBrowsers = [] - sessions.each do |session| - browser = session['browser'] - uniqueBrowsers += [browser] if uniqueBrowsers.all? { |b| b != browser } - end - - report['uniqueBrowsersCount'] = uniqueBrowsers.count - - report['totalSessions'] = sessions.count - - report['allBrowsers'] = - sessions - .map { |s| s['browser'] } - .map { |b| b.upcase } - .sort - .uniq - .join(',') - - # Статистика по пользователям - users_objects = [] - - users.each do |user| - attributes = user - user_sessions = sessions.select { |session| session['user_id'] == user['id'] } - user_object = User.new(attributes: attributes, sessions: user_sessions) - users_objects = users_objects + [user_object] - end - - report['usersStats'] = {} - - # Собираем количество сессий по пользователям - collect_stats_from_users(report, users_objects) do |user| - { 'sessionsCount' => user.sessions.count } - end - - # Собираем количество времени по пользователям - collect_stats_from_users(report, users_objects) do |user| - { 'totalTime' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.sum.to_s + ' min.' } - end - - # Выбираем самую длинную сессию пользователя - collect_stats_from_users(report, users_objects) do |user| - { 'longestSession' => user.sessions.map {|s| s['time']}.map {|t| t.to_i}.max.to_s + ' min.' } - end - - # Браузеры пользователя через запятую - collect_stats_from_users(report, users_objects) do |user| - { 'browsers' => user.sessions.map {|s| s['browser']}.map {|b| b.upcase}.sort.join(', ') } - end - - # Хоть раз использовал IE? - collect_stats_from_users(report, users_objects) do |user| - { 'usedIE' => user.sessions.map{|s| s['browser']}.any? { |b| b.upcase =~ /INTERNET EXPLORER/ } } - end - - # Всегда использовал только Chrome? - collect_stats_from_users(report, users_objects) do |user| - { 'alwaysUsedChrome' => user.sessions.map{|s| s['browser']}.all? { |b| b.upcase =~ /CHROME/ } } - end - - # Даты сессий через запятую в обратном порядке в формате iso8601 - collect_stats_from_users(report, users_objects) do |user| - { 'dates' => user.sessions.map{|s| s['date']}.map {|d| Date.parse(d)}.sort.reverse.map { |d| d.iso8601 } } - end - - File.write('result.json', "#{report.to_json}\n") -end +# work('data_test.txt', false) +# work('data_small.txt', false) +work('data_large.txt', false) class TestMe < Minitest::Test def setup File.write('result.json', '') - File.write('data.txt', + File.write('data_test.txt', 'user,0,Leida,Cira,0 session,0,0,Safari 29,87,2016-10-23 session,0,1,Firefox 12,118,2017-02-27 @@ -169,7 +82,7 @@ def setup end def test_result - work + work('data_test.txt', false) expected_result = '{"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" assert_equal expected_result, File.read('result.json') end