Files
fxalloc/includes/FXAlloc.h
T

204 lines
14 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
/**
* @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).
*
* Теоретический маскимальный размер блока `(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_`:
* В случае когда макрос определён модульне может изменять `(*fxalloc)()/(*fxfree)()` напрямую
* и вынужден работать через прокси-функцию, что добавляет +-25 такстов к каждому вызову
* пользователем `(*fxaloc)()/(*fxfree)()`, т.к. оба указывают на прокси-функции. При
* рпределении макроса необходимость в проксировании отсутствует, т.к. модуль может менять
* `(*fxaloc)()/(*fxfree)()` напрямую. Однако даже при определении макроса пользователь никогда
* ни в коем случае не должен изменять эти указатели!!!
* Определение макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_` равно
* подписанию контракта. Если в дальнейшем код пользователя изменит любой из этих указателей
* вся вина лежит исключительно на нём!
*
* По завершению работы потока/процесса в системах POSIX вся выделенная память
* освобождается автоматически, в Windows необходимо вызвать `fxalloc_cleanup()`.
*
* Подробное описание процесса разработки интерфейса и аллокатора вцелом можно найти в файле:
* `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 Перечисление режимов работы аллокатора
*/
typedef enum eFXAllocProfile {
FXALLOC_SPEED, ///< Максимальная производительность без статистики
FXALLOC_SUMMARY, ///< Поверхностная статистика
FXALLOC_FULL, ///< Глубокий анализ расхода памяти
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 - Предполагаемое количество блоков
*/
typedef struct FXGrade {
const size_t est_size; ///< Предполагаемый размер блока
const size_t est_count; ///< Предполагаемое количество блоков
} FXGrade;
/**
* @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* - Наименование потока, используется при профилировании
* @param[in] SearchType: eXFAllocSearchType - Тип поиска по градациям
* в следующем виде: `[thread_id] 'thread_name': blocks: total=1024 used=64...`.
* Если передан `NULL` - выводится только ID потока, т.е.: `[thread_id]: ...`
*/
int fxalloc_init(const FXGrade* Grades, const char* ThreadName, eXFAllocSearchType SearchType);
#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();
#ifdef __cplusplus
}
#endif //__cplusplus