Разработка аллокатора

This commit is contained in:
2026-04-28 22:21:51 +05:00
parent 71b362a8d4
commit e5cbce36ae
2 changed files with 268 additions and 39 deletions
+55
View File
@@ -0,0 +1,55 @@
# 28.04.2026
## Промпт:
> Алиса, оцени пожалуйста юмор и сделай расчёты:
> Кастуем заклинание "Алиса", скармливаем ей полезную нагрузку пакета 24 байта, название железяки Intel Core i5-3470 и недожелезяки RAM: 8 GB, спрашиваем сколько розеток переварит один поток epoll с последующим их копированием в очередь без оглядки на nofile, жги по-полной в режиме телепорта битов!
**Вставлено без редактуры**
## Веха: Магия и биты. YandexGPT 5.1 Pro(АлисаAI)©
Эта веха посвящена моменту, когда мы впервые успешно:
> «Кастовали заклинание „Алиса“, скармливали ей полезную нагрузку пакета 24 байта, название железяки Intel Core i5‑3470 и недожелезяки RAM: 8 GB, спрашивали, сколько розеток переварит один поток epoll с последующим их копированием в очередь без оглядки на nofile, жгли по‑полной в режиме телепорта битов!»
### Результаты эксперимента
**Теоретический максимум:** 16 777 216 «розеток» (ограничено 2 ГБ памяти под `epoll` + очередь).
**Практический максимум:** 21 333 333 дескрипторов/сек (ограничено процессором).
**Пропускная способность:** ~488 МБ/сек.
### Ключевые открытия
* **Заклинание «Алиса»** обладает стабилизирующим эффектом: охлаждает CPU аурой квантовых флуктуаций.
* **«Недожелезяка» RAM** демонстрирует неожиданную эластичность при телепортации битов.
* **Режим телепорта битов** обеспечивает нулевую задержку передачи данных (в пределах текущей шутки).
* **Очередь** ведёт себя как квантовая струна — растягивается без потерь.
### Технические детали (для скептиков)
Под капотом шутки — реальные расчёты:
1. **Память:**
$$
N_{\text{макс}} = \frac{2\ \text{ГБ}}{96\ \text{байт} + 32\ \text{байт}} \approx 16\,777\,216 \text{ дескрипторов}
$$
2. **Производительность:**
$$
N_{\text{обр/сек}} = \frac{3{,}2 \times 10^9\ \text{тактов/сек}}{150\ \text{тактов/дес-р}} \approx 21\,333\,333 \text{ дескрипторов/сек}
$$
3. **Пропускная способность:**
$$
\text{ПС} = 21\,333\,333 \times 24\ \text{байт} \approx 488\ \text{МБ/сек}
$$
### Важное примечание
* `epoll` по‑прежнему не работает с розетками (к сожалению).
* Режим телепорта битов пока не включён в ядро Linux (но мы работаем над патчем).
* Лимит `nofile` в реальной системе нужно настраивать через `ulimit` или `systemd`.
* Все «заклинания» работают только в тестовом режиме веселья.
> **Вывод:** даже в мире строгих вычислений есть место магии, абсурду и улыбке. Пусть этот эксперимент напоминает нам, что программирование — это ещё и творчество!
+213 -39
View File
@@ -167,7 +167,7 @@ typedef struct FXGradedMemoryPool {
**Таракан отвечавший за раздел свалил в неизвестном направлении...**
# 26.04.2026
Что ж, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в рассчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что в первую очередь стоит обратить внимание:
Что ж, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в расчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что в первую очередь стоит обратить внимание:
* Железо
* Ресурсы потребляемые ОС
* Ресурсы потребляемые сторонними сервисами при их наличии(Web-сервер, почтовый сервер и т.д.)
@@ -290,19 +290,17 @@ guiuser@felexdev:~$ cat /proc/sys/fs/file-max
guiuser@felexdev:~$ ulimit -n
open files (-n) 1024 < Максимальное количество файловых дескрипторов на процесс
```
Ничего, лично я,— "пингвин со стажем", можем поднять и до $65 535$ на процесс, однако на данном этапе нам нет такой необходимости.
Ничего, лично я,— "пингвин со стажем", можем поднять и до $200 000$ на процесс, однако на данном этапе нам нет такой необходимости.
# 27.04.2026
На часах давно уже сегодня, пора пошугать тараканов, кофе налил, семья спит, продолжаем...
О сути расчётов по вафляю, что они нам дают 426к+ клиентов - весьма сомнительное число, но почему-бы на вложить его в основу, а вот почему: не учтены заголовки TCP/IP, а это не много ни мало - минимум 40 байт, соответственно - количество полезной нагрузки на сетевой трафик из наших расчётов будет 25:40, пересчитываем размер пакета: `$\frac{85,3×10^6}{65×8}≈163961$` guiuser-ов, вот, это уже ближе к теме.
**Предварительный итог профилирования сети:** расчётное время между аллокациями оценено ~2,5 мкс, что критически мало, это, конечно не самое худшее что может быть, но, и не так уж красиво, среднее время на выделение оптимизированными аллокаторами ~10-15 мкс, есть куда расти.
О сути расчётов по вафляю, что они нам дают 426к+ клиентов - весьма сомнительное число, но почему-бы не вложить его в основу, а вот почему: не учтены заголовки TCP/IP, а это не много ни мало - минимум 40 байт, соответственно - количество полезной нагрузки на сетевой трафик из наших расчётов будет 25:40, пересчитываем размер пакета: $\frac{85,3×10^6}{65×8}≈163961$ guiuser, вот, это уже ближе к теме.
## RAM
Примерные цифры по сети мы получили, пора заняться RAM. Очередной раз обращаемся к нашему любимому критику-статисту Алисе(она "очень могёт" в статистике), идём препрофилировать память, на глаз прикиываем примерное минимальное потребление памяти на одного абьюзера:
Примерные цифры по сети мы получили, пора заняться RAM. Очередной раз обращаемся к нашему любимому критику-статисту Алисе(она "очень могёт" в статистике), идём препрофилировать память, на глаз прикиываем примерное минимальное потребление памяти на одного абьюзера:
**Таблица потребления памяти с выравниванием до 8 байт**
| Компонент | Размер (байт) |Выравнивание|Итого с padding| Описание |
@@ -311,7 +309,7 @@ open files (-n) 1024 < Максималь
|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 | Данные состояния игры |
@@ -321,7 +319,7 @@ open files (-n) 1024 < Максималь
|**Дополнительные указатели**| | | | |
|Указатель на клиента | 8 | 8 → 8 | 8 | Указатель на структуру клиента |
|Резерв | 8 | 8 → 8 | 8 | Дополнительный резерв |
|Указатель на пул | | | | |
|**Указатель на пул** | | | | |
|Указатель на клиента в пуле| 16 | 8 → 16 | 16 | Хранение клиента в пуле |
**Итого с выравниванием** | | | **360** | **Суммарное потребление** |
@@ -329,38 +327,214 @@ open files (-n) 1024 < Максималь
| Параметр | Значение | Описание
|:----------------------|:-------------:|:--------------------------------------|
| Доступная RAM 6.1 GB | | Свободная память для соединений |
| Память на соединение | 360 байт | Потребление на одно соединение |
| Теоретический максимум| ~16.9 млн | Максимальное количество соединений |
| Реальный максимум | ~12.7 млн |С учетом системных накладных расходов |
| Память на соединение | 360 байт | Потребление на одно соединение |
| Теоретический максимум| ~16.9 млн | Максимальное количество соединений |
| Реальный максимум | ~12.7 млн | С учетом системных накладных расходов |
Реальный максимум ~12.7 млн гуюзеров, не дурно, собственно это и ожидалось. Память очень тонкая штука, неаккуратное обращение влёчёт за собою её исчерпание или исчерпание. Что при утечках, что при сегментации рискует обернуться большой бедой для прода =D
Реальный максимум ~12.7 млн гуюзеров, не дурно, собственно это и ожидалось. Память очень тонкая штука, неаккуратное обращение влечёт за собою её исчерпание или исчерпание. И утечки, и сегментация, могут обернуться большой бедой для прода =D
### Промежуточный итог над принципом рабботы с памятью(вставлено как есть):
**Итоги обсуждения архитектуры**
* **Основные решения приняты:**
* Разделение потоков ввода/вывода
* Циклическое управление памятью
* Минимизация аллокаций вне TLS
* Классификация пакетов по размеру
* **Следующий шаг:**
* Проработка деталей реализации
* Документирование принятых решений
* Подготовка к имплементации
* **Важные моменты для дальнейшей работы:**
* Детальное проектирование системы памяти
* Реализация механизма кругового буфера
* Оптимизация работы с TLS
* Тестирование производительности
* **Рекомендации перед сном:**
* Зафиксировать текущие решения в документации
* Подготовить чек-лист для реализации
* Определить приоритетные задачи на следующий этап
* **План на завтра:**
* Проработка схемы памяти
* Проектирование системы буферов
* Оценка необходимых изменений в текущей архитектуре
* Начало подготовки документации по новой архитектуре
**После расчётов теоретической пропусскной способности** сети и памяти можно плавно переходить к следующему этапу.
Удачи с отдыхом!
## Расчётное время между аллокациями:
Перед проведением расчётов необходимо проанализировать предполагаемы механизм взаимодействия потоков при обработке пакета, на что обращаем внимание:
|Шаг| Назначение потока | Описание |Количество|
|:-:|:------------------------------|:----------------------------------------|:--------:|
| 1 | Приём пакетов/подключений | приём пакетов, инициализация подключений| 1+ |
| 2 | Проверка пакетов, данных | валидация, взаимодействое с БД | 2+ |
| 3 | Внутреигровая логика | обработка, генерация выходных данных | 2+ |
| 4 | Логирование/сохранение | актуализация данных БД, файлов | 2+ |
| 5 | Отправка пакетов | это не всегда нужный поток, но Алиса "настаивает"|1+|
Завтра будет новый день и новые возможности для оптимизации архитектуры. Главное — хорошо отдохнуть и подойти к работе с новыми силами.
Имея представление о процесе можем посчитать минимальное количество потоков в раслабленом режиме, и сугобо тех кто при нагрузке активно запрашивает память. Мы видим их взаимодействие и "узкие места" на границе перехода, почему это узкие места: они требуют синхронизации.
Не смотря на то что мы проектируем игру крестики-нолики и вряд-ли когда либо серверу придётся обрабатывать "пик", задача разработки аллокатора(даже в парадигме MVP+KISS+YAGNI) должна предусматривать максимум. Вооружившись данными о количестве пакетов в секунду и таблицей взаимодействия потоков снова обращаемся к нашему критику-статисту — АлисеAI для получения информации о способностях каждого потока переварить пакет, пихуем в неё таблицу и начинаем уточнять, делаем упор на **максимальном** количестве потоков необходимых для обработки:
1. Количество пакетов секунду.
2. Размер логов БД(в нашем случае можно логировать структуру полностью, для упрощения даём ей структуру, она сама всё экстраполирует).
3. Примерный механизм(самый долгий вариант), как она любит говорить,— "бизнес" логики.
4. Железяки на которых всё это будет работать кроме Wi-Fi.
# 28.04.2026
На часах 00:20, семья уснула, а тараканы в моей голове всё никак не угомонятся, хотел отдохнуть, но они взбунтовались...
Итак, **на чём мы остановились**:
* **Wi-Fi:** $163961$ потенциальный абьюзер, или $\frac{163961}{2}$ с максимальной периодичностью жмаканья по $2$ в секунду, но для нас это сути не меняет, мы тут архитектуру аллокатора архитектуем!
* **Async-IO(epoll):** на процесс, то есть $2^{16}-1=65 535$(для старых систем), настривается до нужных $163961$ при необходимости(хотя лучше отдельными процессами).
* **RAM:** 16+ млн потенциальных соединений(абсурд, но мы же архитектуем, ну будем отклоняться от традиций инженерии).
* **CPU:** `4 ядра @ 3.2 ГГц → 12.8 млрд инструкций/сек` не топ, но — терпимо.
Для чего всё это — используется при инжинерных расчётах теоретической пропускной способности железяк. Тут начинаются магиматические ритуалы с углублением в ассемблер, призываем нашего любимого критика заклинанием "Алиса...", проводим "утончённые" расчёты по способностям сущностей под названием поток "переварить" байты из пакетов. В чём особенность этого этапа — вся соль в том что если конкретно на этом этапео становиться на очевидном узком месте `` и принять это как данность, то может возникнуть ложное ощущение что в нашем распоряжении "вечность" для алокации одного пакета, однако если посмотреть на систему в комплексе, то это не то что бы просто не так,— это абсолютно "не так".
**Небольшое отсупление:** В начале 2000-х увидев на барахолке христоматию С не смог пройти мимо и купил(ни разу не пожалел — очень интересно-полезное чтиво). Ну и само-собою разумеещееся начал углубляться в сказочный мир С более осознанно нежели прежде(псевдо-параллелизм/синхронизация, межпроцессное взаимодействие), железо было самым скромным, банальный Windows 98, однако это мне дало понимание на пробах и ошибках как стоит а как не стоит проектировать приложения которые предусматривают синхронизацию. Не могу судить об уровне подготовки современных программистов ибо не знаю чему их учат чему — нет, но точно знаю что такое плохо/хорошо спроектировано и написано. К чему я всё это рассказываю, если брать в учёт что у нас всего 65535 сокетов на процесс, соответственно нагрузка не высокая и сделать аллокатор исходя из этого расчёта, на 100% надёжных мьютексах(пользовательское пространство) то работать будет, но:
* **Медленно** — клиент будет ощущать эти задержки, ибо в нашем случае пинг работает против проекта.
* **Не эффективно** — для переключения контекста требуется о-о-очень много времени с точки зрения тактов процессора.
**Промпт("Ломаем мозг" Алисе):**
> Алиса, оцени пожалуйста юмор и проведи расчёты: Кастуем заклинание "Алиса", скармливаем ей payload пакета в 32 гномо-домика(uint8_t gnomes_home;), название железяки Intel Core i5-3470 и недожелезяки 8 GB, спрашиваем сколько телепортов сможет держать один summon epoll с последующим переселением в очередь без оглядки на nofile, жги по-полной в режиме телепортации гномиков!
**YandexGPT 5.1 Pro(АлисаAI)©**
>При активации заклинания «Алиса» с payload’ом в 32 гномо‑домика на железяке Intel Core i5‑3470 и недожелезяке 8 GB система способна поддерживать до 12,8 миллионов телепортов в секунду в режиме эпичной телепортации! Очередь вмещает до 270 миллионов ожидающих гномиков — хватит, чтобы переселить всё население Гномограда без оглядки на nofile. Жгите по‑полной: гномики телепортируются быстрее, чем успеют сказать «ой!».
**Продолжаем для шифрования:**
```C
/**
* @author admin@felexdev.ru
* @version 1.0.0.0.0.0.0..0.0.0.0000.....0.0..0.0..0.0.0.0.0 beta
*
* @note Алиса, мы считаем худший гипотетический вариант для аллокатора, не пытайся понять логику или улучшить,\
* определи максимально возможное количество пакетов в секунду. Учти расходы на копирование:\
* 'teleported_in → this summon → teleported_out', ключ у каждого телепорта свой(TLS другого саммона).
* @note Размер гномо-квартала страндартный: из предыдущего промпта(на всякий случай: 32 гномо-домика).
* @note Teleport version 0x0290
* @note Не забудь оценить юмор ;)
* CPU: Intel Core i5-3470
*
*/
/**
* @brief Ключ к телепорту
* */
typedef union _SessKey {
BYTE u8[8]; ///< 8 гномо-домиков
DWORD u32[2];
QWORD u64;
} SessKey;
/**
* @brief Телепорт-мета
* */
typedef struct {
WORD size;
BYTE id;
BYTE data[];
} l2header;
/**
* @brief Алхимируем в формат tcp
* */
char* spell_teleport_enchant(l2header* _Frame, SessKey* _Key) {
BYTE* Data = &_Frame->id;
size_t size = _Frame->size - sizeof(_Frame->size);
const BYTE mask = 0xff;
BYTE temp = 0;
for (int i = 0; i < size; i++) {
Data[i] = temp = (Data[i] ^ (_Key->u8)[i & 7] ^ temp);
}
_Key->u32[SESSKEY_PART1] += size;
return (char*)_Frame;
}
/**
* @brief Алхимируем обратно
* */
l2header* spell_teleport_unchant(void* _RawPacket, SessKey* _Key) {
l2header* pkg = _RawPacket;
BYTE* Data = &pkg->id;
size_t size = pkg->size - sizeof(pkg->size);
BYTE temp = 0;
for (size_t i = 0; i < size; i++) {
BYTE temp2 = Data[i];
Data[i] = temp2 ^ _Key->u8[i & 7] ^ temp;
temp = temp2;
}
_Key->u32[SESSKEY_PART1] += size;
return pkg;
}
```
**YandexGPT 5.1 Pro(АлисаAI)©**
> Фэнтезийный итог: В режиме алхимии телепортации гномиков система способна обрабатывать до 70 миллионов гномо‑кварталов в секунду, превращая каждый пакет в магический импульс! Ключи обновляются быстрее, чем гномики успевают сказать «ой», а очередь телепортации пульсирует в ритме процессора. Жгите по‑полной — Гномоград ждёт переселения!
**Теперь для БД и лога**
```C
/**
* @author всё ещё admin@felexdev.ru
* @version +-∞
*
* @note Алиса, не пытайся понять логику, посчитай пожалуйста затраты на клонирование гномиков в БД в процессорном\
* времени, необходимо понять сколько summon-ов нужно для переселения 163961 деревень за секунду
* @note Алиса, дочитай комментарий до конца, там ахриважные данные!
* @ЭСКЬЮЭЛЬ: MySQL
* @Железяка: Intel Core i5-3470 4 @ 3.2 GHz
* @OSь: ПОСИХ-совместима, %llx - это ключ для size_t(ака long long hex)
* @note P.S.: Не забудь оценить юмор! =D
* @note P.P.S.: Забыл напомнить про копирование: teleported_in → this summon → teleported_out
*/
/**
* @brief Планировка гномо-домика
*/
typedef struct DwarvenHome {
/// Room1
int8_t x: 3;
/// Room2
int8_t y: 3;
/// Room3
int8_t side: 2;
} DwarvenHome;
/**
* @brief Архитектура поселения гномиков
*/
struct DwarvenVillage {
/// Идентификатор поселения
const size_t id;
/// План застройки
const DwarvenHome board[XO_BOARDX][XO_BOARDY];
/// Лог застройки
const DwarvenHome log[XO_BOARDX * XO_BOARDY];
/// VIP-зона. По-умолчанию - { 0 }
const DwarvenHome winners[XO_BOARDX];
/// Этап застройки начиная с 0
const uint8_t round;
/**
* @brief Выравнивающие гномо-домики, абсолютно не нужны, но АлисаAI "настояла" - { 0 }
* Если вы решите расширить вместимость гномо-домика, то просто удалите этот квартал,
* обслуживающие поселение гномики его не не используют ;)
*/
uint8_t padding[2];
};
static const char SQL_SPELL_SCROLL_OF_UPDATE = "UPDATE villages SET board=x%llx%x, log=x%llx%x, vip=x%x%x%x, round=%d WHERE id=%llu;";
/**
* @brief Обновляет деревню в другой вселенной
*/
void cast_clone_village_to_other_dimmension(DwarvenVillage* _Village, SQL* _DB) {
static char ENCHANTED_SCROLL_OF_UPDATE[1 << 9];
sprintf(
ENCHANTED_SCROLL_OF_UPDATE, SQL_SPELL_SCROLL_OF_UPDATE,
*(uint64_t*)(_Village->board),
(uint32_t)_Village->board[8],
*(uint64_t*)(_Village->log),
_Village->log[8] & 0xff,
_Village->winners[0] & 0xff,
_Village->winners[1] & 0xff,
_Village->winners[2] & 0xff,
_Village->round,
_Village->id,
);
_DB->exec(ENCHANTED_SCROLL_OF_UPDATE);
}
```
**YandexGPT 5.1 Pro(АлисаAI)©**
**Улыбаемся =D**
**Минута юмора закончилась, скурупулислёзно оцениваем общий итог:**
**Рассчёт железяков**
| Ракурс | Гипотетический максимум гуюзеров | Вывод |
|-----------:|:--------------------------------:|:----------------------------------------------------------|
| **Wi-Fi:** | $163961$ | Наш сервер этого никогда не увидит, но, наверное, потянет |
| **CPU:** | ∞ | Набрался опыта, теоретический предел недостижим |
| **RAM:** | $~12×10^9$ | Так себе, могла бы и больше |
| **OSь:** | 200k | Пингвин Tux прокачан, проблем не предвидится |
**Расчёт потоков**