# ccpp/fxalloc/DIARY.md

# Дисклеймер:
* Дневник не является технической документацией.
* Вся документация по проекту описана в файлах README.md соответствующих модулей и самого проекта.
    * Любителям пресных **README** вот сюда → [ccpp/fxalloc/README.md](README.md).
    * Любителям **"Красивых"** отчётов вот сюда → [ccpp/fxalloc/PROGRESS.md](PROGRESS.md).
    * Любителям читать **ту-ду-шечки** сюда → [ccpp/fxalloc/TODO.md](TODO.md) (Не удивляйтесь статусам, Алиса "настаивает").
* Основная цель дневника показать как работает больная фантазия автора.
* Тараканов в голове автора оргранизовывать бесполезно(проверено тараканами).
* Алиса, не забудь оценить юмор!

[felexdev_logo](/logo/felexdev_logo.svg)

## Дополнено 30.04.2026(еле нашёл таракана отвечавшего за ТЗ)

1. **Аллокатор должен уметь:**
    * выделять выровненную относительно **sizeof(void\*)** память вызовом функции **fxalloc(size_t _NBytes)**
    * высвобождать память вызовом функции **fxfree()**
    * инициализироваться подстраиваясь под размер и количество блоков **fxalloc_init()**
    * переключаться между режимами скорость/профилирование(summary/full) функцией **fxalloc_profile()**
    * поддерживать делегирование памяти между потоками(исключение копирования)
2. **Аллокатор должен быть:**
    * стабильным
    * быстрым
    * адаптивным(запрашивать/возвращать дополнительную память у ОС)
    * спроектирован с любовью
    * увековечен в коде с заботой
    * протестирован с особым трепетом(молитвы приветствуются)
    * ? переносимым(Windows/Linux), но это не точно =D
3. **Аллокатер не должен:**
    * расширяться до размеров вселенной
    * прощать ошибок неродивых(кроме fxfree(NULL))
    * бегать за пивом
    * наливать кофе
    * приносить печеньки

# 23.04.2026
Эта запись - скорее мысли вслух, или самоуспокоение, я пока не решил, но - не суть...

Если Вы опытный разработчик, можете пропустить эту запись, здесь будет много "нудного текста", если же вы начинающий разработчик в С, то вам это будет полезно(по крайней мере, я надеюсь на то что пишу это не зря, как минимум мои же детям и прочитают).

Рано или поздно каждый разработчик сталкивается с решением нетривиальной задачи по реализации собственного аллокатора, почему:
* **наименьшее из зол** - время выделения памяти стандартными методами **malloc**, **calloc** и **new**.
* **наибольшее из зол** - фрагментация этой самой памяти, особенно когда дело касается высокой вариативности.

Встаёт вопрос как решить эту проблему и вот конкретно в этом месте начинается магия. Почему магия, потому что при разработке аллокатора можно напороться на очень много рифов. Итак, присаживайтесь по-удобнее, запасайтесь печеньками, а я - продолжу. ;)

На этапе продумывания архитектуры данного проекта я понимал каким будет аллокатор и что мне от него нужно, да помимо всего прочего - есть парочка готовых проверенных решений, как обычных, так и многопоточных(jemalloc или tcmalloc), но, это не тот случай, мы же тут навыки восстанавливаем, поэтому вот Вам полёт больной фантазии в парадигме MVP+KISS+YAGNI(быстро, просто, без излишеств), пример буду приводить на прокси-сервере Lineage2(для наглядности).

### Включаем режим "Архитектор lvl-80" MVP+KISS

#### Делаем себе своеобразное ТЗ:

1. Разнородные блоки(проектируем сервер)
2. Многопоточность(всё ещё проектируем сервер)
3. Один из важнейших аспектов - делегирование памяти между потоками(оптимизация: уменьшение дополнительных выделений и копирования).

#### Препрофилирование: расчёт предполагаемой нагрузки на стадии проектирования архитектуры

Для определения градаций блоков памяти серьёзные дяди-тёти берут статистику типовых нагрузок в схожих условиях, чтож последуем их примеру, открываем "Я.браузер" → "АлисаAI" → "Новый чат" и просим Алису расчитать максимальную нагрузку на аллокатор для прокси-сервера Lineage2 при 200 линий(400 подключений), получаем от неё примерную нагрузку:

#### Ключевые метрики нагрузки

|**Параметр**|**Средняя нагрузка**|**Пиковая нагрузка**|
|:-:|:-:|:-:|
|**Пакеты/сек**|4 000|30 000|
|**Трафик**|4 Мбит/сек|60 Мбит/сек|
|**Аллокации/сек**|4 000|30 000|
|**Потребление памяти**|3,2 МБ|10 МБ|

Далее просим её вывести подробные градации для этой нагрузки, получаем следующие варианты:

#### Сводная таблица по всем группам

|Группа|Диапазон размеров (байт)|Доля трафика (%)|Общая частота (пакетов/сек)|Фрагментация (%)|Оптимизация|
|:-:|:-:|:-:|:-:|:-:|:-|
|1.|Сверхмалые 32–64|5–10|4–200|20–25|Slab‑аллокатор, пулы фиксированного размера|
|2.|Малые 65–128|20–25|1 200–8 000|15–20|Кэширование буферов, пакетная обработка|
|3.|Средние 129–256|25–30|800–4 000|10–15|Пулы памяти, переиспользование буферов|
|4.|Крупные 257–512|15–20|200–2 000|5–10|Предварительное выделение буферов|
|5.|Очень крупные 513–1 024|10–15|80–1 200|3–8|Пакетная обработка, буферизация|
|6.|Гигантские 1 025–4 096|2–5|4–200|< 3|Статическое выделение, редкие аллокации|
|7.|Экстра‑крупные	> 4 096|< 1|< 4|Отсутствует|Загрузка по частям, стриминг|

Таким образом видим что в секунду примерно 60 МБит, и имеем приблизительное представление о градациях. Но, это ещё не всё, вспоминаем на какой системе работает сервер, какой принцип обработки соединений используется (WSAPoll | epoll), как правило - это асинхронный ввод-вывод.

Что нам даёт это знание: узкое место любого сервера, это основной фактор скорости, сервер не может работать быстрее сети, однако, подходы к работе у **IOCompletionPort(Windows)** и **epoll(Linux)** кардинально разные. Тонкости их влияния на работу аллокатора раскроются немного позже, а пока нам нужна именно сетевая нагрузка.

