Files
fxalloc/DAIRY.md
T

790 lines
76 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ccpp/fxalloc/DAIRY.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.|Сверхмалые 3264|510|4200|2025|Slab‑аллокатор, пулы фиксированного размера|
|2.|Малые 65128|2025|1 2008 000|1520|Кэширование буферов, пакетная обработка|
|3.|Средние 129256|2530|8004 000|1015|Пулы памяти, переиспользование буферов|
|4.|Крупные 257512|1520|2002 000|510|Предварительное выделение буферов|
|5.|Очень крупные 5131 024|1015|801 200|3–8|Пакетная обработка, буферизация|
|6.|Гигантские 1 0254 096|25|4200|< 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)$.
Это микрооптимизация, но, на уровне архитектуры это важный нюанс, принцип хеширования - основа оптимизации.
Теперь в целях наглядности конкретной оптимизации снова вооружаемся браузером и Алисой. Просим её расчитать примерное время для новой концепции и наших градаций, радуемся результату и смакуем(эта радость будет недолгой):
Сводная таблица: расчётное время выполнения fxalloc() + fxfree() для приведённых градаций
| Группа | Диапазон размеров (байт) | Кол‑во градаций (n) | Сложность fxalloc() | Сложность fxfree() | Суммарная сложность | Расчётное время (операции, худший случай) |
|:--|:----------|:-----:|:-:|:-:|:-:|:-:|
| 1. Сверхмалые | 3264 | 7 | O(n) | O(n) (без gid) / O(1) (с gid) | O(n) / O(n)* | 14 (7+7) / 8 (7+1) |
| 2. Малые | 65128 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 3. Средние | 129256 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 4. Крупные | 257512 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 5. Очень крупные | 5131 024 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
| 6. Гигантские | 1 0254 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$ в действии). Радуем Алису кодом ниже, чувствуем себя Богами оптимизации:
```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() {}
```
***
$^1$ **LIFO/FIFO**(*англ.*— последний вошёл → первый вышел/первый вошёл → первый вышел). Даёт возможность создать односвязный(однонаправленный) список со сложностью доступа к крайним элементам $O(1)$
# 26.04.2026
Что ж, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в расчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что в первую очередь стоит обратить внимание:
* Железо
* Ресурсы потребляемые ОС
* Ресурсы потребляемые сторонними сервисами при их наличии(Web-сервер, почтовый сервер и т.д.)
* Минимальный необходимый запас прочности(как правило вкладывается 20-30% из-за деградации и вариативности нагрузок как отдельно взятого элемента, так и системы в целом)
## Железо:
Отсчётная точка любого инфраструктурного проекта
|Параметр|Значение|
|-------:|:-------|
|**OS:**|Ubuntu Server 24.04|
|**CPU:**|Intel Core i53470, 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(заявленые изготовителем):**
| Параметр | Значение |
|----------------------------:|:----------------------------------------------------------------|
| Тип оборудования | Маршрутизатор (роутер) / точка доступа |
| Стандарты | 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 < Максимальное количество файловых дескрипторов на процесс
```
Ничего, лично я,— "пингвин со стажем", можем поднять и до $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)©** | ← Строки ниже писала она, автор ни при чём =D |
| **ССД:** | ∞ TB | Забыл, где положил половину данных |
|**Блок питания:**| 800 W (но реально 400) | Экономит энергию, отключаясь в самый неподходящий момент |
|**Кулер:**| ∞ об/мин (теоретически) | Любит петь баллады на высоких тонах, особенно под нагрузкой|
|**Сетевой кабель:**| 1 Гбит/с | Завязывается в узлы, когда никто не смотрит |
|**Материнская плата:**|100 лет службы | Помнит DOS, но делает вид, что забыла |
|**USB‑порт:**| 3.0 (но работает как 1.1) | Притворяется, что не видит флешку, пока не постучишь |
|**Монитор:**| 4K @144 Гц | Показывает синий экран именно тогда, когда нужно впечатлить клиента|
|**Клавиатура:**| 10 млн нажатий | Пропускает каждое пятое нажатие «для профилактики износа» |
|**Мышь:** | 12 000 DPI | Иногда решает, что курсор должен жить своей жизнью |
|**Общий вывод:**| — | Система стабильна, если не трогать, не дёргать и не пытаться впечатлить клиента|
|**Рекомендация:**| — | Перед важными презентациями — покормить блок питания, погладить SSD и шепнуть кулеру: «Ты лучший»|
|**Прогноз:**| ∞ лет | Железо будет жить вечно… или пока не отключится блок питания|
|**Автор:**| ∞ терпения | Верит, что однажды всё заработает без танцев с бубном |
## Расчёт потоков
**Промпт("Ломаем мозг" Алисе):**
> Алиса, проведи пожалуйста расчёты: Кастуем заклинание "Алиса", скармливаем ей payload пакета в 32 гномо-домика(uint8_t gnomes_home;), название железяки Intel Core i5-3470 и недожелезяки 8 GB, спрашиваем сколько телепортов сможет держать один summon epoll с последующим переселением в очередь без оглядки на nofile, жги по-полной в режиме телепортации гномиков! P.S.: Знаю что "нечаяно" порвал твои шаблоны, прояви креатив ;) extern "C" { ///< YandexGPT 5.1 Pro(АлисаAI)©: Уровень абсурда 8/10 };
```C
```
**Продолжаем для шифрования:**
```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
```
**Теперь для БД и лога**
```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
```
**Добиваем Алису:**
```Markdown
Алиса, как всегда не забудь про юмор, екструпулируй пожалуйста саммонов в эту таблицу, ты - лучшая, помни об этом!
P.S.: Забыл попросить посчитать количество `get_space(NHouses);` и `free_space();` в худшем случае и вывести время между ними
| | Summons by skills | Sommun description |Потоков|
|:-:|:----------------------------------------------|:------------------------------------------|:-----:|
| 1 | Призыв гномиков внешнего мира | приём пакетов, инициализация подключений | |
| 2 | Расселение гномиков по домам | Расшифровка пакетов | |
| 3 | Сравнение с гномами из параллельной вселенной | валидация, взаимодействое с БД | |
| 4 | Реорганизация гномиков внутри поселения | обработка, генерация выходных данных | |
| 5 | Обновление гномиков в параллельной вселенной | актуализация данных БД, файлов | |
| | **Без этих можно обойтись, но это не точно** | | |
| 6 | Выселение гномиков из домиков | Шифрование | |
| 7 | Изгнание гномиков обратно к месту прописки | отправка пакетов обратно пользователям | |
| | | **ИТОГО:** | |
P.P.S: подбей итоги по юмору за все 4 промпта ;)
```
**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
На каждый пакет(изначально) память выделяется под данные на каждом переходе между потоками, то есть $163961×6=983766$ раз, соответственно если мы будем пуск
### Windows VS Linux(POSIX)
Проблемка, конечно та ещё. Вот сейчас начнут совсем неожиданные нюансы: