#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 #if (defined(__GNUC__) && (__GNUC__ >= 5)) || defined(__clang__) #include #include #define TLS __thread typedef pthread_mutex_t fxsync_t; typedef pthread_t thread_id_t; #elif defined(_MSC_VER) && _MSC_VER >= 1930 #include #include #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