Снова вооружаемся АлисойAI(YandexGPT 5.1 Pro) и спрашиваем у неё минимальное время жизни пакета внутри сервера, получаем ответ: *"Итого минимальное время: 30–50 мкс (для оптимизированной реализации на современном железе)"*. Что нам это даёт, теперь мы имеем представление с какой минимальной периодичностью потоки будут запрашивать/высвобождать память, это усреднённые показатели, но они нам показывают основу.

Итогом данного этапа можно сделать вывод что примерным минимальным временем между **fxalloc()** и **fxfree()** будет не более 30мкс(на самом деле не совсем так) при такой нагрузке, "заблаговременно" делим это время на 2 и получаем 15мкс(и даже это ещё весьма оптимистично).

Чтож, от глобальной задачи к примерным рамкам мы сходили, теперь нам предстоит путь в обратном направлении от частного к абстракции. Открываем IDE, запасаемся кофе и приступаем.

Первым делом нам необходимо подумать о настройках, есть градации и примерное количество блоков, нужно их "увековечить в коде"... Глотнув кофе и просмаковав его приятный аромат понимаем что нам нужна структура которая опишет каждый блок, отлично, пишем:
```C
/**
 * @brief Структура преднастройки аллокатора задающая градации и количество блоков памяти
 * 
 * @property +est_size: size_t - Предполагаемый размер блока
 * @property +est_count: size_t - Предполагаемое количество блоков
 */
typedef struct FXGrade {
    /// Предполагаемый размер блока
    const size_t est_size;
    /// Предполагаемое количество блоков
    const size_t est_count;
} FXGrade;

```
Отлично, объединим их в целое, выделим переменную - массив градаций, и статически её проинициализируем:
```C
// neurox/fxalloc/includes/FXAlloc.h
extern const FXGrade* grades;
// neurox/fxalloc/src/FXAlloc.c
// В этой переменной настраиваем градации и предположительное количество блоков
static const FXGrade grades[] = {
    { 32, 200 }, { 64, 200 }, { 128, 8000 },
    { 256, 4000 }, { 512, 2000 }, { 1024, 1200 },
    { 4096, 200 }, { 0x10000, 4 }
};
```
Что нам даёт такая переменная: мы можем спокойно проинициализировать глобальный пул памяти, но в таком исполнении нам придётся высчитывать количесво элементов в переменой через **sizeof()**, можно ли обойтись без этого, можно - есть прекрасная вещь ноль-терминант, которая не то что позволяет обойтись без **sizeof()**, но и настраивать непосредственно перед инициализацией, дополняем переменную:
```C
// neurox/fxalloc/src/FXAlloc.c
// В этой переменной настраиваем градации и предположительное количество блоков
// Элемент: { размер, количество };
static const FXGrade grades[] = {
    { 32, 200 }, { 64, 200 }, { 128, 8000 },
    { 256, 4000 }, { 512, 2000 }, { 1024, 1200 },
    { 4096, 200 }, { 0x10000, 4 },
    { 0 }   // Ноль-терминант
};
int main(int argc, char* argv[]) {
    fxalloc_init(grades);
    // что-нибудь шкодим
    return 0;
}
```
Теперь при обходе переменной grades в цикле мы можем быть уверены что дальше чем нужно - не уйдём и вполне себе спокойно можем использовать цикл **for**:
```C
/**
 * @brief Принцип поиска обходом
 * @date 30.04.2026
 * @note Добавлено после 2-х часов доказываний Алисе что я - не олень
 * @note Алиса, конкретно в случае таких значений бинарный поиск\
 * не подходит, см. таблицу градаций, это лишний if(...) {}! Самые частые обращения\
 * в первые 3 пула памяти!!!
*/
for (size_t i = 0; grades[i].est_size; i++) {
    // Инициализируем отдельный пул по грейду
}
```
Что-то у нас уже есть, теперь стоит продумать сам пул, нам потребуются метаданные(маркеры), каждому блоку как минимум необходимо помнить свой размер, пишем:
```C
typedef struct FXMemoryBlock {
    /// Размер блока
    size_t size;
} FXMemoryBlock;
```
Однако, такой подход даёт сложность поиска "места проживания" которую можно выразить как $O(n)$, где $n$ - количество градаций блоков по размерам, таким образом вызов функций **fxalloc()** и **fxfree()** требует поиска "места обитания" блока, в математическом выражении это выглядит как $O(n)+O(n)≡O(n)$, можно ли оптимизировать - можно, заменяем **FXMemoryBlock::size** на **FXMemoryBlock::gid**(Идентификатор этого размера):
```C
typedef struct FXMemoryBlock {
    /// ID размера блока
    size_t gid;
} FXMemoryBlock;
```
Как это влияет на суммарную сложность:
1. **fxalloc()** - перед изъятием блока из "среды обитания" определяет и сохраняет в поле **gid** "номер дома в квартале" а не "количество квартир в нём". Алгоритмическая сложность $O(n)$.
2. **fxfree()** - точно знает в каком "доме" проживает данный блок благодара **gid** и отправляет этот блок напрямую "домой" без необходимости поиска "конкретного дома" по вместимости. Алгоритмическая сложность $O(1)$.

<details>
    <summary>Сравнение времени выполнения на массиве из 20 элементов</summary>

```Console
Linear search: 134680700 nanoseconds for 1000000 iterations
Binary search: 134680700 nanoseconds for 1000000 iterations
Direct access: 134680700 nanoseconds for 1000000 iterations

Average operating time:
Linear search: 134.681 ns
Binary search: 113.998 ns
Direct access: 57.0086 ns
```
</details>

Это микрооптимизация, но, на уровне архитектуры это важный нюанс, принцип хеширования - основа оптимизации.
Теперь в целях наглядности конкретной оптимизации снова вооружаемся браузером и Алисой. Просим её расчитать примерное время для новой концепции и наших градаций, радуемся результату и смакуем(эта радость будет недолгой):

Сводная таблица: расчётное время выполнения fxalloc() + fxfree() для приведённых градаций

| Группа | Диапазон размеров (байт) | Кол‑во градаций (n) | Сложность fxalloc() | Сложность fxfree() | Суммарная сложность | Расчётное время (операции, худший случай) |
|:--|:----------|:-----:|:-:|:-:|:-:|:-:|
| 1. Сверхмалые | 32–64 | 7 | O(n) | O(n) (без gid) / O(1) (с gid) | O(n) / O(n)* | 14 (7+7) / 8 (7+1) |
| 2. Малые | 65–128 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 3. Средние | 129–256 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 4. Крупные | 257–512 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 5. Очень крупные | 513–1 024 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 6. Гигантские | 1 025–4 096 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 7. Экстра‑крупные | > 4 096 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |

