Skip to content
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

[Yaroslavzev] Homework #140

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
98 changes: 98 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
benchmark-ips (2.13.0)
benchmark-malloc (0.2.0)
benchmark-perf (0.6.0)
benchmark-trend (0.4.0)
coderay (1.1.3)
date (3.3.4)
diff-lcs (1.5.1)
fasterer (0.11.0)
ruby_parser (>= 3.19.1)
json (2.7.2)
kalibera (0.1.2)
memoist (~> 0.16)
rbzip2 (~> 0.3)
language_server-protocol (3.17.0.3)
memoist (0.16.2)
method_source (1.1.0)
minitest (5.22.3)
parallel (1.24.0)
parser (3.3.1.0)
ast (~> 2.4.1)
racc
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.7.3)
rainbow (3.1.1)
rbzip2 (0.3.0)
regexp_parser (2.9.0)
rexml (3.2.6)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-benchmark (0.6.0)
benchmark-malloc (~> 0.2)
benchmark-perf (~> 0.6)
benchmark-trend (~> 0.4)
rspec (>= 3.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.1)
rubocop (1.63.4)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-prof (1.7.0)
ruby-progressbar (1.13.0)
ruby_parser (3.21.0)
racc (~> 1.5)
sexp_processor (~> 4.16)
sexp_processor (4.17.1)
stackprof (0.2.26)
unicode-display_width (2.5.0)

PLATFORMS
arm64-darwin-22
x86_64-darwin-21

DEPENDENCIES
benchmark-ips
date
fasterer
json
kalibera
minitest
pry
rspec-benchmark
rubocop-performance
ruby-prof
stackprof

RUBY VERSION
ruby 3.2.2p53

BUNDLED WITH
2.3.7
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ head -n N data_large.txt > dataN.txt # create smaller file from larger (take N f
## Checklist
Советую использовать все рассмотренные в лекции инструменты хотя бы по разу - попрактикуйтесь с ними, научитесь с ними работать.

- [ ] Прикинуть зависимость времени работы програмы от размера обрабатываемого файла
- [ ] Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`;
- [ ] Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`;
- [ ] Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`;
- [ ] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
- [ ] Построить дамп `stackprof` и проанализировать его с помощью `CLI`
- [ ] Построить дамп `stackprof` в `json` и проанализировать его с помощью `speedscope.app`
- [x] Прикинуть зависимость времени работы програмы от размера обрабатываемого файла
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `Flat`;
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `Graph`;
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `CallStack`;
- [x] Построить и проанализировать отчёт `ruby-prof` в режиме `CallTree` c визуализацией в `QCachegrind`;
- [x] Построить дамп `stackprof` и проанализировать его с помощью `CLI`
- [x] Построить дамп `stackprof` в `json` и проанализировать его с помощью `speedscope.app`
- [ ] Профилировать работающий процесс `rbspy`;
- [ ] Добавить в программу `ProgressBar`;
- [ ] Постараться довести асимптотику до линейной и проверить это тестом;
- [ ] Написать простой тест на время работы: когда вы придёте к оптимизированному решению, замерьте, сколько оно будет работать на тестовом объёме данных; и напишите тест на то, что это время не превышается (чтобы не было ложных срабатываний, задайте время с небольшим запасом);
- [x] Постараться довести асимптотику до линейной и проверить это тестом;
- [x] Написать простой тест на время работы: когда вы придёте к оптимизированному решению, замерьте, сколько оно будет работать на тестовом объёме данных; и напишите тест на то, что это время не превышается (чтобы не было ложных срабатываний, задайте время с небольшим запасом);

### Главное
Нужно потренироваться методично работать по схеме с фидбек-лупом:
Expand Down
64 changes: 44 additions & 20 deletions case-study-template.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Case-study оптимизации

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

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

Необходимо было обработать файл с данными, чуть больше ста мегабайт.
Expand All @@ -12,45 +13,68 @@
Я решил исправить эту проблему, оптимизировав эту программу.

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

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

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

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

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

Вот как я построил `feedback_loop`: *как вы построили feedback_loop*
Для того, чтобы иметь возможность быстро проверять гипотезы я выстроил эффективный `feedback-loop`, который позволил мне получать обратную связь по эффективности сделанных изменений за _время, которое у вас получилось_
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_it was a placeholder_


Вот как я построил `feedback_loop`:
Изначальный код отметил как начальный и определил перфоманс для 4000 строк
Последующие итерации помещены в версии и для каждой из версий определен прирост производительности на основе тестовю

## Вникаем в детали системы, чтобы найти главные точки роста
Для того, чтобы найти "точки роста" для оптимизации я воспользовался *инструментами, которыми вы воспользовались*

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

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

### Ваша находка №1
- какой отчёт показал главную точку роста
- как вы решили её оптимизировать
- как изменилась метрика
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста?

- флейм граф показал, что Array#select занимает значительное время исполнения
- Array#select происводит поиск по всему массиву сессий и это занимает много времени. Для ускорения поиска, я изменил структуру оранизации сессий и в реализации V1 сессии сгруппированы по user_id в хеш. Таким образом, используются оптимизированный механизмы поиска ruby и достигается прирост происховодительности
- Скрость выполнения увеличина в 4 раза для 4000 строк
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут главное, что асимптотика стала качественно лучше и приблизилась к линейное

Потому что фетч из хэша это O(1), а select из массива это O(N)

- исправленная проблема перестала быть точкой роста

### Ваша находка №2
- какой отчёт показал главную точку роста
- как вы решили её оптимизировать
- как изменилась метрика
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста?

### Ваша находка №X
- какой отчёт показал главную точку роста
- как вы решили её оптимизировать
- как изменилась метрика
- как изменился отчёт профилировщика - исправленная проблема перестала быть главной точкой роста?
- флейм граф показал, что Array#+ занимает значительное время исполнения
- На основе https://github.com/fastruby/fast-ruby?tab=readme-ov-file#arrayconcat-vs-array-code Array#+ можно заменить на Array#concat
- Замена Array#+ на Array#concat дала прирост как минимум 10% на 4000 строк
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4000 строк это маловато уже

хорошая идея взять какую-то часть входных данных, чтобы программа не выполнялась вечность, но хоршо подбирать объём данных так, чтобы программа успевала покрутиться пару секунд. Если она завершается слишком быстро (“не успевает поработать”) могут возникнуть какие-то перекосы (например, на полном объёме основная часть времени тратится в основном цикле, а если данных мало - то большая часть уходит на инициализацию и финализацию, например на чтение из файла и запись потом в файл)

И плюс когда время уже на миллисекунды - больше влияние погрешностей.

- исправленная проблема перестала быть точкой роста и следует увеличить количство строк

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

- флейм граф показал, что метод String#split и Date#parse занимает значительное время
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

- Заменил String#split на String#match и убрал Date#parse
- Указанные изменения повысили производительность на 40% на файле 30 000 строк

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

- флейм граф показал, что поиск уникальных браузеров занимает значительное время
- Рефакторинг вычисления сессий как в находке 1
- Указанные изменения повысили производительность на 50% на файле 30 000 строк

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

- общий рефакторинг кода
- Вызов upcase во время парсинга(а не несколько раз), эмуляция метода any? c break и др
- Указанные изменения не повлияли значительно на производительность

## Результаты

В результате проделанной оптимизации наконец удалось обработать файл с данными.
Удалось улучшить метрику системы с *того, что у вас было в начале, до того, что получилось в конце* и уложиться в заданный бюджет.
Удалось улучшить метрику системы с того, что нельзя дождать окончания результата обработки, до того, что получилось уложиться в выполнению программы в заданный бюджет 30 секунд.
Измеренный прирост производительности составил 33 раз для обработки файла в 10 000 строк

Заданный бюджет выполнения программы в 30 секунд был достигнут на M1

*Какими ещё результами можете поделиться*

## Защита от регрессии производительности
Для защиты от потери достигнутого прогресса при дальнейших изменениях программы *о performance-тестах, которые вы написали*

Для защиты от потери достигнутого прогресса при дальнейших изменениях программы написаны тесты производительности.
16 changes: 16 additions & 0 deletions gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
source 'https://rubygems.org'

ruby '3.2.2'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


gem 'json'
gem 'pry'
gem 'date'
gem 'minitest'
gem 'stackprof'
gem 'ruby-prof'
gem 'fasterer'
gem 'rspec-benchmark'
gem 'kalibera'
gem 'benchmark-ips'
gem 'rubocop-performance'
gem 'fasterer'
Binary file added profilers/.DS_Store
Binary file not shown.
19 changes: 19 additions & 0 deletions profilers/perfomance/benchmark.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'benchmark/ips'
require_relative '../../task-1'

file_name = './data1000.txt'

Benchmark.ips do |x|
# The default is :stats => :sd, which doesn't have a configurable confidence
# confidence is 95% by default, so it can be omitted
x.config(
stats: :bootstrap,
confidence: 95
)

x.report('slow string concatenation') do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong name

work(file_name)
end
end
Loading