Работа над дневником, запись 05.05.2026
This commit is contained in:
@@ -757,7 +757,7 @@ l2header* spell_teleport_unchant(void* _RawPacket, SessKey* _Key) {
|
|||||||
```C
|
```C
|
||||||
/**
|
/**
|
||||||
* @author всё ещё admin@felexdev.ru
|
* @author всё ещё admin@felexdev.ru
|
||||||
* @version +-∞
|
* @version ±∞
|
||||||
*
|
*
|
||||||
* @note Алиса, добавь пожалуйста к предыдущим расчётам затраты на клонирование гномиков в БД в процессорном\
|
* @note Алиса, добавь пожалуйста к предыдущим расчётам затраты на клонирование гномиков в БД в процессорном\
|
||||||
* времени, необходимо понять сколько summon-ов нужно для переселения 163961 деревень за секунду
|
* времени, необходимо понять сколько summon-ов нужно для переселения 163961 деревень за секунду
|
||||||
@@ -1122,3 +1122,67 @@ typedef struct ThreadMemoryBlock {
|
|||||||
*(globalPoll.threads[tid].fxalloc) = fxalloc_summary;
|
*(globalPoll.threads[tid].fxalloc) = fxalloc_summary;
|
||||||
*(globalPoll.threads[tid].fxfree) = fxfree_summary;
|
*(globalPoll.threads[tid].fxfree) = fxfree_summary;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# 05.05.2026
|
||||||
|
|
||||||
|
Времени вчера оказалось маловато, так и не успели закончить с интерфейсом, на чём мы остановились: использование TLS-переменных-указателей на функции выделения/высвобождения памяти.
|
||||||
|
|
||||||
|
## Дополнительные заметки
|
||||||
|
|
||||||
|
Из своего опыта разработки(не смотря на то что это было лишь хобби) могу сказать следующее: даже если вы напишите супер-продвинутый модуль, он не будет защищён от некорректного использования, всегда найдутся теб кто решит что он умнее Вас. Как это относится к нашему случаю, да очень просто,— эти переменные могут быть изменены в любом месте вызывающего кода, что повлечёт за собой неопределённое поведение а самое страшное - утечки памяти. Есть ли защита от этого — нет, невозможно полностью защититься, хотя способы минимизировать подобные случаи имеются, пример для `fxalloc`:
|
||||||
|
### Прокси-функция:
|
||||||
|
```C
|
||||||
|
/* includes/FXAlloc.h: публичный интерфейс */
|
||||||
|
extern thread_local void* (*const fxalloc)(size_t NBytes);
|
||||||
|
|
||||||
|
/* headers/_FXAlloc.h: внутренний интерфейс */
|
||||||
|
static void* fxalloc_proxy(size_t NBytes); ///< функция-прокси
|
||||||
|
static thread_local void* (*current_alloc)(size_t);///< указатель на действующую функцию алокации
|
||||||
|
|
||||||
|
/* src/FXAlloc.c: реализация модуля */
|
||||||
|
void* (*const fxalloc)(size_t) = fxalloc_proxy;
|
||||||
|
void* fxalloc_proxy(size_t NBytes) {
|
||||||
|
/* всё что нужно сделать - вызвать текущую функцию алокации */
|
||||||
|
return current_alloc(NBytes);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Что это даёт: практически 100% гарантию что никто и никогда не изменит указатель `fxalloc`, ибо в противном случае будет ошибка компиляции, однако **огромным минусом** данного способа защиты является вызов дополнительной функции, который сразу накидывает ±25 тактов за раз. Это критично для случая работы с памятью и сводит на нет все нашы намерения организовать атомарную синхронизацию так как на весах каждая инструкция процессора.
|
||||||
|
|
||||||
|
### Контракт разработчика(CP)
|
||||||
|
|
||||||
|
Что подразумевает под собой контракт разработчика(*англ. "Contract Programming": программирование с контрактом*) — код использующий модуль должен соблюдать правила установленные разработчиком этого самого модуля, это одна из основных парадигм системного программирования, да и в целом программирования на Си. Как это влияет на наш случай: мы можем сделать условную компиляцию через макрос без определения которого будут возникать ошибки компиляции так как код внутри модуля будет пытаться изменить константные указатели на функции. Таким образом, для компиляции модуля разработчик использующий этот модуль просто обязан будет физически внести изменения в испоняемый файл(определить макрос) или заголовочный(исправить условную компиляцию), что автоматически снимает с нас ответственность как с разработчиков модуля. Ну а для пущего эфекта назовём макрос длинной фразой с прямым посылом — `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`(Я понимаю что никогда не должен изменять эти указатели) и дополнительно в коментариях к модулю и каждому указателю укажем — **Изменение этих указателей строго запрещено!!!**. Это не гарантирует полной защиты от дурака, зато снимает с нас ответственность: **мы сделали всё что могли** с сохранением скорости вызова нужной функции алокации/высвобождения.
|
||||||
|
|
||||||
|
### CP + proxy
|
||||||
|
|
||||||
|
Пожалуй, это самый логичный способ защитить модуль от некоректного использования. При компиляции с `const`-указателями работаем через прокси, в противном случае - включаем 3-ю космическую. Пример реализации такого подхода приведён ниже:
|
||||||
|
```C
|
||||||
|
/* includes/FXAlloc.h */
|
||||||
|
#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 thread_local void (*const fxalloc)(size_t NBytes);
|
||||||
|
#else
|
||||||
|
/**
|
||||||
|
* @brief Указатель на функцию выделения памяти
|
||||||
|
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
||||||
|
* @param[in] NBytes: size_t - Количество байт
|
||||||
|
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
|
||||||
|
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
|
||||||
|
*/
|
||||||
|
extern thread_local void (*fxalloc)(size_t NBytes);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* headers/_FXAlloc.c */
|
||||||
|
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
|
||||||
|
void* (*const fxalloc)(size_t) = fxalloc_proxy; ///< работает через `current_alloc`
|
||||||
|
#else
|
||||||
|
void* (*fxalloc)(size_t) = fxalloc_init_malloc; ///< работает прямым вызовом
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
Рабочие функции(вроде `fxalloc_init_alloc`) не знают о существовании дополнительной переменной, им без разницы кто их вызывает, они выполняют свои прямые обязанности несмотря на контекст.
|
||||||
+40
-21
@@ -6,6 +6,10 @@
|
|||||||
* @brief Публичный интерфейс модуля-аллокатора fxalloc(includes/FXAlloc.h)
|
* @brief Публичный интерфейс модуля-аллокатора fxalloc(includes/FXAlloc.h)
|
||||||
*
|
*
|
||||||
* @details Language: C11 (ISO/IEC 9899:2011).
|
* @details Language: C11 (ISO/IEC 9899:2011).
|
||||||
|
* Теоритический маскимальный размер блока `(1 << 32) - 25 = 4 294 967 271 байт`
|
||||||
|
* Первый вызов `fxalloc()`(без предварительного вызова `fxalloc_init()`) в потоке/процессе крайне медленный
|
||||||
|
* так как происходит инициализация пула, последующие вызовы будут работать с инициализированным
|
||||||
|
* пулом памяти.
|
||||||
* Изначально аллокатор работает в следующем режиме:
|
* Изначально аллокатор работает в следующем режиме:
|
||||||
* `fxalloc → выделение блока через malloc() с добавлением метаданных`,
|
* `fxalloc → выделение блока через malloc() с добавлением метаданных`,
|
||||||
* `fxfree → анализ метаданных с последующим вызовом free()`. Такое поведение
|
* `fxfree → анализ метаданных с последующим вызовом free()`. Такое поведение
|
||||||
@@ -16,17 +20,19 @@
|
|||||||
* передачи данных по очередям между потоками без повторных выделений, функция `fxfree()`
|
* передачи данных по очередям между потоками без повторных выделений, функция `fxfree()`
|
||||||
* из любого другого потока вернёт блок владельцу без накладных расходов на TLS, только
|
* из любого другого потока вернёт блок владельцу без накладных расходов на TLS, только
|
||||||
* атомарная синхронизация.\
|
* атомарная синхронизация.\
|
||||||
* Изменение указателей `fxalloc` и `fxfree` строго запрещено! Без оперделения макроса
|
* Изменение указателей `fxalloc` и `fxfree` строго запрещено!\
|
||||||
* `_I_UNDERSTAND_THE_RISKS_AND_ASSUME_RESPONSIBILITY_` возникнет ошибка компиляции.\
|
* Без оперделения макроса `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_` модуль
|
||||||
|
* не скомпилируется, возникнет ошибка компиляции, определение макроса = подписание конракта.\
|
||||||
* По завершению работы потока/процесса в системах POSIX вся выделенная память
|
* По завершению работы потока/процесса в системах POSIX вся выделенная память
|
||||||
* освобождается автоматически, в Windows необходимо вызвать `fxalloc_cleanup()`.
|
* освобождается автоматически, в Windows необходимо вызвать `fxalloc_cleanup()`.
|
||||||
* */
|
* */
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <threads.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32 // Windows
|
#ifdef _WIN32 // Windows
|
||||||
|
#include <Windows.h>
|
||||||
#define thread_local __declspec(thread)
|
#define thread_local __declspec(thread)
|
||||||
#else // Linux
|
#else // Linux
|
||||||
|
#include <threads.h>
|
||||||
#define thread_local __thread
|
#define thread_local __thread
|
||||||
#endif //_WIN32
|
#endif //_WIN32
|
||||||
|
|
||||||
@@ -37,7 +43,7 @@ extern "C" {
|
|||||||
/**
|
/**
|
||||||
* @brief Перечисление режимов работы аллокатора
|
* @brief Перечисление режимов работы аллокатора
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum eFXAllocProfile {
|
||||||
FXALLOC_SPEED, ///< Максимальная производительность без статистики
|
FXALLOC_SPEED, ///< Максимальная производительность без статистики
|
||||||
FXALLOC_SUMMARY, ///< Поверхностная статистика
|
FXALLOC_SUMMARY, ///< Поверхностная статистика
|
||||||
FXALLOC_FULL ///< Глубокий анализ расхода памяти
|
FXALLOC_FULL ///< Глубокий анализ расхода памяти
|
||||||
@@ -51,19 +57,17 @@ extern "C" {
|
|||||||
* @var +est_count: size_t - Предполагаемое количество блоков
|
* @var +est_count: size_t - Предполагаемое количество блоков
|
||||||
*/
|
*/
|
||||||
typedef struct FXGrade {
|
typedef struct FXGrade {
|
||||||
/// Предполагаемый размер блока
|
const size_t est_size; ///< Предполагаемый размер блока
|
||||||
const size_t est_size;
|
const size_t est_count; ///< Предполагаемое количество блоков
|
||||||
/// Предполагаемое количество блоков
|
|
||||||
const size_t est_count;
|
|
||||||
} FXGrade;
|
} FXGrade;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Переключает режимы работы алокатора
|
* @brief Переключает режимы работы алокатора
|
||||||
* FXALLOC_SPEED, ///< Максимальная производительность без статистики
|
* `FXALLOC_SPEED` - Максимальная производительность без статистики
|
||||||
* FXALLOC_SUMMARY, ///< Поверхностная статистика
|
* `FXALLOC_SUMMARY` - Поверхностная статистика
|
||||||
* FXALLOC_FULL ///< Глубокий анализ расхода памяти
|
* `FXALLOC_FULL` - Глубокий анализ расхода памяти
|
||||||
* @retval !0 при успешном переключении
|
* @retval `1` при успешном переключении
|
||||||
* @retval 0 при ошибке(не вероятно)
|
* @retval `0` при ошибке(не вероятно)
|
||||||
*/
|
*/
|
||||||
int fxalloc_profile(eFXAllocProfile Profile);
|
int fxalloc_profile(eFXAllocProfile Profile);
|
||||||
|
|
||||||
@@ -72,9 +76,21 @@ extern "C" {
|
|||||||
* @param[in] Grades: const FXGrade* - Указатель на массив градаций
|
* @param[in] Grades: const FXGrade* - Указатель на массив градаций
|
||||||
* @param[in] ThreadName: const char* - Наименование потока, используется при профилировании
|
* @param[in] ThreadName: const char* - Наименование потока, используется при профилировании
|
||||||
* в следующем виде: `[thread_id] 'thread_name': blocks: total=1024 used=64...`.
|
* в следующем виде: `[thread_id] 'thread_name': blocks: total=1024 used=64...`.
|
||||||
* Если передан NULL - выводится только ID потока, т.е.: `[thread_id]: ...`
|
* Если передан `NULL` - выводится только ID потока, т.е.: `[thread_id]: ...`
|
||||||
*/
|
*/
|
||||||
int fxalloc_init(const FXGrade* Grades, const char* ThreadName);
|
int fxalloc_init(const FXGrade* Grades, const char* ThreadName);
|
||||||
|
|
||||||
|
#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 thread_local void (*const fxalloc)(size_t NBytes);
|
||||||
|
#else
|
||||||
/**
|
/**
|
||||||
* @brief Указатель на функцию выделения памяти
|
* @brief Указатель на функцию выделения памяти
|
||||||
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
||||||
@@ -82,27 +98,30 @@ extern "C" {
|
|||||||
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
|
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
|
||||||
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
|
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
|
||||||
*/
|
*/
|
||||||
#ifndef _I_UNDERSTAND_THE_RISKS_AND_ASSUME_RESPONSIBILITY_
|
|
||||||
extern thread_local void (*const fxalloc)(size_t NBytes);
|
|
||||||
#else
|
|
||||||
extern thread_local void (*fxalloc)(size_t NBytes);
|
extern thread_local void (*fxalloc)(size_t NBytes);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
|
||||||
/**
|
/**
|
||||||
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
|
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
|
||||||
* при использовании на любом другом указателе 100% неопределённое поведение
|
* при использовании на любом другом указателе 100% неопределённое поведение
|
||||||
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
||||||
* @param[in] _Ptr: void* - Указатель на блок памяти
|
* @param[in] Ptr: void* - Указатель на блок памяти
|
||||||
*/
|
*/
|
||||||
#ifndef _I_UNDERSTAND_THE_RISKS_AND_ASSUME_RESPONSIBILITY_
|
|
||||||
extern thread_local void (*const fxfree)(void* Ptr);
|
extern thread_local void (*const fxfree)(void* Ptr);
|
||||||
#else
|
#else
|
||||||
|
/**
|
||||||
|
* @brief Указатель на функцию высвобождения памяти выделенной исключительно fxalloc
|
||||||
|
* при использовании на любом другом указателе 100% неопределённое поведение
|
||||||
|
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
|
||||||
|
* @param[in] Ptr: void* - Указатель на блок памяти
|
||||||
|
*/
|
||||||
extern thread_local void (*fxfree)(void* Ptr);
|
extern thread_local void (*fxfree)(void* Ptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Высвобождает ресурсы занятые потоком Вызывать непосредственно перед выходом,
|
* @brief Высвобождает ресурсы занятые потоком. Вызывать непосредственно перед выходом
|
||||||
* в противном случае UB или segfault
|
* из потока/процесса, в противном случае - `UB` или `segfault`
|
||||||
*/
|
*/
|
||||||
void fxalloc_cleanup();
|
void fxalloc_cleanup();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user