Skip to content

Latest commit

 

History

History
109 lines (82 loc) · 10.1 KB

case-study.md

File metadata and controls

109 lines (82 loc) · 10.1 KB

Case-study оптимизации

Актуальная проблема

В нашем проекте возникла серьёзная проблема.

Необходимо было обработать файл с данными, чуть больше ста мегабайт.

У нас уже была программа на ruby, которая умела делать нужную обработку.

Она успешно работала на файлах размером пару мегабайт, но для большого файла она работала слишком долго, и не было понятно, закончит ли она вообще работу за какое-то разумное время.

Я решил исправить эту проблему, оптимизировав эту программу.

Формирование метрики

Для того, чтобы понимать, дают ли мои изменения положительный эффект на быстродействие программы я придумал использовать такую метрику: время обработки файла.

Гарантия корректности работы оптимизированной программы

Программа поставлялась с тестом. Выполнение этого теста в фидбек-лупе позволяет не допустить изменения логики программы при оптимизации.

Feedback-Loop

Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный feedback-loop, который позволил мне получать обратную связь по эффективности сделанных изменений за 28-28 секунд.

Вот как я построил feedback_loop: Итерации: замерял метрики, составлял отчет, находил потенциальную точку роста, менял код, снова смотрел метрики и отчет.

Вникаем в детали системы, чтобы найти главные точки роста

Для того, чтобы найти "точки роста" для оптимизации я воспользовался гемами Ruby Prof, Stackprof

Вот какие проблемы удалось найти и решить

Ваша находка №1

  • в блоке создания объектов User - sessions.select - очень долгий
  • группировка сессий по user_id и итерация по users - выбор аттрибутов для объекта User остался тем же, а сессии берутся из массива сгруппированных сессий (по user_id)
  • обработка ускорилась значительно - раньше 40 тыс. строк обрабатывалось за 37 сек., стало - за 0,65
  • отчёт профилировщика изменился значительно - исправленная проблема не перестала быть главной точкой роста, но с 90% времени в ней показывается около 50%

Ваша находка №2

  • в блоке file_lines.each {} - split и обращение по индексу показался дорогими; проверка условий (здесь скорее рефакторинг)
  • заменить split и :[] на start_with? и добавить ветления if elsif
  • ускорилась обработка на ~ 2-3%
  • отчёт профилировщика почти не изменился - исправленная проблема не перестала быть главной точкой роста

Ваша находка №3

  • в методах parse_user, parse_session лишний split и объявление переменной parsed_result
  • убрать данные конструкции и передавать в parse_session уже массив (заранее 'splitted'), а не строку
  • ускорилась обработка на 5%
  • отчёт профилировщика почти не изменился - исправленная проблема не перестала быть главной точкой роста

Ваша находка №4

  • метод вычисление параметра 'usedIE' вызовом метода 'collect_stats_from_users' - upcase
  • убрать upcase и использовать case-insensitive RegEx
  • ускорилась обработка на пару процентов
  • отчёт профилировщика почти не изменился - исправленная проблема не перестала быть главной точкой роста

Ваша находка №5

  • метод вычисление параметра 'alwaysUsedChrome' вызовом метода 'collect_stats_from_users' - дорогой метод 'all?'
  • заменить метод all? на any? и ревертнуть метод (добавить отрицание)
  • ускорилась обработка на пару процентов
  • отчёт профилировщика почти не изменился - исправленная проблема не перестала быть главной точкой роста

Ваша находка №6

  • вычисление uniqueBrowsers - используется all? - дорого
  • заменить метод all? на sessions.map(<браузеры>).uniq
  • ускорилась обработка на пару процентов
  • отчёт профилировщика почти не изменился - исправленная проблема не перестала быть главной точкой роста

Ваша находка №7

  • парсинг дат для статистики - никак не преобразует их - не нужен
  • убрать парсинг
  • ускорилась обработка на ~ 45%
  • отчёт профилировщика изменился - точка роста переместилась на методы parse_user, parse_session. Файл data_large.txt обработан за 43 секунды.

Ваша находка №8

  • слишком много вызовов метода collect_stats_from_users и многоразовый map и upcase
  • соединить все вызовы collect_stats_from_users в один и присвоить результаты map и upcase переменным. Затем использовать переменнные
  • ускорилась обработка на пару процентов
  • отчёт профилировщика почти не изменился

Ваша находка №9

  • вычисление report['allBrowsers'] происходит путем сортировки, потом уникальности
  • надо сначала отобрать уникальные браузеры, потом сортировать, их будет меньше -> сортировка быстрее
  • ускорилась обработка на 20%
  • получилось, что не это главная точка роста, просто нашел и ускорил этот фрагмент. Точка роста не изменилась.

Ваша находка №10

  • строковые конкатенации и интерполяции
  • надо заменить их оператором '<<'
  • ускорилась обработка на пару процентов
  • получилось, что не это главная точка роста, просто нашел и ускорил этот фрагмент. Точка роста не изменилась. Здесь я, к сожалению, внес оптимизацию по наитию, но она оказалось весьма усместна.

Ваша находка №11

  • использование json
  • решил поменять на Oj (упомяналась в лекциях, как оптимизированная библиотека)
  • ускорилась обработка на пару процентов
  • это была не главная точка роста, просто нашел и ускорил этот фрагмент. Точка роста не изменилась. Оптимизация по наитию опять)

Результаты

В результате проделанной оптимизации наконец удалось обработать файл с данными. Удалось улучшить метрику системы с неопределенного времени обработки до 28-29 секунд и уложиться в заданный бюджет.

Другие наблюдения:

  1. в начале выполнения задания асимптотика была квадратичной (степень ~ 2, на вход подавал в два раза больше, время увеличивалось в 4 раза);
  2. при отключении GC время обработки увеличивалось примерно на четверть (супер странно);

Защита от регрессии производительности

Для защиты от потери достигнутого прогресса при дальнейших изменениях программы я написал тест, замеряющий время обработки небольшого файла. Правда я так и не понял, как нормально написать тесты на линейность. Сделал несколько попыток. Тест то проходит, то нет. Извините, что сложил все в один коммит. Вспомнил про это в конце работы.