Безопасно ли выделение массивов атомов с помощью системных вызовов виртуальной памяти? - PullRequest
0 голосов
/ 04 декабря 2018

Я разрабатываю базу данных в памяти, и моей системе нужен большой массив std::atomic_int объектов, которые примерно действуют как блокировки для записей базы данных.Теперь я бы предпочел распределить эти блокировки с помощью системных вызовов VM, таких как mmap в Unix-подобных системах и VirtualAlloc в Win32 / 64.Для этого есть несколько причин, и только одна из них не нуждается в явной инициализации памяти (т. Е. Память, выделенная системными вызовами ВМ, гарантированно обнуляется ОС).Итак, я бы хотел сделать это:

#include <sys/mman.h>
#include <atomic>

// ...
size_t numberOfLocks = ... some large number ...;
std::atomic_int* locks = reinterpret_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0));
// ... use locks[i].load() or locks[i].store() as with any memory order as appropriate

Мой главный вопрос - безопасен ли этот код?Я бы интуитивно ожидал, что код будет работать на любой разумной платформе с современным компилятором: mmap гарантированно вернет память, выровненную по границе страницы виртуальной машины, поэтому должны соблюдаться все требования выравнивания std::atomic_int и конструктор std::atomic_int не инициализирует значение, поэтому нет опасности не вызывать конструктор, так как длинные операции чтения и записи реализуются разумным способом (например, с использованием встроенных __atomic_* GCC и clang).

Однако,Я могу себе представить, что этот код не обязательно является безопасным в соответствии со стандартом C ++ - я прав, думая, что?Если это правильно, есть ли какая-либо проверка, чтобы, если код успешно компилировался на целевой платформе (т. Е. Если реализация std::atomic_int - это то, что я ожидаю), то все работает так, как я ожидаю?

В связи с этим, я ожидаю, что следующий код, где std::atomic_int не выровнен по свойству, сломается на x86:

uint8_t* region = reinterpret_cast<uint8_t*>(mmap(...));
std::atomic_int* lock = reinterpret_cast<std::atomic_int*>(region + 1);
lock->store(42, std::memory_order_relaxed);

Причина, по которой я думаю, что это не должно работать, заключается в том, что разумная реализацияstd::atomic_int::store с std::memory_order_relaxed на x86 - это просто нормальное движение, которое гарантированно будет атомарным только для доступа с выравниванием по словам.Если я прав в этом, могу ли я что-нибудь добавить в код, чтобы защитить от таких ситуаций и, возможно, обнаружить такие проблемы во время компиляции?

1 Ответ

0 голосов
/ 04 декабря 2018

Это безопасно, поскольку mmap выделяет память, соответствующим образом выровненную для любого встроенного и SIMD-типа.

Убедитесь, что вы вызываете std::uninitialized_default_construct_n (или ваш собственный эквивалент до C ++ 17), чтобы удовлетворитьтребование стандарта C ++, чтобы конструктор вызывался, и std::destroy_n для вызова деструкторов после использования.Эти вызовы компилируются в 0 инструкций, потому что конструктор по умолчанию и деструктор std::atomic<> тривиальны (ничего не делают) :

size_t numberOfLocks = ... some large number ...;
auto* locks = static_cast<std::atomic_int*>(mmap(0, numberOfLocks * sizeof(std::atomic_int), ...));

// initialize
std::uninitialized_default_construct_n(locks, numberOfLocks); 
// ... use ...
// uninitialize
std::destroy_n(locks, numberOfLocks);
...