## Оперативна пам'ять з точки зору програміста Богдюк Микола (Java, C++, Rust, C#) [@mbohdiuk](https://www.instagram.com/mbohdiuk/) https://bogdiuk.github.io/slides-memory/
## Коли застосовно? * Збереження - PNG, MP4, EXE * Ефективна передача даних (між процесами, компами) - RPC, HTTP/2 * Є час відтестувати і зробити переглядач * Швидкість * Відлагоджування (debugging) ## Коли ні? * Оптимізатор змінює розміщення (layout) * Зручність важливіша за швидкість - JSON/YAML, CONF, HTTP/1
## Нащо? * Розуміти, що відбувається "під капотом" * Знаходити баги і повільний код * Одразу писати оптимальніше * "Premature optimization"
## Struct ```c++ struct S1 { int32_t a; int32_t b; int32_t c; int32_t d; } ```
## Struct ```c++ struct S1 { int32_t a; int32_t b; int32_t c; int32_t d; } ``` * sizeof(S1) = 16
|||||||||||||||||| |----|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| | |0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| |0x00|a|a|a|a|b|b|b|b|c|c|c|c|d|d|d|d|
## Struct 1-4-4-4 ```c++ struct S2 { int8_t a; int32_t b; int32_t c; int32_t d; } ```
## Struct 1-4-4-4 ```c++ struct S2 { int8_t a; int32_t b; int32_t c; int32_t d; } ``` * sizeof(S2) = 16
|||||||||||||||||| |----|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| | |0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| |0x00|a|-|-|-|b|b|b|b|c|c|c|c|d|d|d|d|
## Struct 1-2-4-4 ```c++ struct S3 { int8_t a; int16_t b; int32_t c; int32_t d; } ```
## Struct 1-2-4-4 ```c++ struct S3 { int8_t a; int16_t b; int32_t c; int32_t d; } ``` * sizeof(S3) = 12
|||||||||||||||||| |----|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| | |0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| |0x00|a|-|b|b|c|c|c|c|d|d|d|d|
## Struct 1-2-4-4-8 ```c++ struct S4 { int8_t a; int16_t b; int32_t c; int32_t d; int64_t e; } ```
## Struct 1-2-4-4-8 ```c++ struct S4 { int8_t a; int16_t b; int32_t c; int32_t d; int64_t e; } ``` * sizeof(S4) = 24
|||||||||||||||||| |----|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| | |0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| |0x00|a|-|b|b|c|c|c|c|d|d|d|d|-|-|-|-| |0x10|e|e|e|e|e|e|e|e|
## Struct pack(1-2-4-4-8) ```c++ #pragma pack(1) // [repr(packed)] in Rust struct S5 { int8_t a; int16_t b; int32_t c; int32_t d; int64_t e; // ! unaligned access } ```
## Struct pack(1-2-4-4-8) ```c++ #pragma pack(1) // [repr(packed)] in Rust struct S5 { int8_t a; int16_t b; int32_t c; int32_t d; int64_t e; // ! unaligned access } ``` * sizeof(S5) = 19
|||||||||||||||||| |----|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-| | |0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F| |0x00|a|b|b|c|c|c|c|d|d|d|d|e|e|e|e|e| |0x10|e|e|e| |
## Вирівнювання * `alignas` * `alignof` * Rust: `#[repr(C, packed)]` * "Undefined Behaviour", швидкість * RISC (наприклад, ARM)* * Масиви
## Endianness * Little/Big Endian - x86 * (short) 0x1234
5678
||||||||||| |-|-|-|-|-|-|-|-|-|-| |Little Endian|||||Big Endian| ||0|1|2|3||0|1|2|3| ||78|56|34|12||12|34|56|78|
## Бінарні файли * Приклади: PNG, CRLI* * Magic (`%PNG`) * Заголовок (header) - версія, метадані, зсуви і розміри структур * Самі підструктури
# Демо: GIF parser
## Стек і купа * Структури даних * Стек - FILO * Купа (Heap) - дерево * `parent.key ≥ child[*].key` * Області пам'яті * Звернення до стеку швидше, але він маленький * Безпека: легше зіпсувати дані*
## Організація стеку * З більших адрес до менших, історично: * `|heap> ...
## Внутрішні блоки ```c++ int main(int argc, char* args[]) { char c = 'a'; for (int i = 1; i < argc; i++) { // if () int block = strlen(args[i]); printf("block %p\n", &block); } } ```

