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

Memory optimization #110

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

newaaz
Copy link

@newaaz newaaz commented May 12, 2024

  • Построить и проанализировать отчёт гемом memory_profiler
  • Построить и проанализировать отчёт ruby-prof в режиме Flat;
  • Построить и проанализировать отчёт ruby-prof в режиме Graph;
  • Построить и проанализировать отчёт ruby-prof в режиме CallStack;
  • Построить и проанализировать отчёт ruby-prof в режиме CallTree c визуализацией в KCachegrind;
  • Построить и проанализировать текстовый отчёт stackprof;
  • Построить и проанализировать отчёт flamegraph с помощью stackprof и визуализировать его в speedscope.app;
  • Построить график потребления памяти в valgrind massif visualier и включить скриншот в описание вашего PR;
  • Написать тест, на то что программа укладывается в бюджет по памяти

Не удалось корректно проанализировать с Valgrind - отказывается профилировать, искал различные пути решения и настройки, но не докопался. Вот что в логе:

==64848== Massif, a heap profiler
==64848== Copyright (C) 2003-2017, and GNU GPL'd, by Nicholas Nethercote
==64848== Using Valgrind-3.22.0-bd4db67b1d-20231031 and LibVEX; rerun with -h for copyright info
==64848== Command: /home/aaz/.rbenv/shims/ruby work.rb
==64848== Parent PID: 5927
==64848== 
--64848-- 
--64848-- Valgrind options:
--64848--    --tool=massif
--64848--    --massif-out-file=massif.out
--64848--    --log-file=valgrind.log
--64848--    --ignore-fn=memalign
--64848--    --ignore-fn=aligned_alloc
--64848--    --verbose
--64848-- Contents of /proc/version:
--64848--   Linux version 6.5.0-28-generic (buildd@lcy02-amd64-098) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #29~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Apr  4 14:39:20 UTC 2
--64848-- 
--64848-- Arch and hwcaps: AMD64, LittleEndian, amd64-cx16-rdtscp-sse3-ssse3-avx-f16c-rdrand
--64848-- Page sizes: currently 4096, max supported 4096
--64848-- Valgrind library directory: /snap/valgrind/160/usr/libexec/valgrind
--64848-- Massif: alloc-fns:
--64848-- Massif:   malloc
--64848-- Massif:   __builtin_new
--64848-- Massif:   operator new(unsigned)
--64848-- Massif:   operator new(unsigned long)
--64848-- Massif:   __builtin_vec_new
--64848-- Massif:   operator new[](unsigned)
--64848-- Massif:   operator new[](unsigned long)
--64848-- Massif:   calloc
--64848-- Massif:   realloc
--64848-- Massif:   memalign
--64848-- Massif:   posix_memalign
--64848-- Massif:   valloc
--64848-- Massif:   operator new(unsigned, std::nothrow_t const&)
--64848-- Massif:   operator new[](unsigned, std::nothrow_t const&)
--64848-- Massif:   operator new(unsigned long, std::nothrow_t const&)
--64848-- Massif:   operator new[](unsigned long, std::nothrow_t const&)
--64848-- Massif: ignore-fns:
--64848-- Massif:   0: memalign
--64848-- Massif:   1: aligned_alloc
--64848-- Reading syms from /usr/bin/env
--64848-- Reading syms from /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
--64848-- Reading syms from /snap/valgrind/160/usr/libexec/valgrind/massif-amd64-linux
--64848--    object doesn't have a dynamic symbol table
--64848-- Scheduler: using generic scheduler lock implementation.
==64848== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-64848-by-aaz-on-???
==64848== embedded gdbserver: writing to   /tmp/vgdb-pipe-to-vgdb-from-64848-by-aaz-on-???
==64848== embedded gdbserver: shared mem   /tmp/vgdb-pipe-shared-mem-vgdb-64848-by-aaz-on-???
==64848== 
==64848== TO CONTROL THIS PROCESS USING vgdb (which you probably
==64848== don't want to do, unless you know exactly what you're doing,
==64848== or are doing some strange experiment):
==64848==   /snap/valgrind/160/usr/libexec/valgrind/../../bin/vgdb --pid=64848 ...command...
==64848== 
==64848== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==64848==   /path/to/gdb /home/aaz/.rbenv/shims/ruby
==64848== and then give GDB the following command
==64848==   target remote | /snap/valgrind/160/usr/libexec/valgrind/../../bin/vgdb --pid=64848
==64848== --pid is optional if only one valgrind process is running
==64848== 
--64848-- Reading syms from /snap/valgrind/160/usr/libexec/valgrind/vgpreload_core-amd64-linux.so
--64848-- Reading syms from /snap/valgrind/160/usr/libexec/valgrind/vgpreload_massif-amd64-linux.so
--64848-- Reading syms from /usr/lib/x86_64-linux-gnu/libc.so.6
--64848--   Considering /usr/lib/debug/.build-id/96/2015aa9d133c6cbcfb31ec300596d7f44d3348.debug ..
--64848--   .. build-id is valid
==64848== WARNING: new redirection conflicts with existing -- ignoring it
--64848--     old: 0x050b4c60 (memalign            ) R-> (1011.0) 0x04e0abc4 memalign
--64848--     new: 0x050b4c60 (memalign            ) R-> (1017.0) 0x04e0b341 aligned_alloc
==64848== WARNING: new redirection conflicts with existing -- ignoring it
--64848--     old: 0x050b4c60 (memalign            ) R-> (1011.0) 0x04e0abc4 memalign
--64848--     new: 0x050b4c60 (memalign            ) R-> (1017.0) 0x04e0b1d5 aligned_alloc
==64848== WARNING: new redirection conflicts with existing -- ignoring it
--64848--     old: 0x050b4c60 (memalign            ) R-> (1011.0) 0x04e0abc4 memalign
--64848--     new: 0x050b4c60 (memalign            ) R-> (1017.0) 0x04e0b341 aligned_alloc
==64848== WARNING: new redirection conflicts with existing -- ignoring it
--64848--     old: 0x050b4c60 (memalign            ) R-> (1011.0) 0x04e0abc4 memalign
--64848--     new: 0x050b4c60 (memalign            ) R-> (1017.0) 0x04e0b1d5 aligned_alloc
--64848-- REDIR: 0x50b40a0 (libc.so.6:malloc) redirected to 0x4e03103 (malloc)
--64848-- REDIR: 0x50b43e0 (libc.so.6:free) redirected to 0x4e06379 (free)
--64848-- REDIR: 0x50b5520 (libc.so.6:calloc) redirected to 0x4e0a70f (calloc)