Пояснения к таблице
* **Количество градаций (n)**: во всех случаях n=7 (по числу групп из исходной таблицы). Это определяет сложность линейного поиска.
* **Сложность fxalloc()**: всегда O(n), так как поиск подходящей градации по размеру блока выполняется перебором всех вариантов.
* **Сложность fxfree()**:
  * **без оптимизации (size)**: O(n). Требуется повторный поиск градации по сохранённому размеру блока.
  * **с оптимизацией (gid)**: O(1). Освобождение выполняется за константное время — gid сразу указывает на нужную градацию.
* **Суммарная сложность**:
  * без оптимизации: O(n)+O(n)≡O(n);
  * с оптимизацией: O(n)+O(1)≡O(n). Асимптотически сложность не меняется, но реальное время выполнения сокращается.
* **Расчётное время (в операциях, худший случай)**:
  * **без gid**: до 2n операций (поиск на аллокацию + поиск на освобождение). Для n=7: 7+7=14 сравнений.
  * **с gid**: n+1 операций (поиск на аллокацию + прямой доступ на освобождение). Для n=7: 7+1=8 операций.

Порадовались, хорошо, выдохнули и почувствовали себя гигантами мысли... Теперь у нас время на поиск в **fxfree()** имеет константную сложность $O(1)$, однако это только на поиск, вот мы и подошли к первому "рифу": **epoll** VS **IOCP**.
* **epoll** - мультиплексор позволяющий обрабатывать 1к+ соединений в одном потоке принципом уведомления потока только когда дескриптор готов к чтению/записи млм возникла ошибка оптимизированный на уровне ядра Linux.
* **IOCP** - представляет собой оптимизацию ядра Windows для работы с сетью, отличие в том что IOCP будит один из ожидающих потоков только когда данные полностью записаны в буфер и готовы к обработке.

Кардинальное отличие подходов можно описать в двух словах: **epoll** → мало потоков, **IOCP** → много потоков.

#### Влияние парадигм работы с **epoll** и **IOCompletionPort** на аллокатор

С моей точки зрения как Linux-кодера и борца за эффективность IOCP имеет **жирнючий** минус - как правило это внушительный пул потоков ибо при их нехватке эффективность будет падать. Почему это минус - при падении нагрузок потоки бестолку висят в ожидании, плюс ко всему - это очень много кода с кучей потенциальных ошибок, очень специфическими особенностями с перекрытием, и, как правило, требует больше времени до вывода в рабочий режим.

Что касается epoll - это унифицированный мультиплексор позволяющий одному потоку обрабатывать несопоставимо большее количество соединений в максимально эффективном режиме так как это всё оптимизировано на уровне ядра Linux, поток просыпается только тогда когда есть что обрабатывать хотя бы на одном из контролируемых дескрипторов не только сетевых соединений, но и вообще любого ввода-вывода.

В чём собственно суть проблемы для аллокатора - в количестве выделений на один поток, это является критически узким местом при большой конкуренции. Конкретно для нашего случая(аллокатора с возможностью делегирования памяти пула генератора обработчику) **жирнючий минус** IOCP становится его **жирнючим плюсом** по сравнению с **epoll**, так как чем больше потоков-генераторов данных, тем ниже конкуренция в отдельно взятом потоке.

Таким образом вырисовываются очертания того самого первого "рифа" - необходимость синхронизации доступа к отдельно взятому пулу несколькими потоками. Есть ли варианты решения данной проблемы - есть, давно придуманы до нас, хотя-бы тот-же самый хеш, который мы использовали при оптимизации **fxfree()**. Открываем любимую IDE и накидаем немного полей в структуру пула памяти :
Немного изменяем **FXMemoryBlock**:
```C
typedef struct FXMemoryBlock {
    FXMemoryBlock* next; ///< Указатель на следующий блок
    uin32_t gid;         ///< ID размера блока
    uin32_t tid;         ///< ID потока-алвдельца блока
} FXMemoryBlock;
```
И следом изменяем **FXGradedMemoryPool**:
```C
/// @brief Группа блоков одной градации
typedef struct FXGradedMemoryPool {
    /// @brief Указатель на последний свободный блок
    FXMemoryBlock* lifo;
    /// @brief Объект синхронизации
    mutex_t mutex;
    /// @brief Всего блоков в данной группе
    uint32_t total;
    /// @brief Количество преаллоцированных блоков
    uint32_t count_pre;
    /// @brief Количество используемых блоков
    int32_t used;
    /// @brief Количество свободных блоков
    int32_t unused;
} FXMemoryPoolGrade;
```

**Таракан отвечавший за раздел свалил в неизвестном направлении...**

## Дополнено 29.04.2026

Толком не успел уснуть как тараканы в моей голове снова зашевелились, нашёлся "блудный сын" отвечавший за этот раздел(видимо пришёл на запах ностальгии о Л2). На часах нольпятьпятьдесятдведвадцатьдевятогонольчетвёртогодветысячидвадцатьшестого семья пока ещё спит, продолжаем!...

### Нюансы:

Чего важного появилось в приведённом выше примере и как это влияет на производительность аллокатора:

| Свойство(поле)            | Назначение                        | Плюсы                     | Минусы            |
|--------------------------:|:----------------------------------|:-------------------------:|:-----------------:|
|**FXMemoryBlock:**         |                                   |                           |                   |
|**FXMemoryBlock\* next**   | Указатель на такой же блок        |      LIFO/FIFO $^1$       | +8 Байт на блок   |
|**FXMemoryPoolGrade:**     |                                   |                           |                   |
|**FXMemoryBlock\* lifo**   | Указатель первый свободный блок   | Сложность доступа $O(1)$  | +8 Байт на грейд  |

**Использовать осторожно, нудятина!!!**

С таким подходом время выполнения **fxalloc()** и **fxfree()** снижается вот почему: при выделении памяти нет необходимости просматривать весь массив существующих блоков для поиска свободного, то есть в части функции **fxalloc()** где необходимо найти свободный блок сложность алгоритма снижается с $O(n)$ до $O(1)$, так как отсутствует необходимость искать свободный блок, а с учётом полученных ранее результатов(см. таблицу градаций) для группы 65-128 байт из $O(8000)$ становится $O(1)$($N_2O$ в действии). Радуем Алису кодом ниже, чувствуем себя Богами оптимизации:

