Работа с документами, расчёт нагрузок на аллоеатор

This commit is contained in:
2026-04-27 07:26:26 +05:00
parent 82f71c5643
commit 71b362a8d4
+193 -5
View File
@@ -1,5 +1,8 @@
# DAIRY.md
**Дисклеймер:**
Дневник не является технической документацией. Его основная цель показать как работает больная фантазия автора проекта. Вся документация по проекту описана в файлах README.md соответствующих модуей и самого проекта.
# 23.04.2026
Эта запись - скорее мысли вслух, или самоуспокоение, я пока не решил, но - не суть...
Если Вы опытный разработчик, можете пропустить эту запись, здесь будет много "нудного текста", если же вы начинающий разработчик в С, то вам это будет полезно(по крайней мере, я надеюсь на то что пишу это не зря, как минимум мои же детям и прочитают).
@@ -68,7 +71,7 @@ static const FXGrade grades[] = {
{ 4096, 200 }, { 0x10000, 4 }
};
```
Что нам даёт такая переменная: мы можем спокойно проинициализировать глобальный пул памяти, но в таком исполнении нам придётся высчитывать количесво элементов в переменой через **sizeof()**, можно ли обойтись без этого, можно - есть прекрасная вещь ноль-терминант, дополняем переменную:
Что нам даёт такая переменная: мы можем спокойно проинициализировать глобальный пул памяти, но в таком исполнении нам придётся высчитывать количесво элементов в переменой через **sizeof()**, можно ли обойтись без этого, можно - есть прекрасная вещь ноль-терминант, которая не то что позволяет обойтись без **sizeof()**, но и настраивать непосредственно перед инициализацией, дополняем переменную:
```C
// neurox/fxalloc/src/FXAlloc.c
// В этой переменной настраиваем градации и предположительное количество блоков
@@ -79,6 +82,11 @@ static const FXGrade grades[] = {
{ 4096, 200 }, { 0x10000, 4 },
{ 0 } // Ноль-терминант
};
int main(int argc, char* argv[]) {
fxalloc_init(grades);
// что-нибудь шкодим
return 0;
}
```
Теперь при обходе переменной grades в цикле мы можем быть уверены что дальше чем нужно - не уйдём и вполне себе спокойно можем использовать цикл **for**:
```C
@@ -156,16 +164,18 @@ typedef struct FXGradedMemoryPool {
mword_t free;
} FXMemoryPoolGrade;
```
**Таракан отвечавший за раздел свалил в неизвестном направлении...**
# 26.04.2026
Чтож, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в рассчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что стоит обратить внимание:
Что ж, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в рассчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что в первую очередь стоит обратить внимание:
* Железо
* Ресурсы потребляемые ОС
* Ресурсы потребляемые сторонними сервисами при их наличии(Web-сервер, почтовый сервер и т.д.)
* Минимальный необходимый запас прочности
* Минимальный необходимый запас прочности(как правило вкладывается 20-30% из-за деградации и вариативности нагрузок как отдельно взятого элемента, так и системы в целом)
## Железо:
Отсчётная точка любого инфраструктурного проекта
|Параметр|Значение|
|-------:|:-------|
|**OS:**|Ubuntu Server 24.04|
@@ -174,5 +184,183 @@ typedef struct FXGradedMemoryPool {
|**ROM:**|noname 256 GB SSD|
|**WiFi:**|2.4 GHz, прямая видимость до 6 м (~32 Mbit/s)|
1. Инфраструктура:
|Параметр|Значение|Нагрузка на одного клиента||
Все параметры кроме Wi-Fi устанавливаютя заводом изготовителем и имеют ±заявленные(скорее — минус, но на данном этапе считаем заявленный максимум) характеристики, производим замеры:
**Ключевые параметры оборудования(маршрутизатор) TP-Link Archer-A8(заявленые изготовителем):**
| Параметр | Значение |
|----------------------------:|:----------------------------------------------------------------|
| Тип оборудования | Маршрутизатор (роутер) / точка доступа |
| Стандарты | WiFi 802.11a/b/g/n (2.4 ГГц), 802.11ac (5 ГГц) |
| Частотные диапазоны | Dual‑Band: 2.4 ГГц и 5 ГГц |
| Скорость Wi‑Fi (макс.)| 2.4 ГГц: до 600 Мбит/с — 5 ГГц: до 1 300 Мбит/с Суммарно: AC1900|
| Порты | 1 × RJ‑45 WAN (1 Гбит/с) — 4 × RJ45 LAN (1 Гбит/с) |
| Технологии | MUMIMO (3×3), Beamforming, Smart Connect (автовыбор диапазона), Airtime Fairness|
| Поддержка VPN | PPPoE, PPTP, L2TP |
| QoS (приоритизация трафика) | Поддерживается (включая WMM) |
| Процессор | 1.2 ГГц (1 ядро) |
**Оборудование сервера:** TP-Link Wi-Fi свисток, всё что изветно - 2.4 GHz
Показатели **iperf3** в режиме **\*спокойной** сети(03:00, ночь):
**[ 5]local 192.168.0.104 port 34696 connected to 192.168.0.101 port 5201**
|[ ID]| Interval | Transfer | Bitrate |Retr| Cwnd |
|:---:|:---------------|:-----------:|:--------------:|:--:|:----------:|
|[ 5]| 0.00-1.00 sec | 9.25 MBytes | 77.5 Mbits/sec | 0 | 329 KBytes |
|[ 5]| 1.00-2.00 sec | 8.62 MBytes | 72.4 Mbits/sec | 0 | 421 KBytes |
|[ 5]| 2.00-3.00 sec | 8.88 MBytes | 74.4 Mbits/sec | 0 | 450 KBytes |
|[ 5]| 3.00-4.00 sec | 7.75 MBytes | 65.0 Mbits/sec | 0 | 539 KBytes |
|[ 5]| 4.00-5.00 sec | 8.25 MBytes | 69.2 Mbits/sec | 0 | 632 KBytes |
|[ 5]| 5.00-6.00 sec | 7.00 MBytes | 58.8 Mbits/sec | 0 | 735 KBytes |
|[ 5]| 6.00-7.00 sec | 8.38 MBytes | 70.2 Mbits/sec | 0 | 735 KBytes |
|[ 5]| 7.00-8.00 sec | 8.25 MBytes | 69.2 Mbits/sec | 0 | 735 KBytes |
|[ 5]| 8.00-9.00 sec | 7.00 MBytes | 58.7 Mbits/sec | 0 | 735 KBytes |
|[ 5]| 9.00-10.00 sec | 8.25 MBytes | 69.2 Mbits/sec | 0 | 735 KBytes |
|[ ID]| Interval | Transfer | Bitrate |Retr| Side |
|:---:|:----------------|:-----------:|:--------------:|:--:|:----------|
|[ 5]| 0.00-10.00 sec | 81.6 MBytes | 68.5 Mbits/sec | 0 | sender |
|[ 5]| 0.00-10.01 sec | 78.9 MBytes | 66.1 Mbits/sec | | receiver |
Весьма оптимистично, но, это спокойная сеть(ночью без помех), днём показатели будут существенно проседать и итоговая возможность сети будет существенно проседать, однако это — мелочь, что касается стабильности системы - мы должны исходить из максимальных возможностей сети и вкладывать в архитектуру на этом этапе именно это значение, берём максимальное из таблицы(77.5 МБит/с) и добавляем к нему о-о-очень оптимистичные 10%, итог(85.3 МБит/с), TP-Link свисток с 7-милетним стажем показывает топовые результаты не то чтобы для своего возраста, а вообще в принципе(порадовал старика, видимо он "поднатаскался" за 7 лет, как говорят - "Опыт не пропьёшь!"). В нашем случае сеть это узкое место(домашний Wi-Fi за средненьким роутером).
Для чего это: показывает предел сетевого стека, то есть пропускную возможность сети.
В чём особенность в проектировании алокатора(с моей колокольни), это самый основной элемент серверной части. С неправильной архитектурой памяти сервер — не сервер а "кукурузник", а нам нужна 3-я космическая скорость. По это му нам необходимо расчитать по максимуму частот и по минимуму памяти, такой подход считаю наиболее верным, конечно можно зацепиться за тактовую частоту этой самой памяти, но, на данном этапе — это лишнее.
Продолжим рассматривать наши железяки со всех сторон и как они определяют требования к аллокатору, начертаем небольшую таблицу опираясь на максимальные возможности сначала каждого компонента в отдельности, найдём слабое звено и посмотрим что на самом деле выйдет. Чего стоит ожидать от такого подхода — максимальные требования к алокатору по скорости, максимальные требования по локализации пулов, и самое ключевое — потолок для выделения блока в плане времени. С чего начнём расчёт Wi-Fi и RAM(ёмкость), почему именно так — способности процессора переварить нагрузку будет зависить от сложности самой функции выделения памяти, в идеале бы уложиться в один атомарный своп, но,— это не реально, скорость света недостижима, по крайней мере для меня — точно(разве что только для тараканов в моей голове это норма). Как мы будем считать: У нас уже есть заголовочные файлы модуля **XOGame** с вот такой вот архитектурой:
```C
/**
* @brief Клетка игры крестики-нолики
*/
typedef struct XOCell {
/// Координата X
int8_t x: 3;
/// Координата Y
int8_t y: 3;
/// Сторона
int8_t side: 2;
} XOCell;
/**
* @brief Игровое поле крестики-нолики
*/
struct XOGame {
/// Идентификатор игры
const size_t id;
/// Сделать ход.
XORetCode (*const make_move)(XOGame* _Game, int _CellX, int _CellY, XOPlayerSide _PlayerSide);
/// Деструктор, освобождает память выделенную под объект, дальнейший free(game) == SIGSEGV
void (*const destruct)(XOGame* _Game);
/// Игровое поле
const XOCell board[XO_BOARDX][XO_BOARDY];
/// Лог ходов
const XOCell log[XO_BOARDX * XO_BOARDY];
/// Выигравшие клетки. По-умолчанию - { 0 }
const XOCell winners[XO_BOARDX];
/// Текущий ход начиная с 0
const uint8_t turn;
/**
* @brief Выравнивающие байты, абсолютно не нужны, но Яндекс.АлисаAI настояла - { 0 }
* Если вы решите изменить тип полей в XOCell, то просто удалите это поле, модуль не сломается ;)
*/
uint8_t padding[2];
};
```
Что мы можем почерпнуть из такой архитектуры:
* ожидаемая вариативность размеров пакетов крайне низкая.
* даже если в пакет напихивать все ±значимые для сети поля - board, log, winners и turn — это всего $9 + 9 + 3 + 1 = 22$ байта + метаданные(размер + идентификатор пакета) ещё $2 + 1$ байта, итого имее всего 25 байт.
* большие пакеты будут связаны исключительно с инициализацией шифрования и аутентификацией.
Что мы можем ещё расчитать — скорость жмаканья юзерами-абьюзерами по полям, даже самый продвинутый guiuser врядли нажмакает чаще чем 0.5 секунды. Тут даже и вычислений не требуется чтобы понять что, оказывается, Wi-Fi тут далеко не самое узкое место, ну или вернее — сопоставимо узкое, к примеру с CPU или RAM. Берём кулюкулятор, пренебрегаем большими пакетами в виду их большой редкости, выбираем режим "Инженер"(мы же тут — архитектуру планируем) и начинаем считать:
| Параметр | Формула | Лимит guiuser-ов | Описание |
|:--------:|:------------------------:|:----------------:|:------------------------------------|
|**Wi-Fi:**|$⌈\frac{85,3×10^6}{25×8}⌉$| 426 500 | Не то что бы много, но дох-х-ходчиво|
Очешуеваем от того сколько абьюзеров одновременно никогда не увидит наш сервер и понимаем что старый свисток — далеко не самое слабое место нашего проекта, почему, потому что даже топовое железо не всегда тянет столько линий, не говоря уже о том что сама ось(Операционная система) не выделит нам столько подключений, хотя:
```console
guiuser@felexdev:~$ cat /proc/sys/fs/file-max
9223372036854775807 < Максимальное количество файловых дескрипторов всего
```
Подбираем челюсть, смотрим дальше:
```Console
guiuser@felexdev:~$ ulimit -n
open files (-n) 1024 < Максимальное количество файловых дескрипторов на процесс
```
Ничего, лично я,— "пингвин со стажем", можем поднять и до $65 535$ на процесс, однако на данном этапе нам нет такой необходимости.
# 27.04.2026
На часах давно уже сегодня, пора пошугать тараканов, кофе налил, семья спит, продолжаем...
О сути расчётов по вафляю, что они нам дают 426к+ клиентов - весьма сомнительное число, но почему-бы на вложить его в основу, а вот почему: не учтены заголовки TCP/IP, а это не много ни мало - минимум 40 байт, соответственно - количество полезной нагрузки на сетевой трафик из наших расчётов будет 25:40, пересчитываем размер пакета: `$\frac{85,3×10^6}{65×8}≈163961$` guiuser-ов, вот, это уже ближе к теме.
**Предварительный итог профилирования сети:** расчётное время между аллокациями оценено ~2,5 мкс, что критически мало, это, конечно не самое худшее что может быть, но, и не так уж красиво, среднее время на выделение оптимизированными аллокаторами ~10-15 мкс, есть куда расти.
## RAM
Примерные цифры по сети мы получили, пора заняться RAM. Очередной раз обращаемся к нашему любимому критику-статисту Алисе(она "очень могёт" в статистике), идём препрофилировать память, на глаз прикиываем примерное минимальное потребление памяти на одного абьюзера:
**Таблица потребления памяти с выравниванием до 8 байт**
| Компонент | Размер (байт) |Выравнивание|Итого с padding| Описание |
|:--------------------------|:-------------:|:----------:|:-------------:|:-----------------------------------------|
|**Сокетные структуры ядра**| | | | |
|epoll_event | 12 | 8 → 16 | 16 | Структура события epoll |
|file descriptor | 8 | 8 → 8 | 8 | Указатель на файловый дескриптор |
|socket struct | 200 | 8 → 200 | 200 | Базовая структура сокета |
|**Метаданные соединения** | | | | |
|Указатель на игру | 8 | 8 → 8 | 8 | Указатель на структуру игры |
|XOR-ключи | 16 | 8 → 16 | 16 | 2 ключа шифрования |
|Состояние игры | 64 | 8 → 64 | 64 | Данные состояния игры |
|Метаданные блока памяти | 16 | 8 → 16 | 16 | Дополнительные данные управления памятью |
|**Шифрование** | | | | |
|Указатель на шифровщик | 8 | 8 → 8 | 8 | Указатель на модуль шифрования |
|**Дополнительные указатели**| | | | |
|Указатель на клиента | 8 | 8 → 8 | 8 | Указатель на структуру клиента |
|Резерв | 8 | 8 → 8 | 8 | Дополнительный резерв |
|Указатель на пул | | | | |
|Указатель на клиента в пуле| 16 | 8 → 16 | 16 | Хранение клиента в пуле |
**Итого с выравниванием** | | | **360** | **Суммарное потребление** |
**Расчет общей производительности**
| Параметр | Значение | Описание
|:----------------------|:-------------:|:--------------------------------------|
| Доступная RAM 6.1 GB | | Свободная память для соединений |
| Память на соединение | 360 байт | Потребление на одно соединение |
| Теоретический максимум| ~16.9 млн | Максимальное количество соединений |
| Реальный максимум | ~12.7 млн |С учетом системных накладных расходов |
Реальный максимум ~12.7 млн гуюзеров, не дурно, собственно это и ожидалось. Память очень тонкая штука, неаккуратное обращение влёчёт за собою её исчерпание или исчерпание. Что при утечках, что при сегментации рискует обернуться большой бедой для прода =D
### Промежуточный итог над принципом рабботы с памятью(вставлено как есть):
**Итоги обсуждения архитектуры**
* **Основные решения приняты:**
* Разделение потоков ввода/вывода
* Циклическое управление памятью
* Минимизация аллокаций вне TLS
* Классификация пакетов по размеру
* **Следующий шаг:**
* Проработка деталей реализации
* Документирование принятых решений
* Подготовка к имплементации
* **Важные моменты для дальнейшей работы:**
* Детальное проектирование системы памяти
* Реализация механизма кругового буфера
* Оптимизация работы с TLS
* Тестирование производительности
* **Рекомендации перед сном:**
* Зафиксировать текущие решения в документации
* Подготовить чек-лист для реализации
* Определить приоритетные задачи на следующий этап
* **План на завтра:**
* Проработка схемы памяти
* Проектирование системы буферов
* Оценка необходимых изменений в текущей архитектуре
* Начало подготовки документации по новой архитектуре
Удачи с отдыхом!
Завтра будет новый день и новые возможности для оптимизации архитектуры. Главное — хорошо отдохнуть и подойти к работе с новыми силами.