today's studying
This commit is contained in:
parent
171c8d5489
commit
71db955e55
10 changed files with 446 additions and 2 deletions
7
10-linux/00-utils/todo.md
Normal file
7
10-linux/00-utils/todo.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
https://github.com/ducaale/xh
|
||||
https://github.com/Orange-OpenSource/hurl
|
||||
https://github.com/rgwood/systemctl-tui
|
||||
https://github.com/rcoh/angle-grinder
|
||||
https://github.com/stalwartlabs/stalwart
|
||||
https://github.com/FedericoBruzzone/tgt
|
||||
https://github.com/tramhao/termusic
|
||||
|
|
@ -85,4 +85,62 @@
|
|||
| **TLB** | Кэш адресов памяти | Хаотичные прыжки по памяти (Pointer Chasing) забивают не только кэш данных, но и TLB. |
|
||||
|
||||
**Главный вывод для Rust-разработчика:**
|
||||
Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе.
|
||||
Думай о памяти как о **ленте**, которую нужно читать подряд. Любой указатель (`Box`, ссылка, узел графа) — это разрыв ленты, который стоит дорого. Структуры данных, которые минимизируют эти разрывы (как B-Tree или `Vec`), всегда будут побеждать на современном железе.
|
||||
|
||||
|
||||
Это отличный вопрос. Твоя ментальная модель ("загружается в L1, потом копируется в L2") немного **перевернута**. В реальности все происходит ровно наоборот по направлению, но гораздо интереснее в деталях.
|
||||
|
||||
Давай разберем анатомию клонирования (например, строки или вектора) на уровне железа. Представим, что мы делаем `let b = a.clone();`.
|
||||
|
||||
### 1. Иерархия: Путь еды (данных)
|
||||
Сначала важно понять: процессор (ядро) **слеповат**. Он может работать только с тем, что лежит у него "в руках" — в **регистрах** (крошечная память прямо внутри ядра).
|
||||
|
||||
* **RAM (Оперативная память):** Это "склад" в соседнем здании. Огромный, но медленный.
|
||||
* **L3 Кэш:** Это "разгрузочная зона" на этаже. Общая для всех ядер.
|
||||
* **L2 Кэш:** Это "холодильник" на кухне конкретного ядра.
|
||||
* **L1 Кэш:** Это "разделочная доска" прямо перед руками повара. Самая быстрая.
|
||||
|
||||
### 2. Процесс клонирования (пошагово)
|
||||
|
||||
Когда ты вызываешь `clone()`, происходит операция **Read (чтение источника)** + **Write (запись копии)**.
|
||||
|
||||
#### Этап А: Чтение (Read) — "Доставка на кухню"
|
||||
Процессор говорит: "Мне нужны данные по адресу `A` (источник)".
|
||||
1. **Поиск:** Он ищет их в L1. Если нет — в L2. Если нет — в L3. Если нет — идет в RAM.
|
||||
2. **Cache Line Fill:** Данные никогда не ходят по одному байту. Они ходят "пачками" по **64 байта** (Cache Line). Даже если тебе нужен 1 байт, из RAM вытащится вся линия (64 байта) и по цепочке RAM -> L3 -> L2 -> L1 приедет к ядру.
|
||||
3. **Регистры:** Наконец, данные (или их часть) попадают в регистры процессора (например, в 256-битные регистры AVX, если копируем быстро).
|
||||
|
||||
#### Этап Б: Запись (Write) — "Создание клона"
|
||||
Теперь процессор должен записать эти данные по новому адресу `B` (куда мы клонируем).
|
||||
1. **Allocation:** Сначала Rust (аллокатор) находит свободное место в памяти для `B`.
|
||||
2. **Store (Запись):** Процессор пишет данные из регистров **в L1 кэш** по адресу `B`.
|
||||
* *Важный нюанс:* Он **не пишет** сразу в RAM! Это было бы безумно медленно.
|
||||
* Он пишет в L1 и помечает эту строку как **Dirty** (Грязная). Это значит: "Эта версия новее, чем то, что лежит в RAM. Не потеряй".
|
||||
|
||||
#### Этап В: Вытеснение (Eviction) — "Уборка"
|
||||
Вот тут рушится твое предположение про "копируется в L2". Данные попадают в L2 не потому что они "нужны там для работы", а потому что в L1 **закончилось место**.
|
||||
1. Когда L1 переполняется, старые или "грязные" данные вытесняются вниз — в L2.
|
||||
2. Когда L2 переполняется — в L3.
|
||||
3. И только когда L3 переполняется или процессор явно командует (сброс кэша), данные улетают в RAM.
|
||||
|
||||
***
|
||||
|
||||
### 3. "Кишечные" оптимизации (SIMD и Prefetching)
|
||||
|
||||
Если бы процессор копировал байт за байтом через обычные регистры (по 64 бита), это было бы медленно. В Rust (и в `libc` memcpy) клонирование оптимизировано жестко:
|
||||
|
||||
1. **SIMD (Single Instruction, Multiple Data):**
|
||||
Процессор использует широкие регистры (AVX/SSE). За один такт он может "вдохнуть" 32 или 64 байта данных источника в регистр и "выдохнуть" их в новое место в L1. Это как переносить воду ведрами, а не ложками.
|
||||
|
||||
2. **Non-Temporal Stores (Обход кэша):**
|
||||
Это хардкорная тема. Если ты клонируешь **гигантский** массив (больше размера кэша, например 100 МБ), процессор понимает: *"Если я сейчас запихну эти 100 МБ в L1, я вытесню оттуда все полезные данные моей программы, а этот массив мне сейчас не нужен для вычислений, я просто копирую"*.
|
||||
В этом случае используются специальные инструкции (например, `movntdq` в x86), которые пишут данные **мимо L1 и L2**, сразу в буфер записи (Write Combining Buffer) и оттуда в RAM. Это называется "не засорять кэш" (cache pollution).
|
||||
|
||||
### Итог для Rust-разработчика
|
||||
|
||||
Когда ты делаешь `clone()`:
|
||||
1. **CPU:** Читает кусок памяти `A` в регистры (протаскивая через все кэши вверх).
|
||||
2. **CPU:** Пишет из регистров в L1 по адресу `B`.
|
||||
3. **Кэш-контроллер:** Асинхронно, в фоновом режиме, когда-нибудь потом спускает эти данные из L1 вниз до RAM.
|
||||
|
||||
Взаимодействие с оперативной памятью **есть**, но оно отложено во времени (Write-Back), чтобы твой код не тормозил, ожидая записи в медленную RAM.
|
||||
Loading…
Add table
Add a link
Reference in a new issue