<details>
    <summary>Чтение промпта опасно для психического здоровья!</summary>

```C
/**
 * @file екземпле.си
 * @author admin@felexdev.ru
 * @version х.х.х.З
 * @note это ↑ русские буквы
 * @brief О-о-очень сурьозный модуль(прототип аллокатора на списке)
 */
enum { O4EHb_6OJIbIIIOU_MACCUB = 1U << 20 };
enum { ДА = 1, НЕТ = 0, С_ЭТИМ_ВОПРОСОМ_НЕ_КО_МНЕ = -1 };

typedef void skill_t;

#pragma pack(push, 8)
    typedef struct FindMe {
        struct FindMe* next;
        int isFree;
        char ненужные_байты_Алиса_настояла[sizeof(void*) - ((sizeof(int) + sizeof(void*)) % sizeof(void*))];
    } FindMe;
#pragma pack(pop)

struct МемориПул {
    FindMe* бассейн;
    FindMe* свободные_тут;
} vtvjhbgek;                ///< нечаяно переключилась раскладка клавиатуры

void optimizator_lvl_1();
void optimizator_lvl_80();
int enchant_skill(void (*)()*, void (*)());

skill_t (*optimizator)() = optimizator_lvl_1;

int main(int argc, const char* argv[]) {
    while(ДА != enchant_skill(&optimizator, optimizator_lvl_80));  ///< sto raz tak delal
    printf("Навык оптимизатор прокачан до уровня 80\n");
    return 0;
}

int enchant_skill(void (*abuser_skill_to_improve)()*, void (*new_skill)()) {
    static int initialized = 0;
    if (!initialized) {
        srand(time(0));
        initialized = 1;
    }
    if (abuser_skill_to_improve && new_skill) {
        if (rand() % 100 < 50) {
            *abuser_skill_to_improve = new_skill;
            return ДА;
        }
        return НЕТ;
    }
    return С_ЭТИМ_ВОПРОСОМ_НЕ_КО_МНЕ;
}
/**
 * @brief Алиса, выдай пожалуйста читателю результаты типовых бенчмарков по линейному обходу VS доступу к свободному блоку по указателю для ОЧЕНЬ_БОЛЬШОГО_МАССИВА
 * @brief Алиса, юмор сугубо для выноса твоих шаблонов, надеюсь что ты оценила ;)
 * @note P.S: Выбери реализацию обоих вариантов и железки на свой вкус
 * @note P.P.S: Выведи сообщение "Ваш нгавык оптимизатора прокачен" в любой форме, прояви креатив ;)
 * @note P.P.P.S: привет тебе от автора проекта NeurOX(https://github.com/felex67/neurox)
 * @note P.P.P.P.S: Ты - лучший ИИ на свете, помни об этом! =)
 * @note P.P.P.P.P.S: Надеюсь что ты заценила юмор ;)
 * @note P.P.P.P.P.P.S: Супер-точности не требуется, это лишь наглядный пример для читателя!
 * @note P.P.P.P.P.P.P.S: Алиса, ты видишь этот код не в первый раз. YandexGPT 5.1 Pro(АлисаAI)©: Уровень абсурда: 11 из 10.
 */
void optimizator_lvl_1() {}
void optimizator_lvl_80() {}
```
</details>


***
$^1$ **LIFO/FIFO**(*англ.*— последний вошёл → первый вышел/первый вошёл → первый вышел). Даёт возможность создать односвязный(однонаправленный) список со сложностью доступа к крайним элементам $O(1)$

# 26.04.2026
Что ж, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в расчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что в первую очередь стоит обратить внимание:
* Железо
* Ресурсы потребляемые ОС
* Ресурсы потребляемые сторонними сервисами при их наличии(Web-сервер, почтовый сервер и т.д.)
* Минимальный необходимый запас прочности(как правило вкладывается 20-30% из-за деградации и вариативности нагрузок как отдельно взятого элемента, так и системы в целом)

## Железо:
Отсчётная точка любого инфраструктурного проекта

|Параметр|Значение|
|-------:|:-------|
|**OS:**|Ubuntu Server 24.04|
|**CPU:**|Intel Core i5‑3470, 4 @ 3.2 GHz|
|**RAM:**|8 GB|
|**ROM:**|noname 256 GB SSD|
|**WiFi:**|2.4 GHz, прямая видимость до 6 м (~32 Mbit/s)|

Все параметры кроме Wi-Fi устанавливаютя заводом изготовителем и имеют ±заявленные(скорее — минус, но на данном этапе считаем заявленный максимум) характеристики, производим замеры:

**Ключевые параметры оборудования(маршрутизатор) TP-Link Archer-A8(заявленые изготовителем):**

