Работа над документами
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
sandbox
|
||||||
Vendored
+28
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "cppbuild",
|
||||||
|
"label": "C/C++: cl.exe build active file",
|
||||||
|
"command": "cl.exe",
|
||||||
|
"args": [
|
||||||
|
"/Zi",
|
||||||
|
"/EHsc",
|
||||||
|
"/nologo",
|
||||||
|
"/Fe${fileDirname}\\${fileBasenameNoExtension}.exe",
|
||||||
|
"${file}"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${fileDirname}"
|
||||||
|
},
|
||||||
|
"problemMatcher": [
|
||||||
|
"$msCompile"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"detail": "Task generated by Debugger."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "2.0.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimal_required(VERSION 3.10)
|
||||||
|
project(neurox)
|
||||||
|
set(SOURCES src/FXAlloc.c)
|
||||||
|
set(HEADERS headers/_FXAlloc.h)
|
||||||
|
set(INCLUDES includes/FXAlloc.h)
|
||||||
|
add_library(fxalloc STATIC ${SOURCES} ${HEADERS} ${INCLUDES})
|
||||||
|
target_include_directories(fxalloc PUBLIC includes PRIVATE headers)
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# DAIRY.md
|
||||||
|
|
||||||
|
# 23.04.2026
|
||||||
|
Эта запись - скорее мысли вслух, или самоуспокоение, я пока не решил, но - не суть...
|
||||||
|
Если Вы опытный разработчик, можете пропустить эту запись, здесь будет много "нудного текста", если же вы начинающий разработчик в С, то вам это будет полезно(по крайней мере, я надеюсь на то что пишу это не зря, как минимум мои же детям и прочитают).
|
||||||
|
Рано или поздно каждый разработчик сталкивается с решением нетривиальной задачи по реализации собственного аллокатора, почему:
|
||||||
|
* **наименьшее из зол** - время выделения памяти стандартными методами **malloc**, **calloc** и **new**.
|
||||||
|
* **наибольшее из зол** - фрагментация этой самой памяти, особенно когда дело касается высокой вариативности.
|
||||||
|
|
||||||
|
Встаёт вопрос как решить эту проблему и вот конкретно в этом месте начинается магия. Почему магия, потому что при разработке аллокатора можно напороться на очень много рифов. Итак, присаживайтесь по-удобнее, запасайтесь печеньками, а я - продолжу. ;)
|
||||||
|
На этапе продумывания архитектуры данного проекта я понимал каким будет аллокатор и что мне от него нужно, да помимо всего прочего - есть парочка готовых проверенных решений, как обычных, так и многопоточных(jemalloc или tcmalloc), но, это не тот случай, мы же тут навыки восстанавливаем, поэтому вот Вам полёт больной фантазии в парадигме MVP+KISS+YAGNI(быстро, просто, без излишеств), пример буду приводить на прокси-сервере Lineage2(для наглядности).
|
||||||
|
### Включаем режим "Архитектор lvl-80" MVP+KISS
|
||||||
|
#### Делаем себе своеобразное ТЗ:
|
||||||
|
1. Разнородные блоки(проектируем сервер)
|
||||||
|
2. Многопоточность(всё ещё проектируем сервер)
|
||||||
|
3. Один из важнейших аспектов - делегирование памяти между потоками(оптимизация: уменьшение дополнительных выделений и копирования).
|
||||||
|
#### Препрофилирование: расчёт предполагаемой нагрузки на стадии проектирования архитектуры
|
||||||
|
Для определения градаций блоков памяти серьёзные дяди-тёти берут статистику типовых нагрузок в схожих условиях, чтож последуем их примеру, открываем "Я.браузер" → "АлисаAI" → "Новый чат" и просим Алису расчитать максимальную нагрузку на аллокатор для прокси-сервера Lineage2 при 200 линий(400 подключений), получаем от неё примерную нагрузку:
|
||||||
|
#### Ключевые метрики нагрузки
|
||||||
|
|**Параметр**|**Средняя нагрузка**|**Пиковая нагрузка**|
|
||||||
|
|:-:|:-:|:-:|
|
||||||
|
|**Пакеты/сек**|4 000|30 000|
|
||||||
|
|**Трафик**|4 Мбит/сек|60 Мбит/сек|
|
||||||
|
|**Аллокации/сек**|4 000|30 000|
|
||||||
|
|**Потребление памяти**|3,2 МБ|10 МБ|
|
||||||
|
|
||||||
|
Далее просим её вывести подробные градации для этой нагрузки , получаем следующие варианты:
|
||||||
|
|
||||||
|
#### Сводная таблица по всем группам
|
||||||
|
|Группа|Диапазон размеров (байт)|Доля трафика (%)|Общая частота (пакетов/сек)|Фрагментация (%)|Оптимизация|
|
||||||
|
|:-:|:-:|:-:|:-:|:-:|:-|
|
||||||
|
|1.|Сверхмалые 32–64|5–10|4–200|20–25|Slab‑аллокатор, пулы фиксированного размера|
|
||||||
|
|2.|Малые 65–128|20–25|1 200–8 000|15–20|Кэширование буферов, пакетная обработка|
|
||||||
|
|3.|Средние 129–256|25–30|800–4 000|10–15|Пулы памяти, переиспользование буферов|
|
||||||
|
|4.|Крупные 257–512|15–20|200–2 000|5–10|Предварительное выделение буферов|
|
||||||
|
|5.|Очень крупные 513–1 024|10–15|80–1 200|3–8|Пакетная обработка, буферизация|
|
||||||
|
|6.|Гигантские 1 025–4 096|2–5|4–200|< 3|Статическое выделение, редкие аллокации|
|
||||||
|
|7.|Экстра‑крупные > 4 096|< 1|< 4|Отсутствует|Загрузка по частям, стриминг|
|
||||||
|
|
||||||
|
Таким образом видим что в секунду примерно 60 МБит, и имеем приблизительное представление о градациях. Но, это ещё не всё, вспоминаем на какой системе работает сервер, какой принцип обработки соединений используется (WSAPoll | epoll), как правило - это асинхронный ввод-вывод, что нам даёт это знание - узкое место любого сервера, это основной фактор скорости, сервер не может работать быстрее сети, однако, подходы к работе у **IOCompletionPort(Windows)** и **epoll(Linux)** кардинально разные, тонкости их влияния на работу аллокатора раскроются немного позже, а пока нам нужна именно сетевая нагрузка. Снова вооружаемся АлисойAI(YandexGPT 5.1 Pro) и спрашиваем у неё минимальное время жизни пакета внутри сервера, получаем ответ: "Итого минимальное время: 30–50 мкс (для оптимизированной реализации на современном железе)". Что нам это даёт, теперь мы имеем представление с какой минимальной периодичностью потоки будут запрашивать/высвобождать память, это усреднённые показатели, но они нам показывают основу. Итогом данного этапа можно сделать вывод что примерным минимальным временем между **fxalloc()** и **fxfree()** будет не более 30мкс при такой нагрузке, "заблаговременно" делим это время на 2 и получаем 15мкс.
|
||||||
|
Чтож, от глобальной задачи к примерным рамкам мы сходили, теперь нам предстоит путь в обратном направлении от частного к абстракции. Открываем IDE, запасаемся кофе и приступаем.
|
||||||
|
Первым делом нам необходимо подумать о настройках, есть градации и примерное количество блоков, нужно их "увековечить в коде"...
|
||||||
|
Глотнув кофе и просмаковав его приятный аромат понимаем что нам нужна структура которая опишет каждый блок, отлично, пишем:
|
||||||
|
```C
|
||||||
|
/**
|
||||||
|
* @brief Структура преднастройки аллокатора задающая градации и количество блоков памяти
|
||||||
|
*
|
||||||
|
* @property +est_size: size_t - Предполагаемый размер блока
|
||||||
|
* @property +est_count: size_t - Предполагаемое количество блоков
|
||||||
|
*/
|
||||||
|
typedef struct FXGrade {
|
||||||
|
/// Предполагаемый размер блока
|
||||||
|
const size_t est_size;
|
||||||
|
/// Предполагаемое количество блоков
|
||||||
|
const size_t est_count;
|
||||||
|
} FXGrade;
|
||||||
|
|
||||||
|
```
|
||||||
|
Отлично, объединим их в целое, выделим переменную - массив градаций, и статически её проинициализируем:
|
||||||
|
```C
|
||||||
|
// neurox/fxalloc/includes/FXAlloc.h
|
||||||
|
extern const FXGrade* grades;
|
||||||
|
// neurox/fxalloc/src/FXAlloc.c
|
||||||
|
// В этой переменной настраиваем градации и предположительное количество блоков
|
||||||
|
static const FXGrade grades[] = {
|
||||||
|
{ 32, 200 }, { 64, 200 }, { 128, 8000 },
|
||||||
|
{ 256, 4000 }, { 512, 2000 }, { 1024, 1200 },
|
||||||
|
{ 4096, 200 }, { 0x10000, 4 }
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Что нам даёт такая переменная: мы можем спокойно проинициализировать глобальный пул памяти, но в таком исполнении нам придётся высчитывать количесво элементов в переменой через **sizeof()**, можно ли обойтись без этого, можно - есть прекрасная вещь ноль-терминант, дополняем переменную:
|
||||||
|
```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 } // Ноль-терминант
|
||||||
|
};
|
||||||
|
```
|
||||||
|
Теперь при обходе переменной grades в цикле мы можем быть уверены что дальше чем нужно - не уйдём и вполне себе спокойно можем использовать цикл **for**:
|
||||||
|
```C
|
||||||
|
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. Сверхмалые | 32–64 | 7 | O(n) | O(n) (без gid) / O(1) (с gid) | O(n) / O(n)* | 14 (7+7) / 8 (7+1) |
|
||||||
|
| 2. Малые | 65–128 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
| 3. Средние | 129–256 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
| 4. Крупные | 257–512 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
| 5. Очень крупные | 513–1 024 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
| 6. Гигантские | 1 025–4 096 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
| 7. Экстра‑крупные | > 4 096 | 7 | O(n) | O(n) / O(1) | O(n) / O(n)* | 14 / 8 |
|
||||||
|
|
||||||
|
Пояснения к таблице
|
||||||
|
* **Количество градаций (n)**: во всех случаях n=7 (по числу групп из исходной таблицы). Это определяет сложность линейного поиска.
|
||||||
|
* **Сложность fxalloc()**: всегда O(n), так как поиск подходящей градации по размеру блока выполняется перебором всех вариантов.
|
||||||
|
* **Сложность fxfree()**:
|
||||||
|
* **без оптимизации (size)**: O(n). Требуется повторный поиск градации по сохранённому размеру блока.
|
||||||
|
* **с оптимизацией (gid)**: O(1). Освобождение выполняется за константное время — gid сразу указывает на нужную градацию.
|
||||||
|
* **Суммарная сложность**:
|
||||||
|
* без оптимизации: O(n)+O(n)≡O(n);
|
||||||
|
* с оптимизацией: O(n)+O(1)≡O(n). Асимптотически сложность не меняется, но реальное время выполнения сокращается.
|
||||||
|
* **Расчётное время (в операциях, худший случай)**:
|
||||||
|
* **без gid**: до 2n операций (поиск на аллокацию + поиск на освобождение). Для n=7: 7+7=14 сравнений.
|
||||||
|
* **с gid**: n+1 операций (поиск на аллокацию + прямой доступ на освобождение). Для n=7: 7+1=8 операций.
|
||||||
|
|
||||||
|
Порадовались, хорошо, выдохнули и почувствовали себя гигантами мысли, теперь у нас время на поиск в **fxfree()** имеет константную сложность $O(1)$, однако это только на поиск, вот мы и подошли к первому "рифу": **epoll** VS **IOCP**:
|
||||||
|
* **epoll** - мультиплексор позволяющий обрабатывать 1к+ соединений в одном потоке принципом уведомления потока только когда дескриптор готов к чтению/записи млм возникла ошибка оптимизированный на уровне ядра Linux.
|
||||||
|
* **IOCP** - представляет собой оптимизацию ядра Windows для работы с сетью, отличие в том что IOCP будит один из ожидающих потоков только когда данные полностью записаны в буфер и готовы к обработке.
|
||||||
|
|
||||||
|
Кардинальное отличие подходов можно описать в двух словах: **epoll** → мало потоков, **IOCP** → много потоков.
|
||||||
|
#### Влияние парадигм работы с **epoll** и **IOCompletionPort** на аллокатор
|
||||||
|
С моей точки зрения как Linux-кодера и борца за эффективность IOCP имеет **жирнючий** минус - как правило это внушительный пул потоков ибо при их нехватке эффективность будет падать. Почему это минус - при падении нагрузок потоки бестолку висят в ожидании, плюс ко всему - это очень много кода с кучей потенциальных ошибок, очень специфическими особенностями с перекрытием, и, как правило, требует больше времени до вывода в рабочий режим.
|
||||||
|
Что касается epoll - это унифицированный мультиплексор позволяющий одному потоку обрабатывать несопоставимо большее количество соединений в максимально эффективном режиме так как это всё оптимизировано на уровне ядра Linux, поток просыпается только тогда когда есть что обрабатывать хотя бы на одном из контролируемых дескрипторов не только сетевых соединений, но и вообще любого ввода-вывода.
|
||||||
|
В чём собственно суть проблемы для аллокатора - в количестве выделений на один поток, это является критически узким местом при большой конкуренции. Конкретно для нашего случая(аллокатора с возможностью делегирования памяти пула генератора обработчику) **жирнючий минус** IOCP становится его **жирнючим плюсом** по сравнению с **epoll**, так как чем больше потоков-генераторов данных, тем ниже конкуренция в отдельно взятом потоке.
|
||||||
|
Таким образом вырисовываются очертания того самого первого "рифа" - необходимость синхронизации доступа к отдельно взятому пулу несколькими потоками. Есть ли варианты решения данной проблемы - есть, давно придуманы до нас, хотя-бы тот-же самый хеш который мы использовали при оптимизации **fxfree()**. Открываем любимую IDE и накидаем немного полей в структуру пула памяти :
|
||||||
|
```C
|
||||||
|
/// @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;
|
||||||
|
```
|
||||||
|
|
||||||
|
# 26.04.2026
|
||||||
|
Чтож, примерное представление о работе сети и нагрузках на асинхронный ввод-вывод мы получили, но, мы брали в рассчёт Lineage2 где достаточно высокая вариативность пакетов, теперь вернёмся к нашему проекту прикинем примерную вариативность пакетов, учтём железо на котором будет работать сервер и посмотрим сколько он сможет выдержать клиентов в теории. На что стоит обратить внимание:
|
||||||
|
* Железо
|
||||||
|
* Ресурсы потребляемые ОС
|
||||||
|
* Ресурсы потребляемые сторонними сервисами при их наличии(Web-сервер, почтовый сервер и т.д.)
|
||||||
|
* Минимальный необходимый запас прочности
|
||||||
|
|
||||||
|
## Железо:
|
||||||
|
Отсчётная точка любого инфраструктурного проекта
|
||||||
|
|Параметр|Значение|
|
||||||
|
|-------:|:-------|
|
||||||
|
|**OS:**|Ubuntu Server 24.04|
|
||||||
|
|**CPU:**|Intel Core i5‑3470, 4 @ 3.2 GHz|
|
||||||
|
|**RAM:**|8 GB|
|
||||||
|
|**ROM:**|noname 256 GB SSD|
|
||||||
|
|**WiFi:**|2.4 GHz, прямая видимость до 6 м (~32 Mbit/s)|
|
||||||
|
|
||||||
|
1. Инфраструктура:
|
||||||
|
|Параметр|Значение|Нагрузка на одного клиента||
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# README.md
|
||||||
|
|
||||||
|
## Сервер:
|
||||||
|
* OS: Ubuntu Server 24.04;
|
||||||
|
* CPU: Intel Core i5‑3470, 4 @ 3.2 GHz;
|
||||||
|
* RAM: 8 GB;
|
||||||
|
* ROM: noname 256 GB SSD;
|
||||||
|
* WiFi: 2.4 GHz, прямая видимость до 6 м (~32 Mbit/s).
|
||||||
|
|
||||||
|
## Вариативность траффика
|
||||||
|
|
||||||
|
## Расчёт и таблица результатов proxy-Lineage2 30 MBits/s
|
||||||
|
Для расчёта принята пиковая нагрузка на сеть
|
||||||
|
Параметр Значение Обоснование
|
||||||
|
Пропускная способность канала 30 000 kbit/s Исходные данные
|
||||||
|
Доступная полоса (80 %) 24 000 kbit/s Запас на служебный трафик
|
||||||
|
С учётом накладных расходов 21 600 kbit/s Поправка на заголовки и ошибки
|
||||||
|
Пиковый трафик на клиента 100 kbit/s Массовые события в Lineage 2
|
||||||
|
Макс. клиентов (прямой расчёт) 240 Без учёта накладных расходов сети
|
||||||
|
Макс. клиентов (реальный прогноз) 210 С запасом и поправкой на накладные расходы
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# PROGRESS.md
|
||||||
|
|
||||||
|
## Формат
|
||||||
|
* Даты следуют в обратном хронологическом порядке
|
||||||
|
* Формат таблиц: Статус → Задача → Краткое описание
|
||||||
|
|
||||||
|
## Условные обозначения
|
||||||
|
* ➤ — задача выполняется
|
||||||
|
* ✔️ — задача выполнена полностью
|
||||||
|
* ⚠️ — задача отложена
|
||||||
|
* ☐ — задача ожидает выполнения
|
||||||
|
|
||||||
|
Структура таблиц
|
||||||
|
|
||||||
|
| Статус | Задача | Описание |
|
||||||
|
|:------:|:-------|:---------|
|
||||||
|
|
||||||
|
|
||||||
|
## Общий прогресс
|
||||||
|
| Статус | Задача | Описание |
|
||||||
|
|:------:|:-------|:---------|
|
||||||
|
| ✔️ |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
|
||||||
|
| ➤ |**Проектирование архитектуры**|Определение узких мест, API, внутреннего устройства|
|
||||||
|
| ☐ |Logic|Реализация внутренней логики модуля|
|
||||||
|
| ☐ |API|Реализация внешнего интерфейса|
|
||||||
|
| ☐ |Предварительное тестирование|Юнит-тесты, бенчмарки|
|
||||||
|
| ☐ |Нагрузочные тесты|Анализ работы при максимальных нагрузках|
|
||||||
|
| ☐ |Интеграция|Тестирование взаимодействия с другими модулями|
|
||||||
|
| ☐ |Документирование|Подготовка технической документации модуля|
|
||||||
|
| ☐ |Итоги|Финальная проверка, анализ работы, фидбэк(если предусмотрен)|
|
||||||
|
|
||||||
|
|
||||||
|
## 23.04.2026
|
||||||
|
| Статус | Задача | Описание |
|
||||||
|
|:------:|:-------|:---------|
|
||||||
|
| ✔️ |Препрофилирование|Расчёт предполагаемых нагрузок малые/средние/максимальные|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# TODO.md
|
||||||
|
|
||||||
|
## Информация
|
||||||
|
* Файл для отслеживания текущих задач модуля **FXAlloc**
|
||||||
|
* Последнее обновление: [25.04.2026]
|
||||||
|
|
||||||
|
## Условные обозначения
|
||||||
|
* 🔥 - Наивысший приоритет
|
||||||
|
* 🔴 - Высокий приоритет
|
||||||
|
* 🟡 - Средний приоритет
|
||||||
|
* 🟢 - Низкий приоритет
|
||||||
|
|
||||||
|
## Задачи в процессе
|
||||||
|
|Срочность|Компонент|Задача|Описание|
|
||||||
|
|:-------:|:-------:|:-----|:-------|
|
||||||
|
|🔥|**FXAlloc**|**Проектирование архитектуры**|Интерфейс, внутреннее устройство|
|
||||||
|
|🔥|**FXAlloc**|**Документация**|Описание архитектуры решения, ведение DAIRY.md|
|
||||||
|
|🔥|**FXAlloc**|**Logic**|Реализации внутренних методов|
|
||||||
|
|🔥|**FXAlloc**|**API**|Реализация внешних методов|
|
||||||
|
|🔥|**FXAlloc**|**Тестирование**|Написание юнит-тестов|
|
||||||
|
|🔥|**FXAlloc**|**Оптимизация**|Анализ производительности|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "FXAlloc.h"
|
||||||
|
|
||||||
|
#include "mword.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 FXGradedMemoryPool {
|
||||||
|
/// @brief Указатель на последний свободный блок
|
||||||
|
FXMemoryBlock* free;
|
||||||
|
/// @brief Всего блоков в данной группе
|
||||||
|
umword_t total;
|
||||||
|
/// @brief Количество преаллоцированных блоков
|
||||||
|
umword_t count_pre;
|
||||||
|
/// @brief Количество используемых блоков
|
||||||
|
mword_t used;
|
||||||
|
/// @brief Количество свободных блоков
|
||||||
|
mword_t free;
|
||||||
|
} FXMemoryPoolGrade;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/types.h>
|
||||||
|
#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 Переменная для сохранения преднастроек пула памяти с ноль-терминантом
|
||||||
|
* Определена в src/FXAlloc.c
|
||||||
|
*/
|
||||||
|
extern const FXGrade* grades;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Функция выделения памяти
|
||||||
|
* @param[in] _NBytes: size_t - Количество байт
|
||||||
|
* @retval !0 - Кратный size_t указатель выровненный для любого типа данных
|
||||||
|
* @retval NULL - В случае единственно возможной ошибки EBADALLOC результат сохранён в errno
|
||||||
|
*/
|
||||||
|
void* fxalloc(size_t _NBytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Высвобождает память выделенную исключительно fxalloc
|
||||||
|
* @param[in] _Ptr: void*
|
||||||
|
*/
|
||||||
|
void fxfree(void* _Ptr);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif //__cplusplus
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#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
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#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
|
||||||
|
// RISC‑V 64: 64-битное машинное слово
|
||||||
|
typedef int64_t mword_t;
|
||||||
|
typedef uint64_t umword_t;
|
||||||
|
#elif __riscv_xlen == 32
|
||||||
|
// RISC‑V 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");
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "FXAlloc.h"
|
||||||
|
|
||||||
|
#include <threads.h>
|
||||||
|
|
||||||
|
// В этой переменной настраиваем градации и предположительное количесвто блоков
|
||||||
|
static const FXGrade grades[] = {
|
||||||
|
{ 32, 200 }, { 64, 200 }, { 128, 8000 },
|
||||||
|
{ 256, 4000 }, { 512, 2000 }, { 1024, 1200 },
|
||||||
|
{ 4096, 200 }, { 0x10000, 4 },
|
||||||
|
{ 0 } // Ноль-терминант
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
void* fastalloc();
|
||||||
|
|
||||||
|
void init_tls() {
|
||||||
|
pthread_setspecific(thread_key, (void*)fxalloc);
|
||||||
|
fxalloc = fastalloc;
|
||||||
|
// или более сложный вариант с хранением структуры
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user