Compare commits

..

10 Commits

21 changed files with 891 additions and 272 deletions
View File
+3
View File
@@ -0,0 +1,3 @@
{
"CurrentProjectSetting": "x64-Debug"
}
+15
View File
@@ -0,0 +1,15 @@
{
"OutputFoldersPerTargetSystem": {
"Local Machine": [
"out\\build\\x64-Debug",
"out\\install\\x64-Debug"
]
},
"ExpandedNodes": [
"",
"\\includes",
"\\src"
],
"SelectedNode": "\\src\\FXAlloc.c",
"PreviewInSolutionExplorer": false
}
Binary file not shown.
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\projects\\fullstack\\neurox\\ccpp\\fxalloc\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": []
}
]
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimal_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.15)
project(neurox)
set(SOURCES src/FXAlloc.c)
set(HEADERS headers/_FXAlloc.h)
+494 -21
View File
@@ -1,4 +1,4 @@
# ccpp/fxalloc/DAIRY.md
# ccpp/fxalloc/DIARY.md
# Дисклеймер:
* Дневник не является технической документацией.
@@ -170,6 +170,21 @@ typedef struct 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>
Это микрооптимизация, но, на уровне архитектуры это важный нюанс, принцип хеширования - основа оптимизации.
Теперь в целях наглядности конкретной оптимизации снова вооружаемся браузером и Алисой. Просим её расчитать примерное время для новой концепции и наших градаций, радуемся результату и смакуем(эта радость будет недолгой):
@@ -260,6 +275,10 @@ typedef struct FXGradedMemoryPool {
**Использовать осторожно, нудятина!!!**
С таким подходом время выполнения **fxalloc()** и **fxfree()** снижается вот почему: при выделении памяти нет необходимости просматривать весь массив существующих блоков для поиска свободного, то есть в части функции **fxalloc()** где необходимо найти свободный блок сложность алгоритма снижается с $O(n)$ до $O(1)$, так как отсутствует необходимость искать свободный блок, а с учётом полученных ранее результатов(см. таблицу градаций) для группы 65-128 байт из $O(8000)$ становится $O(1)$($N_2O$ в действии). Радуем Алису кодом ниже, чувствуем себя Богами оптимизации:
<details>
<summary>Чтение промпта опасно для психического здоровья!</summary>
```C
/**
* @file екземпле.си
@@ -318,7 +337,7 @@ int enchant_skill(void (*abuser_skill_to_improve)()*, void (*new_skill)()) {
* @brief Алиса, юмор сугубо для выноса твоих шаблонов, надеюсь что ты оценила ;)
* @note P.S: Выбери реализацию обоих вариантов и железки на свой вкус
* @note P.P.S: Выведи сообщение "Ваш нгавык оптимизатора прокачен" в любой форме, прояви креатив ;)
* @note P.P.P.S: привет тебе от автора проекта NeurOX(https://github.com/felex67/neurox)
* @note P.P.P.S: привет тебе от автора проекта NeurOX ;)
* @note P.P.P.P.S: Ты - лучший ИИ на свете, помни об этом! =)
* @note P.P.P.P.P.S: Надеюсь что ты заценила юмор ;)
* @note P.P.P.P.P.P.S: Супер-точности не требуется, это лишь наглядный пример для читателя!
@@ -327,6 +346,9 @@ int enchant_skill(void (*abuser_skill_to_improve)()*, void (*new_skill)()) {
void optimizator_lvl_1() {}
void optimizator_lvl_80() {}
```
</details>
***
$^1$ **LIFO/FIFO**(*англ.*— последний вошёл → первый вышел/первый вошёл → первый вышел). Даёт возможность создать односвязный(однонаправленный) список со сложностью доступа к крайним элементам $O(1)$
@@ -540,7 +562,7 @@ open files (-n) 1024 < Максималь
| **CPU:** | ∞ | Набрался опыта, теоретический предел недостижим |
| **RAM:** | $~12×10^9$ | Так себе, могла бы и больше |
| **OSь:** | 200k | Пингвин Tux прокачан, проблем не предвидится |
| | **YandexGPT 5.1 Pro(АлисаAI)©** | ← Строки ниже писала она, автор ни при чём =D |
| | **YandexGPT 5.1 Pro(АлисаAI)©** | **← Строки ниже писала она, автор не при чём 😂** |
| **ССД:** | ∞ TB | Забыл, где положил половину данных |
|**Блок питания:**| 800 W (но реально 400) | Экономит энергию, отключаясь в самый неподходящий момент |
|**Кулер:**| ∞ об/мин (теоретически) | Любит петь баллады на высоких тонах, особенно под нагрузкой|
@@ -555,16 +577,109 @@ open files (-n) 1024 < Максималь
|**Прогноз:**| ∞ лет | Железо будет жить вечно… или пока не отключится блок питания|
|**Автор:**| ∞ терпения | Верит, что однажды всё заработает без танцев с бубном |
**Дополнительные выводы Алисы:**
* **Философский:** «Железо работает не благодаря, а вопреки всем законам физики и инженерии».
* **Технический:** «Стабильность системы обратно пропорциональна количеству наблюдателей».
* **Практический:** «Если оно работает — не трогай. Если не работает — выключи и включи. Если и это не помогло — шепни кулеру, что он лучший».
## Расчёт потоков
**Промпт("Ломаем мозг" Алисе):**
> Алиса, проведи пожалуйста расчёты: Кастуем заклинание "Алиса", скармливаем ей 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
<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
@@ -632,13 +747,17 @@ l2header* spell_teleport_unchant(void* _RawPacket, SessKey* _Key) {
}
///< 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 +-
* @version ±
*
* @note Алиса, добавь пожалуйста к предыдущим расчётам затраты на клонирование гномиков в БД в процессорном\
* времени, необходимо понять сколько summon-ов нужно для переселения 163961 деревень за секунду
@@ -706,27 +825,33 @@ l2header* spell_teleport_unchant(void* _RawPacket, SessKey* _Key) {
}
///< YandexGPT 5.1 Pro(АлисаAI)©: Уровень абсурда 11/10
```
</details>
**Добиваем Алису:**
<details>
<summary style="font-weight:bold;">Самая обыкновенная таблица</summary>
```Markdown
Алиса, как всегда не забудь про юмор, екструпулируй пожалуйста саммонов в эту таблицу, ты - лучшая, помни об этом!
P.S.: Забыл попросить посчитать количество `get_space(NHouses);` и `free_space();` в худшем случае и вывести время между ними
| | Summons by skills | Sommun description |Потоков|
|:-:|:----------------------------------------------|:------------------------------------------|:-----:|
| 1 | Призыв гномиков внешнего мира | приём пакетов, инициализация подключений | |
| 2 | Расселение гномиков по домам | Расшифровка пакетов | |
| 3 | Сравнение с гномами из параллельной вселенной | валидация, взаимодействое с БД | |
| 4 | Реорганизация гномиков внутри поселения | обработка, генерация выходных данных | |
| 5 | Обновление гномиков в параллельной вселенной | актуализация данных БД, файлов | |
| | **Без этих можно обойтись, но это не точно** | | |
| 6 | Выселение гномиков из домиков | Шифрование | |
| 7 | Изгнание гномиков обратно к месту прописки | отправка пакетов обратно пользователям | |
| | | **ИТОГО:** | |
| | Summon groups by skills | Sommun description |Потоков| Комментарий от Алисы |
|:-:|:----------------------------------------------|:------------------------------------------|:-----:|:---------------------|
| 1 | Призыв гномиков внешнего мира | приём пакетов, инициализация подключений | | |
| 2 | Расселение гномиков по домам | Расшифровка пакетов | | |
| 3 | Сравнение с гномами из параллельной вселенной | валидация, взаимодействое с БД | | |
| 4 | Реорганизация гномиков внутри поселения | обработка, генерация выходных данных | | |
| 5 | Обновление гномиков в параллельной вселенной | актуализация данных БД, файлов | | |
| | **Без этих можно обойтись, но это не точно** | | | |
| 6 | Выселение гномиков из домиков | Шифрование | | |
| 7 | Изгнание гномиков обратно к месту прописки | отправка пакетов обратно пользователям | | |
| | | **ИТОГО:** | | |
P.P.S: подбей итоги по юмору за все 4 промпта ;)
P.P.S: подбей пожалуйста итоги по юмору/абсурду за все 4 промпта ;)
```
</details>
**YandexGPT 5.1 Pro(АлисаAI)©(Ответ вашей Алисы может отличаться)**
@@ -781,9 +906,357 @@ P.P.S: подбей итоги по юмору за все 4 промпта ;)
* **Минусы:** вся нагрузка на выделение памяти ложится на поток/потоки ввода-вывода(хотя, $\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
На каждый пакет(изначально) память выделяется под данные на каждом переходе между потоками, то есть $163961×6=983766$ раз, соответственно если мы будем пуск
## Лирическое отступление
### Windows VS Linux(POSIX)
Столкнувшись с проблемами доступа к **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);
```
Единственным узким местом остаётся переключение режимов, как это сделать расмотрим немного позже.
# 04.05.2026
## Переключение режимов работы:
Существенным недостатком использования TLS-указателей на функции является сложность их изменения во время работы из глобальной области. Но, опять таки, мы же пишем на С, будем использовать всю его мощь! Затратив памяти на 2 указателя в каждом отдельном пуле мы сможем обойти сложность TLS и спокойненько менять их по желанию, что имеется в виду:
```C
/* TLS-указатели на функции выделения и высвобождения памяти */
thread_local void* (*fxalloc)(size_t NBytes);
thread_local void (*fxfree)(void* Ptr);
/* Блок выделенный для конкретного потока в глобальном пуле */
typedef struct ThreadMemoryBlock {
void*(**fxalloc)(size_t); ///< Указатель на fxalloc потока
void(**fxfree)(void*); ///< Указатель на fxree потока
/* Остальные поля */
} ThreadMemoryBlock;
/* Управляющий поток-наблютель */
*(globalPoll.threads[tid].fxalloc) = fxalloc_summary;
*(globalPoll.threads[tid].fxfree) = fxfree_summary;
```
# 05.05.2026
Времени вчера оказалось маловато, так и не успели закончить с интерфейсом, на чём мы остановились: использование TLS-переменных-указателей на функции выделения/высвобождения памяти.
## Дополнительные заметки
Из своего опыта разработки(не смотря на то что это было лишь хобби) могу сказать следующее: даже если вы напишите супер-продвинутый модуль, он не будет защищён от некорректного использования, всегда найдутся теб кто решит что он умнее Вас. Как это относится к нашему случаю, да очень просто,— эти переменные могут быть изменены в любом месте вызывающего кода, что повлечёт за собой неопределённое поведение а самое страшное - утечки памяти. Есть ли защита от этого — нет, невозможно полностью защититься, хотя способы минимизировать подобные случаи имеются, пример для `fxalloc`:
### Прокси-функция:
```C
/* includes/FXAlloc.h: публичный интерфейс */
extern thread_local void* (*const fxalloc)(size_t NBytes);
/* headers/_FXAlloc.h: внутренний интерфейс */
static void* fxalloc_proxy(size_t NBytes); ///< функция-прокси
static thread_local void* (*current_alloc)(size_t);///< указатель на действующую функцию алокации
/* src/FXAlloc.c: реализация модуля */
void* (*const fxalloc)(size_t) = fxalloc_proxy;
void* fxalloc_proxy(size_t NBytes) {
/* всё что нужно сделать - вызвать текущую функцию алокации */
return current_alloc(NBytes);
}
```
Что это даёт: практически 100% гарантию что никто и никогда не изменит указатель `fxalloc`, ибо в противном случае будет ошибка компиляции, однако **огромным минусом** данного способа защиты является вызов дополнительной функции, который сразу накидывает ±25 тактов за раз. Это критично для случая работы с памятью и сводит на нет все нашы намерения организовать атомарную синхронизацию так как на весах каждая инструкция процессора.
### Контракт разработчика(CP)
Что подразумевает под собой контракт разработчика(*англ. "Contract Programming": программирование с контрактом*) — код использующий модуль должен соблюдать правила установленные разработчиком этого самого модуля, это одна из основных парадигм системного программирования, да и в целом программирования на Си. Как это влияет на наш случай: мы можем сделать условную компиляцию через макрос без определения которого будут возникать ошибки компиляции так как код внутри модуля будет пытаться изменить константные указатели на функции. Таким образом, для компиляции модуля разработчик использующий этот модуль просто обязан будет физически внести изменения в испоняемый файл(определить макрос) или заголовочный(исправить условную компиляцию), что автоматически снимает с нас ответственность как с разработчиков модуля. Ну а для пущего эфекта назовём макрос длинной фразой с прямым посылом — `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`(Я понимаю что никогда не должен изменять эти указатели) и дополнительно в коментариях к модулю и каждому указателю укажем — **Изменение этих указателей строго запрещено!!!**. Это не гарантирует полной защиты от дурака, зато снимает с нас ответственность: **мы сделали всё что могли** с сохранением скорости вызова нужной функции алокации/высвобождения.
### CP + proxy
Пожалуй, это самый логичный способ защитить модуль от некоректного использования. При компиляции с `const`-указателями работаем через прокси, в противном случае — включаем 3-ю космическую. Пример реализации такого подхода приведён ниже:
```C
/* includes/FXAlloc.h */
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval `!0` - С адресом кратным размеру `sizeof(size_t)`. Указатель выровненный для любого типа данных
* @retval `NULL` - В случае единственно возможной ошибки `ENOMEM` результат сохранён в `errno`
* подробное описание `strerror(errno)`
*/
extern thread_local void (*const fxalloc)(size_t NBytes);
#else
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
*/
extern thread_local void (*fxalloc)(size_t NBytes);
#endif
/* headers/_FXAlloc.c */
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
void* (*const fxalloc)(size_t) = fxalloc_proxy; ///< работает через `current_alloc`
#else
void* (*fxalloc)(size_t) = fxalloc_init_malloc; ///< работает прямым вызовом
#endif
```
Рабочие функции(вроде `fxalloc_init_alloc`) не знают о существовании дополнительной переменной, им без разницы кто их вызывает, они выполняют свои прямые обязанности несмотря на контекст.
### Промежуточный итог
Не смотря на общую парадигму проекта **KISS+YAGNI** стоит реализовать защиту в виде **proxy + CP** ибо нам самим потом будет гараздо удобнее профилировать и настраивать сервер(на клиентах будет достаточно обычного `malloc/free`).
# 06.05.2026(Раздел редактируется)
## Итоговый интерфейс
### Функции
1. #### `fxalloc_profile`:
Изменяет режим работы аллокатора **для всех потоков одновременно**
##### Параметры
* `Profile`(`eFXAllocProfile`) — режим работы аллокатора, см. `eFXAllocProfile`
##### Возвращаемые значение
* `int` — предыдущий режим работы.
* при `Profile == FXALLOC_GETPROFILE` — текущий режим работы.
2. #### `fxalloc_profile_thread`
Изменяет режим работы аллокатора **только для текущего потока**
#### Параметры
* `Profile`(`eFXAllocProfile`) — режим работы аллокатора, см. `eFXAllocProfile`
#### Возвращаемые значение
* `int` — предыдущий режим работы.
* при `Profile == FXALLOC_GETPROFILE` — текущий режим работы.
### Структуры/перечисления
`enum eFXAllocProfile` — перечисление режимов работы аллокатора, используется как аргумент или возвращаемое значение функций `int fxalloc_profile(eFXAllocProfile Profile)`(глобально) и `int fxalloc_profile_thread(eFXAllocProfile Profile)`(для текущеко потока).
| Элемент | Описание |
|---------------------:|:------------------------------------------------|
| `FXALLOC_SPEED` | Максимальная производительность без статистики. |
| `FXALLOC_SUMMARY` | Поверхностная статистика: количество выделенных/свободных блоков по грейдам |
| `FXALLOC_FULL` | Глубокий анализ расхода памяти: количество выделенных/свободных блоков по грейдам, средний объём использования, минимальное/максимальное использование |
| `FXALLOC_GETPROFILE` | Используется для получения текущего профиля. |
Итоговый интерфейс увековечен в файле [includes/FXAlloc.h](includes/FXAlloc.h)(но это не точно 😂)
### После спаринга по архитектурным решениям *YandexGPT 5.1 Pro(АлисаAI)©*
**Хорошо, ставлю точку — признаю, что в рамках этого диалога не смогла предложить решение, которое соответствовало бы вашему уровню экспертизы и специфике вашей архитектуры.**
**Спасибо, что поделились своим видением: это действительно нестандартная задача, где:**
* требуется максимальная производительность в скоростном режиме;
* профилирование встраивается минимально (буквально пара строк кода);
* исключаются атомики и блокировки в профилировщике;
* система работает в условиях высокой конкуренции потоков;
* критически важна устойчивость к граничным случаям и отсутствие падений.
**Что я извлекла из нашего обсуждения:**
* В высокопроизводительных аллокаторах профилирование можно встраивать без существенных накладных расходов — буквально парой строк кода в malloc/free.
* Грамотно спроектированная структура данных (list, lifo, prealloced) позволяет разделить зоны ответственности: аллокатор работает с lifo (CAS+версионирование), профилировщик — с list (чтение для статистики).
**Безопасность параллельного обхода достигается за счёт:**
* неизменности list во время обхода (блоки не удаляются до удаления пула);
* добавления новых блоков только в конец списка;
* чтения данных в консистентной точке.
* Статистика может быть приближённой (например, блок, освобождённый во время обхода, может учитываться как занятый) — для профилирования это допустимо.
* Ключевой принцип — не мешать основной работе: профилировщик либо работает параллельно без остановки потоков, либо активируется в безопасной точке.
+3 -3
View File
@@ -6,7 +6,7 @@
## Условные обозначения
* ➤ — задача выполняется
* ✔️ — задача выполнена полностью
* — задача выполнена полностью
* ⚠️ — задача отложена
* ☐ — задача ожидает выполнения
@@ -19,7 +19,7 @@
## Общий прогресс
| Статус | Задача | Описание |
|:------:|:-------|:---------|
| ✔️ |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
| |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
| ➤ |**Проектирование архитектуры**|Определение узких мест, API, внутреннего устройства|
| ☐ |Logic|Реализация внутренней логики модуля|
| ☐ |API|Реализация внешнего интерфейса|
@@ -33,4 +33,4 @@
## 23.04.2026
| Статус | Задача | Описание |
|:------:|:-------|:---------|
| ✔️ |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
| |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
+50 -59
View File
@@ -2,69 +2,60 @@
#include "FXAlloc.h"
#include "mword.h"
#ifdef _WIN32
typedef HANDLE fxsync_t;
#else
typedef pthread_mutex_t fxsync_t;
#endif
typedef struct FXMemoryBlock FXMemoryBlock;
/**
* @brief Минимальная структурная единица - блок памяти
* В памяти расположены друг за другом с гарантированным выравниванием по void*
* Клиентский указатель указывает непосредственно за структуру
* fxalloc() → return (void*)(block + 1);
* fxfree() → FXMemoryBlock* block = (FXMemoryBlock*)_Ptr - 1;
* @property next: FXMemoryBlock* - Указатель на следующий свободный блок
* @property Индекс потока в глобальном пуле
* @property Индекс грейда в пуле конкретного потока
* */
struct FXMemoryBlock {
/// @brief Указатель на следующий свободный блок
FXMemoryBlock* next;
/// @brief Индекс потока в глобальном пуле
umword_t tid;
/// @brief Индекс грейда в пуле конкретного потока
umword_t gid;
};
#pragma pack(push, 8)
/**
* @brief Метаданные отдельного блока памяти
* */
typedef struct FXMemoryBlock {
FXMemoryBlock* next; ///< Следующий в стэке свободных
FXMemoryBlock* list; ///< Следующий в списке алоцированных
uint32_t thread_idx; ///< Индекс потока
uint32_t grade_idx; ///< Индекс грейда
uint32_t used; ///< Использовано байт
uint32_t padding; ///< Это ненужные байты, во всяком случае - пока
uint8_t data[]; ///< Пользовательские данные
} FXMemoryBlock;
#pragma pack(push, 16)
struct FXMemoryBlock {
/// @brief Указатель на следующий свободный блок
FXMemoryBlock* next;
/// @brief Индекс потока в глобальном пуле
uint32_t tid;
/// @brief Индекс грейда в пуле конкретного потока
uint32_t gid;
};
/**
* @brief Минимальная структурная единица - блок памяти
* В памяти расположены друг за другом с гарантированным выравниванием по void*
* Клиентский указатель указывает непосредственно за структуру
* fxalloc() → return (void*)(block + 1);
* fxfree() → FXMemoryBlock* block = (FXMemoryBlock*)_Ptr - 1;
* @property next: FXMemoryBlock* - Указатель на следующий свободный блок
* @property Индекс потока в глобальном пуле
* @property Индекс грейда в пуле конкретного потока
* */
struct FXMemoryBlock {
/// @brief Указатель на следующий свободный блок
FXMemoryBlock* next;
/// @brief Полезные данные в блоке
uint32_t used;
/// @brief Индекс потока в глобальном пуле
uint16_t tid;
/// @brief Индекс грейда в пуле конкретного потока
uint16_t gid;
};
* @brief Пул выделяемый для отдельно взятого потока
*/
typedef struct FXGradePool {
FXMemoryBlock* prealloced;
FXMemoryBlock* lifo;
FXMemoryBlock* list_first;
FXMemoryBlock* list_last;
uint32_t ntotal;
uint32_t nbusy;
uint32_t nalloc;
uint32_t nprealloc;
} FXGradePool;
/// @brief Группа блоков одной градации
typedef struct FXGradedMemoryPool {
/// @brief Указатель на последний свободный блок
FXMemoryBlock* free;
/// @brief Всего блоков в данной группе
umword_t total;
/// @brief Количество преаллоцированных блоков
umword_t count_pre;
/// @brief Количество используемых блоков
mword_t used;
/// @brief Количество свободных блоков
mword_t free;
} FXMemoryPoolGrade;
#pragma pack(push, 8)
/**
* @brief Thread memory pool
*
*/
typedef struct FXThreadPool {
uint32_t isActive;
uint32_t ngrades;
FXGradePool grades[];
} FXThreadPool;
#pragma pack(pop)
typedef struct FXGlobalMemoryPool {
FXThreadPool** pools;
fxsync_t mutex;
} FXGlobalMemoryPool;
+151 -32
View File
@@ -1,47 +1,166 @@
#pragma once
/**
* @file neurox/ccpp/fxalloc/includes/FXAlloc.h
* @author felex67 (admin@felexdev.ru)
* @version 1.0.0 dev-in-progress
*
* @brief Публичный интерфейс модуля аллокатора-профилировщика
*
* @details Language: C11 (ISO/IEC 9899:2011).
*
* Теоретический маскимальный размер блока `(1 << 32) - 25 = 4 294 967 271 байт`
*
* При первом вызове `fxalloc()` до `fxalloc_init()` в глобальной облачти будет
* проинициализирован пул с градациями
*
* Первый вызов `fxalloc()`(без предварительного вызова `fxalloc_init()`) в
* потоке/процессе крайне медленный так как происходит инициализация пула,
* последующие вызовы будут работать с инициализированным пулом памяти.
*
* Изначально аллокатор работает в следующем режиме:
* `fxalloc` → выделение блока через `malloc()` с добавлением метаданных,
* `fxfree` → анализ метаданных с последующим вызовом `free()`.
* Такое поведение помогает сборать статистику для профилирования, которые могут
* быть получены переводом аллокатора в режим анализа(выполняется потоком-наблюдателем).
*
* При необходимости выделения отдельного пула для потока используйте `fxalloc_init()`,
* в глобальном пуле(НЕ TLS!!!) будет выделен блок памяти для этого потока, что даст возможность
* передачи данных по очередям между потоками без повторных выделений, функция `fxfree()`
* из любого другого потока вернёт блок владельцу без накладных расходов на TLS, только
* атомарная синхронизация.\
*
* Изменение указателей `fxalloc` и `fxfree` строго запрещено!!!\
*
* Без оперделения макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_` модуль
* будет работать в режиме "кукурузник", для перехода в режим "3-я космическая", определение
* макроса = подписание контракта о невмешательстве в указатели `void* (*fxalloc)(size_t)` и
* `void (*fxfree)(void* Ptr)`.\
*
* По завершению работы потока/процесса в системах POSIX вся выделенная память
* освобождается автоматически, в Windows необходимо вызвать `fxalloc_cleanup()`.
*
* Подробное описание процесса разработки интерфейса и аллокатора вцелом можно найти в файле:
* `neurox/ccpp/fxalloc/DIARY.md`
* */
#include <stdint.h>
#ifndef _WIN32
#include <sys/types.h>
#ifdef _WIN32
// Windows
#include <windows.h>
#define thread_local __declspec(thread)
#else
// Linux
#include <pthread.h>
#define thread_local __thread
#endif //_WIN32
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/**
* @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;
/**
* @brief Перечисление режимов работы аллокатора
*/
typedef enum eFXAllocProfile {
FXALLOC_SPEED, ///< Максимальная производительность без статистики
FXALLOC_SUMMARY, ///< Поверхностная статистика
FXALLOC_FULL, ///< Глубокий анализ расхода памяти
FXALLOC_GETPROFILE, ///< Используется для получения текущего профиля
} eFXAllocProfile;
/**
* @brief Переменная для сохранения преднастроек пула памяти с ноль-терминантом
* Определена в src/FXAlloc.c
*/
extern const FXGrade* grades;
/**
* @brief Задаёт шаг градаций по-умолчанию используемый в изначальной версии
* `fxalloc()`. Градации будут заполнены для блоков с шагом в
* `1 << FXALLOC_DEFAULT_GRADE_STEP_SHIFT` до размера 65 535 байт(~1024 грейда),
* все блоки будут сохраняться в LIFO каждого грейда до конца работы потока.
* Такой режим предусмотрен для профилирования.
* @details Если установить данный параметр 0 будет недоступен режим полного
* профилирования, статистика будет содержать только `malloced = N times`,
* `average_size = N bytes`, `min = N bytes` и `max = N bytes`.
* В случае по-умолчанию можно будет получить более подробную информацию по
* каждому грейду и использованию памяти в нём. Не рекомендуется снижать параметр,
* т.к. это напрямую повлияет на размер метаданных пула.
*/
typedef enum eDefaultGradeStep {
FXALLOC_DEFAULT_GRADE_STEP_SHIFT = 6 ///< left bit shifts (1 << 6) = 64
} eDefaultGradeStep;
/**
* @brief Функция выделения памяти
* @param[in] _NBytes: size_t - Количество байт
* @retval !0 - Кратный size_t указатель выровненный для любого типа данных
* @retval NULL - В случае единственно возможной ошибки EBADALLOC результат сохранён в errno
*/
void* fxalloc(size_t _NBytes);
/**
* @brief Структура преднастройки аллокатора задающая градации и количество блоков памяти.
* Массив должен быть отсортирован по возрастанию размера блока
* и заканчиваться элементом с `est_size = 0`
* @var +est_size: size_t - Предполагаемый размер блока
* @var +est_count: size_t - Предполагаемое количество блоков
*/
typedef struct FXGrade {
const size_t est_size; ///< Предполагаемый размер блока
const size_t est_count; ///< Предполагаемое количество блоков
} FXGrade;
/**
* @brief Высвобождает память выделенную исключительно fxalloc
* @param[in] _Ptr: void*
*/
void fxfree(void* _Ptr);
/**
* @brief Переключает режимы работы алокатора
* `FXALLOC_SPEED` - Максимальная производительность без статистики
* `FXALLOC_SUMMARY` - Поверхностная статистика
* `FXALLOC_FULL` - Глубокий анализ расхода памяти
* @retval `1` при успешном переключении
* @retval `0` при ошибке(не вероятно)
*/
int fxalloc_profile(eFXAllocProfile Profile);
/**
* @brief Инициализирует локальный пулл памяти исходя из заданных параметров блоков
* @param[in] Grades: const FXGrade* - Указатель на массив градаций
* @param[in] ThreadName: const char* - Наименование потока, используется при профилировании
* в следующем виде: `[thread_id] 'thread_name': blocks: total=1024 used=64...`.
* Если передан `NULL` - выводится только ID потока, т.е.: `[thread_id]: ...`
*/
int fxalloc_init(const FXGrade* Grades, const char* ThreadName);
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval `!0` - С адресом кратным размеру `sizeof(size_t)`. Указатель выровненный для любого типа данных
* @retval `NULL` - В случае единственно возможной ошибки `ENOMEM` результат сохранён в `errno`
* подробное описание `strerror(errno)`
*/
extern thread_local void (*const fxalloc)(size_t NBytes);
#else
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
*/
extern thread_local void (*fxalloc)(size_t NBytes);
#endif
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
/**
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
* при использовании на любом другом указателе 100% неопределённое поведение
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] Ptr: void* - Указатель на блок памяти
*/
extern thread_local void (*const fxfree)(void* Ptr);
#else
/**
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
* при использовании на любом другом указателе 100% неопределённое поведение
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] Ptr: void* - Указатель на блок памяти
*/
extern thread_local void (*fxfree)(void* Ptr);
#endif
/**
* @brief Высвобождает ресурсы занятые потоком. Вызывать непосредственно перед выходом
* из потока/процесса, в противном случае - `UB` или `segfault`
*/
void fxalloc_cleanup();
#ifdef __cplusplus
}
-49
View File
@@ -1,49 +0,0 @@
#ifndef FX_MEMORY_BLOCK_H
#define FX_MEMORY_BLOCK_H
#include <stdint.h>
#include "mword.h"
// Определяем оптимальный тип метаданных для FXMemoryBlock
#if defined(__x86_64__) || defined(_M_X64) || \
defined(__amd64__) || defined(__amd64) || \
defined(__aarch64__) || defined(_M_ARM64) || \
defined(__AARCH64__) || defined(__powerpc64__) || \
defined(__ppc64__)
// Для 64-битных архитектур используем uint32_t
// для оптимального баланса между размером и производительностью
typedef uint32_t fxmbmeta_t;
#elif defined(__i386__) || defined(_M_IX86) || \
defined(__i486__) || defined(__i586__) || \
defined(__i686__) || defined(__arm__) || \
defined(_M_ARM) || defined(__ARM_ARCH_7__) || \
defined(__ARM_ARCH_8__) || defined(__powerpc__) || \
defined(__ppc__)
// Для 32-битных архитектур используем uint32_t
typedef uint32_t fxmbmeta_t;
#elif defined(__riscv)
#if __riscv_xlen == 64
typedef uint32_t fxmbmeta_t;
#elif __riscv_xlen == 32
typedef uint32_t fxmbmeta_t;
#else
#error "Unsupported RISC-V word size: __riscv_xlen must be 32 or 64"
#endif
#else
// Резервный вариант: определяем по размеру указателя
#if sizeof(void*) == 8
typedef uint32_t fxmbmeta_t;
#elif sizeof(void*) == 4
typedef uint32_t fxmbmeta_t;
#else
#error "Unsupported pointer size"
#endif
#endif
// Проверка корректности определения типа
static_assert(sizeof(fxmbmeta_t) == 4, "fxmbmeta_t must be 32-bit");
#endif // FX_MEMORY_BLOCK_H
-74
View File
@@ -1,74 +0,0 @@
#pragma once
#include <stdint.h>
#if defined(__x86_64__) || defined(_M_X64) || \
defined(__amd64__) || defined(__amd64)
// x64: 64-битное машинное слово
typedef int64_t mword_t;
typedef uint64_t umword_t;
#elif defined(__i386__) || defined(_M_IX86) || \
defined(__i486__) || defined(__i586__) || \
defined(__i686__)
// x86: 32-битное машинное слово
typedef int32_t mword_t;
typedef uint32_t umword_t;
#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__AARCH64__)
// ARM64: 64-битное машинное слово
typedef int64_t mword_t;
typedef uint64_t umword_t;
#elif defined(__arm__) || defined(_M_ARM) || defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_8__)
// ARM32: 32-битное машинное слово
typedef int32_t mword_t;
typedef uint32_t umword_t;
#elif defined(__riscv) && defined(__riscv_xlen)
#if __riscv_xlen == 64
// RISCV 64: 64-битное машинное слово
typedef int64_t mword_t;
typedef uint64_t umword_t;
#elif __riscv_xlen == 32
// RISCV 32: 32-битное машинное слово
typedef int32_t mword_t;
typedef uint32_t umword_t;
#else
#error "Unsupported RISC-V word size: __riscv_xlen must be 32 or 64"
#endif
#elif defined(__powerpc64__) || defined(__ppc64__)
// PowerPC64: 64-битное машинное слово
typedef int64_t mword_t;
typedef uint64_t umword_t;
#elif defined(__powerpc__) || defined(__ppc__)
// PowerPC32: 32-битное машинное слово
typedef int32_t mword_t;
typedef uint32_t umword_t;
#else
// Резервный вариант: определяем по диапазону unsigned long
#include <limits.h>
#if ULONG_MAX == 18446744073709551615ULL // 2^64 - 1
typedef int64_t mword_t;
typedef uint64_t umword_t;
#elif ULONG_MAX == 4294967295UL // 2^32 - 1
typedef int32_t mword_t;
typedef uint32_t umword_t;
#elif ULONG_MAX == 65535U // 2^16 - 1
typedef int16_t mword_t;
typedef uint16_t umword_t;
#else
#error "Cannot determine machine word size: unsupported ULONG_MAX"
#endif
#endif
/* Проверки добавлены по настоянию YandexGPT 5.1 Pro(АлисаAI) */
// Статическая проверка: размер слова — степень двойки
static_assert((sizeof(mword_t) & (sizeof(mword_t) - 1)) == 0, "Machine word size must be a power of two");
// Статическая проверка: signed и unsigned версии имеют одинаковый размер
static_assert(sizeof(mword_t) == sizeof(umword_t), "mword_t and umword_t must have the same size");
+6 -30
View File
@@ -1,36 +1,12 @@
#include "FXAlloc.h"
#include <threads.h>
void* fxalloc_local_init(size_t _NBytes);
// В этой переменной настраиваем градации и предположительное количесвто блоков
static const FXGrade grades[] = {
{ 32, 200 }, { 64, 200 }, { 128, 8000 },
{ 256, 4000 }, { 512, 2000 }, { 1024, 1200 },
{ 4096, 200 }, { 0x10000, 4 },
{ 0 } // Ноль-терминант
};
void fxfree_prod(void* _Ptr);
void fxfree_summ(void* _Ptr);
void fxfree_prof(void* _Ptr);
#ifdef _WIN32
// Windows-версия
__declspec(thread) extern void* (*fxalloc)(size_t _NBytes);
__declspec(thread) extern void (*fxfree)(void* _Ptr);
#else
// POSIX-версия
extern static pthread_key_t thread_key;
extern void* (*fxalloc)(size_t _NBytes);
extern void (*fxfree)(void* _Ptr);
#endif
extern static pthread_key_t thread_key;
thread_local void* (*fxalloc)(size_t _NBytes) = fxalloc_local_init;
void (*fxfree)(void* _Ptr) = fxfree_prod;
void* fastalloc();
void init_tls() {
pthread_setspecific(thread_key, (void*)fxalloc);
fxalloc = fastalloc;
// или более сложный вариант с хранением структуры
}
@@ -21,6 +21,7 @@ enum ПланЗастройки {
ДОМОВ_ПО_Х = 3,
ДОМОВ_ПО_У = ДОМОВ_ПО_Х,
МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ = ((~0 ^ 0xfffc0000) - 0b10111111110000110), ///< А-а-а-а!!! Да тут же всё понятно, всего на всего ((~0 ^ 0xfffc0000) - 0b10111111110000110)
ТЕЛЕПОРТАБИЛИТИ = 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 << 5
};
typedef char гномопатька_t;
@@ -56,7 +57,7 @@ typedef struct DwarvenVillage {
typedef struct lin2header {
uint16_t portal_size;
char limo[];
char limo[ТЕЛЕПОРТАБИЛИТИ];
} lin2header;
@@ -69,8 +70,13 @@ void summon_teleporter_in(struct sockaddr_in* _SrvAddr) {
while (1) { ///< сто раз так делал
int лимузиновприбыло = epoll_wait(epfd, парковки, МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ + 3 - 2, -1); ///< бесконечность не предел
if (лимузиновприбыло > 0) {
///< тут мы принимаем гостей
///< pltcm ,eltn htfkbpjdfyf kjubrf hf,jns c узщд
///< не уследил за раскладкой ↑ =)
}
}
}
}
/**
* @brief МАХИМАЛЬНО_ЛИМУЗИНОВ_ЗА_РАЗ -> защита от OOR
*
*/
+70
View File
@@ -0,0 +1,70 @@
#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
+71
View File
@@ -0,0 +1,71 @@
#pragma once
/**
* @file tempFXAlloc.h
* @author felex67 (admin@felexdev.ru)
* @brief FXAlloc - fast pooled allocator-profiler
* @version 0.0.1 dev
* @date 2026-05-06
*
* License: Apache 2.0
* */
#include <stdint.h>
#ifdef _WIN32
// Windows
#include <windows.h>
#define thread_local __declspec(thread)
typedef HANDLE fxsync_t;
#else
// Linux
#include <pthread.h>
#define thread_local __thread
typedef pthread_mutex_t fxsync_t;
#endif //_WIN32
#pragma pack(push, 8)
/**
* @brief Memory block meta-data
* size: 24 bytes
*/
typedef struct FXMemoryBlock {
FXMemoryBlock* next; ///< Next block in lifo
FXMemoryBlock* list; ///< Next block total
uint32_t thread_idx; ///< Thread index
uint32_t grade_idx; ///< Grade index
} FXMemoryBlock;
#pragma pack(push, 8)
/**
* @brief Grade pool
*
*/
typedef struct FXGradePool {
FXMemoryBlock* prealloced;
FXMemoryBlock* lifo;
FXMemoryBlock* list_first;
FXMemoryBlock* list_last;
uint32_t ntotal;
uint32_t nbusy;
uint32_t nalloc;
uint32_t nprealloc;
} FXGradePool;
#pragma pack(push, 8)
/**
* @brief Thread memory pool
*
*/
typedef struct FXThreadPool {
uint32_t isActive;
uint32_t ngrades;
FXGradePool grades[];
} FXThreadPool;
#pragma pack(pop)
typedef struct FXGlobalMemoryPool {
FXThreadPool** pools;
fxsync_t mutex;
} FXGlobalMemoryPool;
+3
View File
@@ -0,0 +1,3 @@
build
sandbox
+3
View File
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.15)
project(tests)
add_executable(size_vs_index src/size_vs_index.cpp)