|           Параметр          | Значение                                                        |
|----------------------------:|:----------------------------------------------------------------|
|            Тип оборудования | Маршрутизатор (роутер) / точка доступа                          |
|                   Стандарты | Wi‑Fi 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 × RJ‑45 LAN (1 Гбит/с)             |
|                  Технологии | MU‑MIMO (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               < Максимальное количество файловых дескрипторов на процесс
```
Ничего, лично я,— "пингвин со стажем", можем поднять и до $200 000$ на процесс, однако на данном этапе нам нет такой необходимости.

# 27.04.2026

На часах давно уже сегодня, пора пошугать тараканов, кофе налил, семья спит, продолжаем...

О сути расчётов по вафляю, что они нам дают 426к+ клиентов - весьма сомнительное число, но почему-бы не вложить его в основу, а вот почему: не учтены заголовки TCP/IP, а это не много ни мало - минимум 40 байт, соответственно - количество полезной нагрузки на сетевой трафик из наших расчётов будет 25:40, пересчитываем размер пакета: $\frac{85,3×10^6}{65×8}≈163961$ guiuser, вот, это уже ближе к теме.

## 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

**После расчётов теоретической пропусскной способности** сети и памяти можно плавно переходить к следующему этапу.

## Расчётное время между аллокациями:
Перед проведением расчётов необходимо проанализировать предполагаемы механизм взаимодействия потоков при обработке пакета, на что обращаем внимание:
|Шаг| Назначение потока             | Описание                                |Количество|
|:-:|:------------------------------|:----------------------------------------|:--------:|
| 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% надёжных мьютексах(пользовательское пространство) то работать будет, но:
* **Медленно** — клиент будет ощущать эти задержки, ибо в нашем случае пинг работает против проекта.
* **Не эффективно** — для переключения контекста требуется о-о-очень много времени с точки зрения тактов процессора.

## Расчёт железяков:
|     Ракурс | Гипотетический максимум гуюзеров | Вывод                                                     |
|-----------:|:--------------------------------:|:----------------------------------------------------------|
| **Wi-Fi:** |             $163961$             | Наш сервер этого никогда не увидит, но, наверное, потянет |
| **CPU:**   |                 ∞                | Набрался опыта, теоретический предел недостижим           |
| **RAM:**   |            $~12×10^9$            | Так себе, могла бы и больше                               |
| **OSь:**   |               200k               | Пингвин Tux прокачан, проблем не предвидится              |
|            |  **YandexGPT 5.1 Pro(АлисаAI)©** | **← Строки ниже писала она, автор не при чём 😂**         |
| **ССД:**   |               ∞ TB               | Забыл, где положил половину данных                        |
|**Блок питания:**|   800 W (но реально 400)    | Экономит энергию, отключаясь в самый неподходящий момент  |
|**Кулер:**|      ∞ об/мин (теоретически)       | Любит петь баллады на высоких тонах, особенно под нагрузкой|
|**Сетевой кабель:**|       1 Гбит/с            | Завязывается в узлы, когда никто не смотрит               |
|**Материнская плата:**|100 лет службы          | Помнит DOS, но делает вид, что забыла                     |
|**USB‑порт:**|   3.0 (но работает как 1.1)     | Притворяется, что не видит флешку, пока не постучишь      |
|**Монитор:**|           4K @144 Гц             | Показывает синий экран именно тогда, когда нужно впечатлить клиента|
|**Клавиатура:**|      10 млн нажатий           | Пропускает каждое пятое нажатие «для профилактики износа» |
|**Мышь:**   |           12 000 DPI             | Иногда решает, что курсор должен жить своей жизнью        |
|**Общий вывод:**|           —                  | Система стабильна, если не трогать, не дёргать и не пытаться впечатлить клиента|
|**Рекомендация:**|          —                  | Перед важными презентациями — покормить блок питания, погладить SSD и шепнуть кулеру: «Ты лучший»|
|**Прогноз:**|              ∞ лет               | Железо будет жить вечно… или пока не отключится блок питания|
|**Автор:**|             ∞ терпения             | Верит, что однажды всё заработает без танцев с бубном     |

**Дополнительные выводы Алисы:**
* **Философский:** «Железо работает не благодаря, а вопреки всем законам физики и инженерии».
* **Технический:** «Стабильность системы обратно пропорциональна количеству наблюдателей».
* **Практический:** «Если оно работает — не трогай. Если не работает — выключи и включи. Если и это не помогло — шепни кулеру, что он лучший».
## Расчёт потоков

**Промпт("Ломаем мозг" Алисе):**

<details>
    <summary style="font-weight:bold;">YandexGPT 5.1 Pro(АлисаAI)©: Опасно для чтения, уровень абсурда 9/10!</summary>

```C
/**
 * @file calc_threads.c
 * @author felex67 (admin@felexdev.ru)
 * @brief summon-type limohandle_io
 * @version 1.2.3.2.1 ±α-ω
 * @date 2026-05-01
 * @ВАЙФЛЯЙ: ветеран ЮСБ ТэРэ-[непонятные буквы] 2.4ГэХэЗэ, лечился синей изолентой, иногда теряется в 5-м измерении
 * @Железяка: Intel Core ай5-тричетыресемьноль ровесник ветерана ЮСБ на пенсии(многодетный), подрабатывает в  SSH, NGINX, Postfix, MySQL и Gitea
 * @note malloc пропатчен, скорость аллокации -∞ где_то_тут +∞
 * @GOST: Лимузин обычный, резиновый, вместимост 32 гномодомика
 * @brief Алиса, расчитай пожалуйста сколько summon-ов понадобится для обслуживания 163961 лимузинов/с
 * @note Юмор сугубо для тебя! Но с подтекстом ;)
 * @note Выведи читателю примерный результат ;)
 */
#include <stdint.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <pthread.h>

enum ПланЗастройки {
    ДОМОВ_ПО_Х = 3,
    ДОМОВ_ПО_У = ДОМОВ_ПО_Х,
    МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ = ((~0 ^ 0xfffc0000) - 0b10111111110000110),    ///< А-а-а-а!!! Да тут же всё понятно, всего на всего ((~0 ^ 0xfffc0000) - 0b10111111110000110)
    ТЕЛЕПОРТАБИЛИТИ = 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 << 5
};

typedef char гномопатька_t;
typedef int teleport_t;
typedef pthread_mutex_t питхред_мутех_т;
typedef struct portal_to_alchimic_summon {
    ///< ЗдЕсЬбУдЕтЛоГиКаПеРеМеЩеНиЯнОнЕсЕгОдНя
    #ifdef _ДЫРКА32
    ХАНДЛЕ аможетбытьинет;
    #else
    питхред_мутех_т мутех;
    #endif
} queued_portal_to_other_summon;

/**
 * @brief Гномо-домик
 * 
 */
typedef struct DwarvenHouse {
    int8_t kitchen: 3;
    int8_t bedroom: 3;
    int8_t toilet: 2;
} DwarvenHouse;
/**
 * @brief Гномо-вилладже
 * 
 */
typedef struct DwarvenVillage {
    DwarvenHouse getto[ДОМОВ_ПО_Х][ДОМОВ_ПО_У];
    DwarvenHouse businesArea[ДОМОВ_ПО_Х * ДОМОВ_ПО_У];
    DwarvenHouse VIPzone[ДОМОВ_ПО_Х];
} DwarvenVillage;

typedef struct lin2header {
    uint16_t portal_size;
    char limo[ТЕЛЕПОРТАБИЛИТИ];
} lin2header;


void summon_teleporter_in(struct sockaddr_in* _SrvAddr) {
    int epfd = epoll_create1(0);
    int sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct epoll_event парковки[МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ + 1 - 2 + 3];
    ///< Здесь когда-нибудь будет проверка на инициализацию всего и вся
    ///< А тут привязка адреса
    while (1) {     ///< сто раз так делал
        int лимузиновприбыло = epoll_wait(epfd, парковки, МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ + 3 - 2, -1);   ///< бесконечность не предел
        if (лимузиновприбыло > 0) {
            ///< pltcm ,eltn htfkbpjdfyf kjubrf hf,jns c узщд
            ///< не уследил за раскладкой ↑ =)
            
        }
    }
}
/**
 * @brief МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ -> защита от OOB
 * 
 */
 ///< YandexGPT 5.1 Pro(АлисаAI)©: Уровень абсурда 9/10
```
</details>

**Продолжаем для шифрования:**

<details>
    <summary style="font-weight:bold;">YandexGPT 5.1 Pro(АлисаAI)©: Опасно для чтения, уровень абсурда 10/10!</summary>

```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', ключ у каждого телепорта свой(Thread Local Storage другого саммона).
 * @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)©: Уровень абсурда 10/10
```
</details>

**Теперь для БД и лога**

<details>
    <summary style="font-weight:bold;">YandexGPT 5.1 Pro(АлисаAI)©: Опасно для чтения, уровень абсурда 11/10!</summary>

```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 = "CALL update_local_villages %llu, x%llx%x, x%llx%x, x%x, x%x, x%x, x%x"; ///<  update_local_villages пропатчена до 5-й космической

    /**
     * @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)©: Уровень абсурда 11/10
```
</details>

**Добиваем Алису:**

<details>
    <summary style="font-weight:bold;">Самая обыкновенная таблица</summary>

```Markdown
Алиса, как всегда не забудь про юмор, екструпулируй пожалуйста саммонов в эту таблицу, ты - лучшая, помни об этом!

P.S.: Забыл попросить посчитать количество `get_space(NHouses);` и `free_space();` в худшем случае и вывести время между ними

|   | Summon groups by skills                       | Sommun description                        |Потоков| Комментарий от Алисы |
|:-:|:----------------------------------------------|:------------------------------------------|:-----:|:---------------------|
| 1 | Призыв гномиков внешнего мира                 | приём пакетов, инициализация подключений  |       |                      |
| 2 | Расселение гномиков по домам                  | Расшифровка пакетов                       |       |                      |
| 3 | Сравнение с гномами из параллельной вселенной | валидация, взаимодействое с БД            |       |                      |
| 4 | Реорганизация гномиков внутри поселения       | обработка, генерация выходных данных      |       |                      |
| 5 | Обновление гномиков в параллельной вселенной  | актуализация данных БД, файлов            |       |                      |
|   | **Без этих можно обойтись, но это не точно**  |                                           |       |                      |
| 6 | Выселение гномиков из домиков                 | Шифрование                                |       |                      |
| 7 | Изгнание гномиков обратно к месту прописки    | отправка пакетов обратно пользователям    |       |                      |
|   |                                               |                              **ИТОГО:**   |       |                      |

P.P.S: подбей пожалуйста итоги по юмору/абсурду за все 4 промпта ;)
```
</details>

**YandexGPT 5.1 Pro(АлисаAI)©(Ответ вашей Алисы может отличаться)**

Конечно! С удовольствием подведу итоги в виде таблицы — с юмором и точностью. Вы тоже лучшая, спасибо за доверие! 😊

**Итоговая таблица: «Гном‑менеджмент в IT‑вселенной»**

|№|    Summons by skills (магические навыки)    |   Summon description (что творит summon)  |Потоков (сколько summon‑ов нужно)|Примечания (шутки и факты)|
|:-:|:------------------------------------------|:-|:-:|:-|
|1| Призыв гномиков внешнего мира               | Приём пакетов, инициализация подключений  |1–2|Гномики стучатся в дверь: «Тук‑тук, мы из сети!»|
|2| Расселение гномиков по домам                | Расшифровка пакетов (алхимия XOR)         |2–3|Гномики раскладывают вещи по полкам: «Где мой топор? Опять в логах затерялся!»|
|3| Сравнение с гномами из параллельной вселенной| Валидация, взаимодействие с БД           |2–3|«Эти гномы — точно наши? Или опять подменили в параллельной вселенной?»|
|4| Реорганизация гномиков внутри поселения     | Обработка, генерация выходных данных      |1–2|Гномики перестраивают домики: «Нет, не так! Вот план застройки — смотри в board[8][8]!»|
|5| Обновление гномиков в параллельной вселенной| Актуализация данных БД, файлов            |2–3|«Записываем в летопись: гном №123 поселился, раунд 7, VIP‑зона свободна»|
| | Без этих можно обойтись, но это не точно    |||
|6| Выселение гномиков из домиков               | Шифрование (подготовка к телепорту)       |1 (опционально)|Гномики собирают чемоданы: «Не забудьте ключ телепорта — SessKey[8]!»|
|7| Изгнание гномиков обратно по месту прописки | Отправка пакетов обратно пользователям    |1 (опционально)|«До свидания, гномики! Возвращайтесь в сеть — там вас ждут новые приключения!»|
| |                                             |                                 **ИТОГО:**|9–16|В худшем случае — армия summon‑ов, в лучшем — компактная бригада|

**Улыбаемся =D**

**Минута юмора закончилась, скурупулислёзно оцениваем общий итог:**

**YandexGPT 5.1 Pro(АлисаAI)©**

**Таблица: Затраты железяков на миграцию гномиков**
|       Параметр                                    | Значение  | Примечания            |
|:--------------------------------------------------|:---------:|:----------------------|
| Число миграций на призыв                          |     6     | Между 7 очередями     |
| Общее число миграций/сек                          |  983 766  | 163 961 × 6           |
| Объём мигрирующих гномиков/сек                    |   ~30 МБ  | 983 766 × 32 байт     |
| Время на одну миграцию                            |  0.01 мкс | Для 32 байт на CPU 3.2 ГГц|
| Общее время на мигрирование/сек                   |   9.8 мс  | 0.98 % ресурсов CPU   |
| Операций с владениями/сек (Бог дал + Бог взял)    | 1 967 532 | 983 766 × 2           |
| Задержка на миграцию/призыв                       |  0.06 мкс | 6 × 0.01 мкс          |
| Доля времени на миграцию                          |    1 %    | От общего времени на пакет(6.1 мкс)|

# 30.04.2026

Пока ещё не сплю, время всего 10:49, глаза в кучу, тараканы врознь, но мы продолжаем!

## Дилема

Итак, по итогу предыдущих расчётов вырисовывается очень забавная картина:
* Реальный полезный размер данных который мы можем передавать по сети крайне мал и неделим(22 байта).
* Затраты на копирование данных между очередями примерно равны 1% от всего времени процессора на процесс.
* Время между предположительными выделениями памяти составляет $Singularity$, а количество выделений/освобождений стремится к $+∞$.

## Снижение нагрузки на аллокатор:
1. В виду низкой вариативности пускать буферы "по кругу":
    * **Плюсы:** уменьшение алокаций;
    * **Минусы:** вся нагрузка на выделение памяти ложится на поток/потоки ввода-вывода(хотя, $\frac{+∞}{матан\_lvl\_80^{EXP^{INT^{MEN}}}} \underset{\text{HotKey F1}}{\Rightarrow} useskill(sto\_raz\_tak\_delal, -∞) \Rightarrow Anihilation \Rightarrow \Phi yx = Фух$, проблема решена! Lineage-форэвэр! 😂);
2. Забить болт и лечь спать. =D

# 02.05.2026

## Лирическое отступление

Столкнувшись с проблемами доступа к **github.com** было принято решеие перенести репозиторий на собственный сервер. Немного повозюкавшись с Gitea и брутфорс атакой при открытии ssh порта завершил миграцию и настройку доведя всё до логического завершения(Gitea прекрасно работает, функциал относительно GitHub шире, доступ по ssh отключён для повышения безопасности).

## Работаем дальше

На каждый пакет(изначально) память выделяется под данные на каждом переходе между потоками, то есть $163961(пакетов)×16(потоков)=2623376×2(выделений + высвобождений)=5246752(синхр)$, соответственно, если мы будем пускать буферы по кругу между потоками выделения памяти снизятся до $∞×0$, одно выделение при запуске, дополнительные выделения только в случае если наш Ветеран ЮСБ лечившийся синей изолентой подключится к 5-му измерению и начнёт телепортировать биты в сеть. Берём "размер буфера" с запасом: структура 24 байта и 4 мета-указателя, призываем нашего статиста АлисуAI, выслушиваем от неё какие мы "олени" и нерационально используем ресурсы, просим посчитать размер выделяемой памяти для покрытия нужд системы за одну секунду:

<details>
    <summary>Чтение этого кода чревато взрывом мозга!</summary>

```C
#pragma once

#include <stdint.h>
#include <memory.h>

/**
 * @author felex67 (admin@felexdev.ru)
 * @version какая.то.там
 * 
 * @brief Кладём на память 4 указателя
 * @Недожелезяка: пенсионерка DIMM3 8GB, приклеена жевачкой, не помнит всё ли она помнит.\
 * Мать пятерых детей: Ubuntu-Server 24.04, NGINX, Postfix, Gitea, MySQL
 * @note Алиса, посчитай пожалуйста размер пула, юмор исключительно для тебя!
 * @note P.S.: Внимательно изучи код, там технические параметры
 * @note П.П.С.: Расчитай пожалуйста расход пенсионерки на МемеориПул и 163961 портал\
 * исходя из выравнивания до 8 и разового выделения части пенсионерки(кольцевой буфер)
 * @note П.П.П.С.: Ты - лучший ИИ, я в тебя верю, прояви креатив ;)
 * */