## Calling convention * x86: `cdecl`, `stdcall`, `vectorcall`, `thiscall`, `fastcall` * amd64: `Microsoft x64`, `System V` * Визначають: * Хто видаляє аргументи зі стеку? * Починаємо з 1го аргументу чи з останнього? * Використані регістри? * Обережно зі сторонніми бібліотеками*
## Microsoft x64 * Чистить той, хто викликав * Для порівняння, `stdcall` функції чистять аргументи самі => компілятор не дозволить `printf(format, ...)` * Вирівнювання 16B, резерв 32B ("shadow") * Перші 4 аргументи: `RCX/XMM0`, `RDX/XMM1`, `R8/XMM2`, `R9/XMM3` * Подальші - RTL: `push argN`, ..., `push arg0`
## Debug (MS VC++): * **0xCCCCCCCC**: не ініціалізований стек (одночасно і breakpoint) * **0xCDCDCDCD**: не ініціалізована купа* * **0xDDDDDDDD**: звільнена купа * 0xABABABAB: HeapAlloc: "захисні байти" навколо виділених в купі даних * 0xFDFDFDFD: "захисні байти" навколо виділених в купі даних * 0xBAADF00D: LocalAlloc(LMEM_FIXED): неініціалізовані, але виділені в купі * 0xFEEEFEEE: після HeapFree()
## Hexspeak: * 0x000FF1CE: "Office", GUID of some MS Office parts * 0x8BADF00D: "Ate Bad Food" (iOS, app takes too long to launch) * 0xABADCAFE: "A Bad Cafe" * 0xBADCAB1E: "Bad Cable" * 0xBEEFCACE: "Beef Cake" * 0xC00010FF: "Cool Off" (iOS, when app was killed due to a thermal event)
## Розмір стеку * Основний потік: * `link /STACK:reserve[,commit]` = 1M/4k * `ld -stack_size 0x800000` = 8M * Додатковий потік: ```c++ // Win HANDLE CreateThread(..., SIZE_T dwStackSize, ...); // Linux int pthread_attr_setstack(..., size_t stacksize); ```
## Швидкодія * Регістри * Кеш, приклад для 4800H: * L1 - в ядрі процесора, `64KB*cores` * L2 - як де, `512KB*cores` * L3 - спільний, `12MB`
## Віртуальна пам'ять * Безпека, ізоляція * Сторінки по 4kB (рідко +) * read-write-exec * Адресний простір - 2GB/128TB на процес * Memory-mapped files * Page Fault блокує потік


## Контекст потоку ## (Thread context) * Регістри * Адреса стеку (хоча інші потоки мають доступ) * Службова інформація ОС (branches, exceptions) * Окремий контекст для ядра (без SSE)
## Інші пам'яті * GPU * кеш HDD, SSD
## Task Manager (система) * **Committed** - cумарно зайнята віртуальна пам'ять всіх процесів. * **Cached** - пасивно використовується ОС. Наприклад, кеш прочитаних файлів * **Paged pool** - переміщено в swap (pagefile) * **Non-paged pool** - скільки пам'яті неможливо перенести в swap
## Task Manager (процес) * **Memory - Private Working Set** - приватна RAM, не доступна іншим процесам* * **Memory - Working Set** - приватна RAM + спільні DLL * **Memory - Peak Working Set** - максимум "Working Set" * **Memory - Commit Size** - об'єм зарезервованої чи виданої віртуальної пам'яті * **Page Faults** - скільки раз системі довелось вантажити дані зі swap'у
## Будова EXE/DLL/ELF * Код (.text) * Константи (.rdata) * Глобальні/статичні змінні (.data, .bss) * Інші (.reloc, .rsrc, .pdata, .idata, ...)
## Пам'ять процесу * Регістри`[2*thread_count]` * Стек (stack)`[2*thread_count]` * Секції з EXE * Найкраще значення за замовчуванням - `0` * Купа (heap)
## Купа (heap) ```c++ [1-3|4-6|4,7|4,8|4,9|1-9] // колись: Point* point = new Point(x, y); // requires "delete array"; Point* points = new Point[50]; // requires "delete[] array"; // C++11: RAII - явний власник std::unique_ptr
point = std::make_unique
(x, y); auto point = std::make_unique
(x, y); std::shared_ptr
point = std::make_shared
(x, y); Point* borrowed = point.get(); std::weak_ptr
weak = point; ```
## Купа (heap) * `std::vector
` * `std::map
` * `std::unordered_map
` * `std::string` * `std::wstring`
## Купа (heap): `std::vector
` 


## Протікання (Memory leaks) * Забули `delete` * Циклічні посилання* * Дійсно і для мов, що мають збірку сміття: Java, C# * Приклад 1: внутрішні класи * Приклад 2: кнопка, що змінює закриті документи*
## Оптимізації компілятора * inline * регістри * вирівнювання * зміна порядку операцій (крім `atomic`) * купа - повільніше (див. Java, C#)
## Додатково * Випадкова зміна біта: ECC, контролюючі обчислення * Глобальні змінні: порядок ініціалізації та знищення
## https://bogdiuk.github.io/slides-memory/ [@mbohdiuk](https://www.instagram.com/mbohdiuk/)