Copy link
Collaborator

@spajic spajic left a comment

Choose a reason for hiding this comment

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

👍

### Ваша находка №4
- MemoryProfiler указывает на строку с парсингом даты. Оптимизируем
- Метрика изменилась с 46 МБ до 35 МБ
- строка с датой занимала *13 МБ* и аллоцировала *171163 (!)* объекта. После оптимизации стала занимать *1 МБ с 9929 объектами*. Очень наглядная оптимизация :smirk:
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

### Ваша находка №5
- Дальнейшее профилирование указывает на split (это необходимо) и генерацию и накопление массивов с пользователями и сессиями.
- Поэтому решаем переписать программу на потоковый режим работы. В память будет загружаться информация о пользователе и его сессиях, считаться статистика, и записываться в файл. После чего в память будет загружаться следующий пользователь.
- Метрика показывает результат выполнения 21 МБ. Причём как на 10_000, так и на 3_250_940 строк (файл data_large.txt). Это означает, что мы вложились в бюджет (< 70 МБ). Ура! :smiley:
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 и самое приятное, что так любой объём данных можно перелопатить

- После оптимизации программы решил проверить как влияет фриз строк после оптимизации, и выяснил интересный момент - на 100 000 строках:
- 146 МБ - без фриза
- 139 МБ - с фризом
Получается, данная оптимизация не зависит от количества данных, а зависит от количества использования String в программе?
Copy link
Collaborator

Choose a reason for hiding this comment

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

он же фризит только строковые литералы в коде; от данных не зависит

Получается, данная оптимизация не зависит от количества данных, а зависит от количества использования String в программе?

### Замер скорости
- После оптимизации по памяти провели тестирование на времени выполнения программы. С файлом data_large.txt оно составило - **всего лишь 22 секунды(!)** против 32 сек до оптимизации памяти. Возможно, разница больше, так как замеры выполнялись на разных конфигурациях VM
Copy link
Collaborator

Choose a reason for hiding this comment

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

Да, чаще всего у всех получается в таком подходе результаты лучше чем в первом ДЗ

Такой интересный момент, что когда заходили только с точки зрения CPU вроде бы более прямо ориентировались на время выполнения; но работа с памятью и GC могут давать свои тормоза, которые мы можем не заметить при профилировании CPU. Хотя справедливости ради зачастую именно GC бывает на первых местах в отчётах профилировщиков CPU. Тогда можно задуматься о том, что надо как-то объектов поменьше аллоцировать постараться

## Результаты
- В результате проделанной оптимизации наконец удалось обработать файл с данными. Удалось улучшить метрику системы с 767 МБ до 29 МБ и уложиться в заданный бюджет < 70 MB.
- Приобрел практические навыки в оптимизации используемой памяти в работе приложений
- На практике выявил сильную взаимосвязь между оптимизацией CPU и памятью
Copy link
Collaborator

Choose a reason for hiding this comment

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

++


report['uniqueBrowsersCount'] = uniqueBrowsers.count
File.foreach('data_large.txt') do |line|
line_type, *fields = line.chomp.split(',')
Copy link
Collaborator

Choose a reason for hiding this comment

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

тут на самом деле есть вариант сэкономить на аллокациях - использовать split with block

или скажем идти по строке посимвольно и записывать подстроки в 4-5 заранее заготовленных строковых переменных (сколько там полей)

но это экономия на спичках уже в данном случае

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants