diff --git a/includes/FXAlloc.h b/includes/FXAlloc.h index bbd09ca..8072b6b 100644 --- a/includes/FXAlloc.h +++ b/includes/FXAlloc.h @@ -1,41 +1,51 @@ #pragma once /** - * @file fxalloc/includes/FXAlloc.h - * @author felex67 (admin@felexdev.ru) - * @version 1.0.0 dev-in-progress + * \mainpage + * \file fxalloc/includes/FXAlloc.h + * \author felex67 (admin@felexdev.ru) + * \version 1.0.0 dev-in-progress * - * @brief Публичный интерфейс модуля аллокатора-профилировщика для серверных очередей/пакетов + * \brief Публичный интерфейс модуля аллокатора-профилировщика для серверных очередей/пакетов * - * @details Language: C11 (ISO/IEC 9899:2011). + * \details Language: C11 (ISO/IEC 9899:2011). * * SRP + KISS + YAGNI + HLP(High Level Performance) * - * @note Поддерживаемые компиляторы: + * Лицензия: Apache 2.0 + * + * \note Поддерживаемые компиляторы: * - MSVC: Версия >= 1930(VisualStudio 22+) * - GCC: Версия >= 5.0 - * - CLang: Любая + * - CLang: Любая поддерживающая C-11 * - * @warning Изменять указатели `(*fxalloc)()/(*fxfree)()` строго запрещено!!! - * @note Для увеличения производительности необходимо заключение контракта: определение + * \warning Изменять указатели `(*fxalloc)()/(*fxfree)()` строго запрещено!!! + * + * \note Для увеличения производительности необходимо заключение контракта: определение * макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`. Без определения * этого макроса модуль вынужден работать через прокси-функции что влечёт за собой * снижение производительности(увеличение ~25 тактов на вызов `fxalloc/fxfree`). * - * @note Режим `__FXALLOC_TURBO` работает только при неконстантных `fxalloc/fxfree`. + * \note Режим `__FXALLOC_TURBO` работает только при неконстантных `fxalloc/fxfree`. * В этом режиме недоступен глубокий анализ использования памяти. Особенности: * - Метаданные для каждого блока уменьшаются до 16-ти байт. * - Все блоки выравниваются по адресам кратным 16. * - Все LIFO Грейдов выравниваются по L1(кэш процессора первого уровня). Настроить можно * изменив `FXALLOC_LIFO_HEAD_ALIGN` на соответствующее Вашей архитектуре значение * - * @note Первый вызов `fxalloc()`(без предварительного вызова `fxalloc_init()`) в + * \note Первый вызов `fxalloc()`(без предварительного вызова `fxalloc_init()`) в * потоке/процессе крайне медленный так как происходит инициализация пула, * для потока/процесса, последующие вызовы будут работать с инициализированным пулом памяти. * Для преднастройки пула потока используйте функцию `fxalloc_init()`. * - * @note Теоретический максимальный размер блока `(1U << 32) - 25` → 4'294'967'271 байт. + * \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` → При первом вызове инициализирует пул в глобальной области с настройками @@ -51,9 +61,28 @@ * Такое поведение помогает собрать статистику для профилирования. Данные об использовании * памяти могут быть получены переводом аллокатора в режим анализа(выполняется потоком-наблюдателем). * + * \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` + * * */ @@ -79,9 +108,69 @@ extern "C" { #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 Перечисление режимов работы аллокатора + * @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, ///< Максимальная производительность без статистики @@ -90,51 +179,13 @@ extern "C" { FXALLOC_GETPROFILE, ///< Используется для получения текущего профиля } eFXAllocProfile; - /** - * @brief Задаёт шаг градаций по умолчанию используемый в изначальной версии - * `fxalloc()`. Градации будут заполнены для блоков с шагом в - * `1 << FXALLOC_DEFAULT_GRADE_STEP_SHIFT` до размера 65 535 байт(~1024 грейда), - * все блоки будут сохраняться в LIFO каждого грейда до конца работы потока. - * Такой режим предусмотрен для профилирования. - * @note Изменение шага напрямую влияет на количество грейдов и размер метаданных - * при увеличении на 1(7): шаг грейда - 128 байт, размер пула - 512 грейдов и т.д. - * при уменьшении на 1(5): шаг грейда - 32 байта, размер пула - 2048 грейдов и т.д. - * @details Если установить данный параметр 0 будет недоступен режим полного - * профилирования, статистика будет содержать только `malloced = N times`, - * `average_size = N bytes`, `min = N bytes` и `max = N bytes`. - * В случае по умолчанию можно будет получить более подробную информацию по - * каждому грейду и использованию памяти в нём. Не рекомендуется снижать параметр, - * т.к. это напрямую повлияет на размер метаданных пула. - */ - typedef enum eFXAllocConfig { - FXALLOC_GRADE_STEP_SHIFT = 6,///< left bit shifts (1 << 6) = 64 - hf - FXALLOC_LIFO_HEAD_ALIGN = 64,///< Задаёт выравнивание LIFO по L1 cache - } eFXAllocConfig; - /** - * @brief Варианты настройки алгоритма поиска грейдов для данного потока - * В случае если первой в потоке вызывается функция `fxalloc` режим автоматически - * устанавливается в сдвиговый, т.к. Инициализируется пул согласно - * `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 Структура преднастройки аллокатора задающая градации и количество блоков памяти. * Массив должен быть отсортирован по возрастанию размера блока * и заканчиваться элементом с `est_size = 0` - * @var +est_size: size_t - Предполагаемый размер блока - * @var +est_count: size_t - Предполагаемое количество блоков + * Поля: + * - +est_size: size_t - Предполагаемый размер блока; + * - +est_count: size_t - Предполагаемое количество блоков. */ typedef struct FXGrade { const size_t est_size; ///< Предполагаемый размер блока @@ -142,24 +193,41 @@ extern "C" { } FXGrade; /** - * @brief Переключает режимы работы алокатора - * `FXALLOC_SPEED` - Максимальная производительность без статистики - * `FXALLOC_SUMMARY` - Поверхностная статистика - * `FXALLOC_FULL` - Глубокий анализ расхода памяти - * @retval `1` при успешном переключении - * @retval `0` при ошибке(не вероятно) - */ - int fxalloc_profile(eFXAllocProfile Profile); + * @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 Инициализирует локальный пулл памяти исходя из заданных параметров блоков - * @param[in] Grades: const FXGrade* - Указатель на массив градаций - * @param[in] ThreadName: const char* - Наименование потока, используется при профилировании - * @param[in] SearchType: eXFAllocSearchType - Тип поиска по градациям - * в следующем виде: `[thread_id] 'thread_name': blocks: total=1024 used=64...`. - * Если передан `NULL` - выводится только ID потока, т.е.: `[thread_id]: ...` + * @brief Инициализирует локальный пул памяти исходя из заданных параметров блоков + * @note Блокирующий вызов + * @param[in] Options: FXAllocSetupThread*: Указатель на структуру настроек. + * + * @returns int - внутренний идентификатор потока(порядковый номер), либо -1 в случае ошибки + * @retval (int)-1 - Ошибка, данные сохраняются в errno(ENOMEM || EINVAL), описание ошибки можно получить + * используя strerror(errno) + * @retval (int)>= 0 - порядковый номер потока использующего аллокатор FXAlloc */ - int fxalloc_init(const FXGrade* Grades, const char* ThreadName, eXFAllocSearchType SearchType); + int fxalloc_init(FXAllocSetupThread* Options); #ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_ /** @@ -206,26 +274,104 @@ extern "C" { void fxalloc_cleanup(); /** - * @brief Структурная единица отчёта + * @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 { - uint32_t block_size; ///< Размер блока(грейд) - uint32_t blocks_total; ///< Всего блоков - uint32_t blocks_prealloced; ///< Преаллоцировано блоков - uint32_t blocks_malloced; ///< Блоков выделенных дополнительно - uint32_t blocks_used; ///< Блоков используется - uint32_t data_min; ///< Минимальный размер данных - uint32_t data_max; ///< Максимальный размер данных - uint32_t data_avg; ///< Средняя арифметическая `(data_1 + ... + data_N) / total` bytes + /* 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; ///< + 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 \ No newline at end of file