Files
fxalloc/includes/FXAlloc.h
T
2026-05-18 02:08:57 +05:00

378 lines
25 KiB
C
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.
#pragma once
/**
* \mainpage
* \file fxalloc/includes/FXAlloc.h
* \author felex67 (admin@felexdev.ru)
* \version 1.0.0 dev-in-progress
*
* \brief Публичный интерфейс модуля аллокатора-профилировщика для серверных очередей/пакетов
*
* \details Language: C11 (ISO/IEC 9899:2011).
*
* SRP + KISS + YAGNI + HLP(High Level Performance)
*
* Лицензия: Apache 2.0
*
* \note Поддерживаемые компиляторы:
* - MSVC: Версия >= 1930(VisualStudio 22+)
* - GCC: Версия >= 5.0
* - CLang: Любая поддерживающая C-11
*
* \warning Изменять указатели `(*fxalloc)()/(*fxfree)()` строго запрещено!!!
*
* \note Для увеличения производительности необходимо заключение контракта: определение
* макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`. Без определения
* этого макроса модуль вынужден работать через прокси-функции что влечёт за собой
* снижение производительности(увеличение ~25 тактов на вызов `fxalloc/fxfree`).
*
* \note Режим `__FXALLOC_TURBO` работает только при неконстантных `fxalloc/fxfree`.
* В этом режиме недоступен глубокий анализ использования памяти. Особенности:
* - Метаданные для каждого блока уменьшаются до 16-ти байт.
* - Все блоки выравниваются по адресам кратным 16.
* - Все LIFO Грейдов выравниваются по L1(кэш процессора первого уровня). Настроить можно
* изменив `FXALLOC_LIFO_HEAD_ALIGN` на соответствующее Вашей архитектуре значение
*
* \note Первый вызов `fxalloc()`(без предварительного вызова `fxalloc_init()`) в
* потоке/процессе крайне медленный так как происходит инициализация пула,
* для потока/процесса, последующие вызовы будут работать с инициализированным пулом памяти.
* Для преднастройки пула потока используйте функцию `fxalloc_init()`.
*
* \note Теоретический максимальный размер блока `(1U << 32) - 25` → 4'294'967'271 байт.
* Рекомендуемый размер блока не более 64 КиБ.
*
* \note Профилированием и очисткой памяти потока занимается поток-наблюдатель, запускается
* при первом обращении к аллокатору и завершается только после очистки последнего пула, для обеспечения
* безаварийного завершения перед выходом из процесса используете fxalloc_finalize().
* Пул потока освобождается только когда все блоки возвращены потоку-владельцу или при вызове fxalloc_finalize().
*
* \p
* Алгоритмы использования:
* 1. `void* ptr = fxalloc(N) → работаем с ptr → fxfree(ptr) → fxalloc_cleanup()`
* - `fxalloc` → При первом вызове инициализирует пул в глобальной области с настройками
* по умолчанию для данного потока, вызывает `malloc`, устанавливает метаданные.
* - Работа с указателем: Можно передавать в любой поток.
* - `fxfree` → анализ метаданных с последующим возвратом блока потоку-владельцу без
* вызова `free()`, блок остаётся в пуле.
* - `fxalloc_cleanup()` → Запускает процесс очищения памяти занятой потоком.
* 2. `fxalloc_init(grades, threadName, FXSEARCH_...) → работаем → fxalloc_cleanup()`
* - `fxalloc_init` → Инициализирует пул в глобальной области для данного потока с
* переданными настройками: FXGrade*, ThreadName, eXFAllocSearchType.
* - `fxalloc_cleanup()` → Запускает процесс очищения памяти занятой потоком.
* Такое поведение помогает собрать статистику для профилирования. Данные об использовании
* памяти могут быть получены переводом аллокатора в режим анализа(выполняется потоком-наблюдателем).
*
* \p
* Рекомендации по использованию:
* Аллокатор в первую очередь рассчитан на рпофилирование с постепенным переходом в рабочий режим.
* Режимы профилирования можно переключать во время работы приложения как для конкретного потока(fxalloc_profile_thread),
* так и для всего приложения в целом(fxalloc_profile). Концепция использования:
* 1. Проверка на критические ошибки в вызывающем коде без определения мкросов:
* На этом этапе отлавливаются все попытки изменить указатели `fxalloc` и `fxfree`, попытки повторного
* высвобождения, а также передача "не родных" указателей(приложение моментально падает с SegmentationFault, создаётся
* файл `fxalloc.log` с текстовым описанием ошибки).
* 2. Определение макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`:
* исключает работу аллокатора через прокси-функции первичные проверки первого этапа, подразумевается что этап 1 пройден. Требуется перекомпияция проекта
* после первого этапа. Вэтом режиме как и в предыдущем доступен режим глубокого профилирования.
* 3. После первичных рабочих нагрузок и стабильной работы перекомпиляция проекта с определённым макросом __FXALLOC_TURBO. Максимально уменьшаются
* метаданные(до 16 байт, 2 указателя + выравнивание(если x86)), остаётся доступен только режим поверхностного анализа:
* блоков всего, праллоцированно и выделено дополнительно.
*
* \note Во всех режимах аллокатор работаетпо принципу LIFO на атомиках(без блокировок), все головы стеков выровнены по линии L1,
* адреса блоков и пользовательских данных кратны 16-ти.
* \p
* Подробное описание процесса разработки интерфейса и аллокатора в целом можно найти в файле:
* `neurox/ccpp/fxalloc/DIARY.md`
*
* */
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
#include <stdint.h>
#if (defined(__GNUC__) && (__GNUC__ >= 5)) || defined(__clang__)
#include <stdatomic.h>
#include <pthread.h>
#define TLS __thread
typedef pthread_mutex_t fxsync_t;
typedef pthread_t thread_id_t;
#elif defined(_MSC_VER) && _MSC_VER >= 1930
#include <stdatomic.h>
#include <windows.h>
#define TLS __declspec(thread)
typedef HANDLE fxsync_t;
typedef HANDLE thread_id_t;
#else
#error "Unsupported compiler. Only Clang, GCC >=5.0 and MSVC VS 2022+ support _Atomic in C11"
#endif
/*=================================================================================================
Установки по умолчанию
*=================================================================================================*/
/**
* @brief Это перечисление устанавливает настройки аллокатора по умолчанию
*
* @b FXALLOC_GRADE_STEP_SHIFT - шаг размера блоков, напрямую влияет на количество грейдов
* и суммарный размер метаданных всех гоейдов пула для каждого потока.
*
* @b FXALLOC_LIFO_HEAD_ALIGN - настраивается для вашего процессора по размеру
* кеша @b L1. Значение по умолчанию @b 64. Действет на весь процесс, для изменения необходима
* рекомпиляция.
*
* @b FXALLOC_MAX_GRADE_SIZE - Ограничитель максимаьного размера грейда
* Значение по умолчанию @b 0x10000 = @b 65'536 байт.
*
* @note
* @b Пример для @b FXALLOC_GRADE_STEP_SHIFT = 5, @b FXALLOC_MAX_GRADE_SIZE = 1024
* - Шаг размера: (1 << 5) = @b 32 байта: [32, 64, 96, 128...]
* - Количество грейдов: 1024 / 32 = @b 32
*/
typedef enum eFXAllocDefaultConfig {
/**
* Количество сдвигов определяющее шаг размера блока: (1 << 6) = 64\n
* Значение по умолчанию: @b 6.
*/
FXALLOC_GRADE_STEP_SHIFT = 6,
/**
* Выравние головы стека свободных блоков по линии кеша L1\n
* Значение по умолчанию: @b 64.
*/
FXALLOC_LIFO_HEAD_ALIGN = 64,
/**
* Ограничитель максимаьного размера блока\n
* Значение по умолчанию: @b 65'536 байт.
*/
FXALLOC_MAX_GRADE_SIZE = 0x10000,
} eFXAllocDefaultConfig;
/*=================================================================================================*/
/**
* @brief Варианты настройки алгоритма поиска подходящего грейда для данного потока
* В случае если первой в потоке вызывается функция @b `fxalloc` режим автоматически
* устанавливается в сдвиговый, т.к. Инициализируется пул согласно
* @b `FXALLOC_GRADE_STEP_SHIFT` в своём алгоритме функции сдвигового поиска опираются
* именно на это значение.
* В противном случае(первый вызов - `fxalloc_init`) вы можете сами задать тип поиска.
* Рекомендации под задачу:
* * Высокая вариативность - оставить градации по умолчанию откалибровав `FXALLOC_GRADE_STEP_SHIFT`
* * Низкая вариативность(очереди) - линейный поиск
* * Средняя вариативность(запросы и т.д.) - бираный поиск
*/
typedef enum eXFAllocSearchType {
FXSEARCH_AUTO, /// Выберется Бинарный/линейный в зависимости от длины массива градаций(>= 7)
FXSEARCH_LINEAR, /// Линейный поиск
FXSEARCH_BINARY, /// Бинарный поиск
FXSEARCH_SHIFTED, /// Поиск сдвигом вправо(`idx = (NBytes - 1) >> FXALLOC_GRADE_STEP_SHIFT;`)
} eXFAllocSearchType;
/**
* @brief Перечисление режимов работы аллокатора.
* @note Подробное описание статистики в см. в описании структуры `FXAllocGradeStat`
*/
typedef enum eFXAllocProfile {
FXALLOC_SPEED, ///< Максимальная производительность без статистики
FXALLOC_SUMMARY, ///< Поверхностная статистика
FXALLOC_FULL, ///< Глубокий анализ расхода памяти
FXALLOC_GETPROFILE, ///< Используется для получения текущего профиля
} eFXAllocProfile;
/**
* @brief Структура преднастройки аллокатора задающая градации и количество блоков памяти.
* Массив должен быть отсортирован по возрастанию размера блока
* и заканчиваться элементом с `est_size = 0`
* Поля:
* - +est_size: size_t - Предполагаемый размер блока;
* - +est_count: size_t - Предполагаемое количество блоков.
*/
typedef struct FXGrade {
const size_t est_size; ///< Предполагаемый размер блока
const size_t est_count; ///< Предполагаемое количество блоков
} FXGrade;
/**
* @brief Структура настройки потока
*
* - +name: const char*: Указатель на название потока. Может быть NULL.
*
* - +grades: const FXGrade: Указатель на массив настроенных грейдов. Может быть NULL.
*
* - +profile: eFXAllocProfile: Режим потока один из:
* - FXALLOC_SPEED
* - FXALLOC_SUMMARY
* - FXALLOC_SPEED.
*
* - +search_type: eFXSearchType: Алгоритм поиска подходящего блока, один из:
* - FXSEARCH_AUTO
* - FXSEARCH_LINEAR
* - FXSEARCH_BINARY
* - FXSEARCH_SHIFTED
*/
typedef struct FXAllocSetupThread {
const char* name; /// Название потока
const FXGrade* grades; /// Указатель на массив с настройками градаций
eFXAllocProfile profile; /// Профиль
eXFAllocSearchType search_type; /// Тип поиска
} FXAllocSetupThread;
/**
* @brief Инициализирует локальный пул памяти исходя из заданных параметров блоков
* @note Блокирующий вызов
* @param[in] Options: FXAllocSetupThread*: Указатель на структуру настроек.
*
* @returns int - внутренний идентификатор потока(порядковый номер), либо -1 в случае ошибки
* @retval (int)-1 - Ошибка, данные сохраняются в errno(ENOMEM || EINVAL), описание ошибки можно получить
* используя strerror(errno)
* @retval (int)>= 0 - порядковый номер потока использующего аллокатор FXAlloc
*/
int fxalloc_init(FXAllocSetupThread* Options);
#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 TLS void (*const fxalloc)(size_t NBytes);
#else
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
*/
extern TLS 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 TLS void (*const fxfree)(void* Ptr);
#else
/**
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
* при использовании на любом другом указателе 100% неопределённое поведение
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] Ptr: void* - Указатель на блок памяти
*/
extern TLS void (*fxfree)(void* Ptr);
#endif
/**
* @brief Высвобождает ресурсы занятые потоком. Вызывать непосредственно перед выходом
* из потока/процесса, в противном случае - `UB` или `segfault`
*/
void fxalloc_cleanup();
/**
* @brief Структурная единица отчёта по каждому грейду
*
* @note В режиме @b `FXALLOC_SPEED` статистика не собирается
*
* Статистика по режимам:
* - @b FXALLOC_SUMMARY:
* - +block_size: uint32_t: размер блока(грейд)
* - +blocks_total: uint32_t: всего блоков
* - +blocks_preallocated: uint32_t: преаллоцировано блоков
* - +blocks_malloced: uint32_t: дополнительно выделено блоков
* - @b FXALLOC_FULL (дополнительно к SUMMARY):
* - +blocks_used: uint32_t: используется на данный момент
* - +data_min: uint32_t: минимальное использование
* - +data_max: uint32_t: максимальное использование
* - +data_avg: uint32_t: арифметическая средняя по грейду
*/
typedef struct FXAllocGradeStat {
/* FXALLOC_SUMMARY + FXALLOC_FULL */
uint32_t block_size; /// Размер блока(грейд)
uint32_t blocks_total; /// Всего блоков
uint32_t blocks_prealloced; /// Преаллоцировано блоков
uint32_t blocks_malloced; /// Блоков выделенных дополнительно
/* FXALLOC_FULL */
uint32_t blocks_used; /// Блоков используется
uint32_t data_min; /// Минимальный размер данных
uint32_t data_max; /// Максимальный размер данных
uint32_t data_avg; /// Средняя арифметическая `(data_1 + ... + data_N) / total` bytes
} FXAllocGradeStat;
/**
* @brief Структурная елиница отчёта по потокам
* - +thread_name: const char*: Название потока
* - +profile: FXAllocGradeStat*: Данные профилирования по каждому грейду
* - +thread_id: size_t: Внутренний идентификатор потока(вощвращается fxalloc_init)
* - +ngrades: size_t: Количество градаций.
*/
typedef struct FXAllocStatistics {
const char* thread_name; /// Название потока
FXAllocGradeStat* profile; /// Данные профилирования по каждому грейду
size_t thread_id; /// Внутренний идентификатор потока
size_t ngrades; /// Количество градаций
} FXAllocStatistics;
/**
* @brief Переключает режимы работы алокатора для всего роцесса
*
* @param Profile: eFXAllocProfile: режим профилирования
* `FXALLOC_SPEED` - Максимальная производительность без статистики
* `FXALLOC_SUMMARY` - Поверхностная статистика
* `FXALLOC_FULL` - Глубокий анализ расхода памяти
*
* @param Callback: void(*)(FXAllocStatistics*): указатель на функцию-обработчик данных.
* При передаче NULL аргумент Profile будет проигнорирован.
*
* @param MemoryState: FXAllocStatistics**: Массив указателей на статиистику для каждого грейда. Нулевой
* указатель в массиве обозначает конецмассива: MemoryState[N] == NULL. После обработки данных профилирования
* следует использовать free(MemoryState) для освобождения памяти.
*
* @note При `Profile != FXALLOC_SPEED && Callback == NULL` возвращает `(int)0`, `errno` устанавоивается в `EINVAL`
*
* @return int
* @retval `1` при успешном переключении
* @retval `0` при ошибке, errno == EINVAL, описание strerror(errno).
*/
int fxalloc_profile(eFXAllocProfile Profile, void(*Callback)(FXAllocStatistics** MemoryState));
/**
* @brief Переключает режим работы алокатора для потока указанного в ThreadID
*
* @param ThreadID: int: Внутренний идентификатор потока возвращённый функцией fxalloc_init()
*
* @param Profile: eFXAllocProfile: режим профилирования
* `FXALLOC_SPEED` - Максимальная производительность без статистики
* `FXALLOC_SUMMARY` - Поверхностная статистика
* `FXALLOC_FULL` - Глубокий анализ расхода памяти
*
* @param Callback: void(*)(FXAllocStatistics*): указатель на функцию-обработчик данных.
* При передаче NULL аргумент Profile будет проигнорирован.
*
* @param MemoryState: FXAllocStatistics**: Массив указателей на статиистику для каждого грейда. Нулевой
* указатель в массиве обозначает конецмассива: MemoryState[N] == NULL. После обработки данных профилирования
* следует использовать free(MemoryState) для освобождения памяти.
*
* @note При `Profile != FXALLOC_SPEED && Callback == NULL` возвращает `(int)0`, `errno` устанавоивается в `EINVAL`
*
* @return int
* @retval `1` при успешном переключении
* @retval `0` при ошибке, errno == EINVAL, описание strerror(errno).
*/
int fxalloc_profile_thread(int ThreadID, eFXAllocProfile Profile, void(*Callback)(FXAllocStatistics** MemoryState));
/**
* @brief Функция очистки всей занятой памяти.
* @warning Блокирующий вызов, должен быть вызван перед завершением процесса/приложения!
*
*/
void fxalloc_finalize();
#ifdef __cplusplus
}
#endif //__cplusplus