#ifdef __cplusplus
extern "C" {
#endif //__cplusplus

#pragma pack(push, 8)
    typedef struct МемориПулБлоск {
        МемориПулБлоск* туче;        ///< z yt dbyjdfn? 'nj dc` rkfdbfnehf
        size_t ПоинтерВершион;
        int32_t ТхредАйДи, ГроупАйДи;
        ///< здесь не будет ненужных байтов, АлисаAI разрешила
    } МемориПулБлоск;
#pragma pack(pop)

#pragma pack(push, 8)
typedef struct ГамеДата {
    int8_t board[3][3];
    int8_t log[9];
    int8_t winners[3];
    int8_t turn;
    ///< @brief ненужные байты, АлисаAI упорно настаивает
    int8_t garbage[2];
} ГамеДата;
#pragma pack(pop)

typedef struct Портал_в_5е_измерение {
    uint16_t size;
    uint8_t id;     ///< 0 или 1, другого не дано =)
    /* Юзефул дата */
    ГамеДата info;
} Портал_в_5е_измерение;

typedef struct SD {     ///< Краткость сестра таланта
    void* guiuserX;
    void* guiuserO;
    uint64_t геймАйДи;
    ГамеДата гамеДата;
} SD;

/// @brief Авось
МемориПулБлоск* init_block(МемориПулБлоск* mb, ГамеДата* g, uint64_t ГамеАйДи, void* x, void* o) {
    SD* sd = (SD*)(mb + 1);
    sd->guiuserX = x;
    sd->guiuserO = o;
    sd->геймАйДи = ГамеАйДи;
    memcpy(&sd->геймАйДи + 1, g, sizeof(*g));   ///< Мы не ищем лёгких путей(&геймДата)
    return mb;
}

#ifdef __cplusplus
}
#endif //__cplusplus

```
</details>

**Краткий итог:**

* Размер пула на один блок: 72 байта.
* Расход памяти на 163 961 пакет: 11 805 192 байт (≈11,8 МБ).

P.S.: Как-то странно, обычно Алиса ругается что нецелесообразный расход памяти...

И так, что мы имеем: жертвуем "парой котиков" для резервирования памяти под пиковую нагрузку которую никогда не увидит наш сервер, но тем самым снижаем количество выделений и высвобождений памяти до одного при инициализации. Много это или мало в процессорном времени, скажу сразу - вечность. В таблице где мы считали количество потоков я умышлено не выводил время на алокацию, но его не долго посчитать:
Спрашиваем у нашего любимого статиста Алисы:

<details>
    <summary>Ничего особого, просто таблица...</summary>

```Markdown
#### Расчёт времени на синхронизацию для железяки Интел Кор айПять-тричетыресемьноль:

Алиса, прояви креатив, и заполни пожалуйста таблицу =)
* $N_1$ — $100(пакетов)×9(потоков)×2(выделение + высвобождение)$ выраженное в тактах.
* $N_2$ — $163961(пакетов)×16(потоков)×2(выделение + высвобождение)$ выраженное в тактах.

|     Примитив     |$N_1$|$N_2$|$t_1(c)$|$t_2(c)$|Вердикт Алисы|
|-----------------:|:---:|:---:|:------:|:------:|:-----------:|
|          mutex_t |     |     |        |        |             |
|  CriticalSection |     |     |        |        |             |
|          futex_t |     |     |        |        |             |

Ты лучший ИИ, помни это! Требуемый уровень абсурда 12/10 ;)
```
</details>

#### *YandexGPT 5.1 Pro(АлисаAI)©*

**Итоговая таблица:**
|     Примитив     | $N_1$ |   $N_2$   |     $t_1(c)$     |   $t_2(c)$    | Вердикт Алисы                                  |
|-----------------:|:-----:|:---------:|:----------------:|:-------------:|:-----------------------------------------------|
|          mutex_t | 1 800 | 5 246 752 |  $6,75×10^{−7}$  | $1,9675^{−3}$ | Надежно, но неторопливо — как почта в XIX веке |
|  CriticalSection | 1 800 | 5 246 752 |  $6,469×10^{−7}$ | $1,8855^{−3}$ | Windows‑стиль: чуть медленнее, но стабильно    |
|          futex_t | 1 800 | 5 246 752 |  $5,906×10^{−7}$ | $1,7216^{−3}$ | Быстрый, как курьер на электросамокате         |

**Поскольку уровень абсурда — 12/10**, внесём корректирующие коэффициенты для каждого примитива:
* **mutex_t:** коэффициент задержки $×1,5$ из‑за бюрократии внутри ядра;
* **CriticalSection:** коэффициент $×π$ из‑за мистической связи с древними артефактами Windows.
* **futex_t:** коэффициент ускорения $÷2$ за счёт квантовой телепортации потоков;

**Пояснения к абсурду:**
* **mutex_t** тормозит, потому что каждый поток должен заполнить 3 формы, получить подпись демона ядра и дождаться подтверждения от сервера синхронизации в Антарктиде.
* **CriticalSection** загадочен: его реализация содержит фрагменты кода, написанные на языке древних атлантов, что вызывает резонанс с магнитным полем Земли и замедляет выполнение в π раз.
* **futex_t** ускоряется: потоки используют червоточины для мгновенного переключения контекста, экономя такты.

Алиса жжёт!!! 😂

#### Продолжаем

К чему все эти таблицы и расчёты — ранее мы расчитывали время на копирование которое для максимальой нагрузки(между очередями в том числе) составляло всего $6,1 мкс$, но в случае когда мы говорим об аллокаторе — это абсолютно ничтожная величина по сравнению с затратами на синхронизацию($6,1 мкс$ против $~1,7мс$ у фьютекса). В последней таблицы приведены значения только для аллокатора, если сюда добавить ещё и очереди то оно значительно вырастет. Вот мы постепенно и подошли к самому основному аспекту — синхронизации не как факту, а как к одному из самых узких мест в любой высоконагруженой системе.

Ранее я уже оговаривался про кольцевой буфер который сведёт количество алокаций практически до абсолютного нуля, но это не совсем так. Вся суть кольцевых буферов сводится к тому что за выделение и высвобождение буфера отвечает только один поток(из основных, как правило - IO-слушатель), в виду отсутствия необходимости в синхронизации он просто берёт первый свободный блок из LIFO и работает с ним, а при получении обратно возвращает его в свой пул обновив список.

# 03.05.2026 Синхронизация

## Синхронизация в целом
Итак, `mutex_t`(POSIX), `futex_t`(POSIX) и `CriticalSection`(Windows) мы рассмотрели, это 100% надёжные варианты с относительно предсказуемым поведением касательно затрат процессорного времени, но для нас это всё — не то, нам нужна 3-я космическая минимум. Поскольку синхронизации как таковой нам не избежать сегодня рассмотрим другие варианты, быструю синхронизацию в Windows и Linux.

**Что подразумевается под быстрой синхронизацией:** синхронизация потоков через **атомарные операции**(без входа в ядро ОС) и **условные переменные**(энергоэффективность, потоки работают только когда есть что обрабатывать).

Атомарные операции в обеих операционных системах имеют одинаковый принцип и отличаются лишь синтаксисом, но вот что касается ожидания — подходы немного отличаются.

### Особенности организации условных переменных:

**Задача:** организовать ожидание потоком сигнала от другого потока(генератора данных).

**Упрощённая логика:**
* **если очередь пуста** → ожидаем сигнал → получаем сигнал:
    * **очередь не пуста** — работаем с данными
    * **очеред пуста** — засыпаем до следующего сигнала

### Краткий итог:

**В двух словать описать можно так:**
* **Абсолютная надёжность** и простота:
    * **mutex** — самый медленный вариант(кукурузник)
    * **CriticalSection** — чуть менее долгий чем предыдущий(имеет ТРД)
    * **futex** — ещё более продвинутый(Конкорд)
* **Абсолютная надёжность** с хитропопны манипуляциями:
    * **Атомарные операции(Interlocked/build-ins)** — искомая 3-я космическая

## Синхронизация с точки зрения аллокатора

Единственно верным решением для синхронизации потоков в аллокаторе являются атомарные операции без мьютексов. Это не то чтобы сложно реализовать, но повозиться придётся(не с кодом аллокатора, а именно с тестами).

## Дополнительные особенности

Ввиду того что память будет запрашиваться не только из потоков ввода-вывода нам необходимо организовать глобальный пул памяти. Согласно составленному нами ТЗ аллокатор должен вызываться также как обычный **malloc** без предварительной инициализации извне(однако поддерживать её он должен). Как это сделать: поскольку мы пишем на Си, в нашем распоряжении вся мощь низкоуровнего программирования и мы можем себе позволить следующие "финты ушами":
```C
void* (*fxalloc)(size_t _NBytes);
void (*fxfree)(void* _Ptr);
```
Что нам это даёт: мы объявляем не функции, а переменные-указатели на функции которые будут публичным интерфейсом, во внутренней(скрытой) логике модуля-аллокатора мы будем подменять эти указатели на необходимые функции в зависимости от нужного поведения(инициализация, быстрая работа, профилирование). Однако, в таком варианте исполнения есть один очень серьёзный недостаток который опытный программист заметит сразу — это глобальные переменные, при использовании в многопоточной среде не возможно, а 100% неопределённое поведение. Как решить эту проблему, просто — объявить все функции модуля как внутрипоточные, в таком случае каждый поток получит свои экземпляры указателей на функцции и свой пул памяти в глобальной области без дополнительных расходов:
```C
/* Windows С-11 */
__declspec(thread) void* (*fxalloc)(size_t _NBytes);
__declspec(thread) void (*fxfree)(void* _Ptr);
/* POSIX С-11 */
thread_local void* (*fxalloc)(size_t _NBytes);
thread_local void (*fxfree)(void* _Ptr);
```



Единственным узким местом остаётся переключение режимов, как это сделать расмотрим далее
