C++: Fetch_add в файле с отображением памяти

я открываю файл, используя библиотеку сопоставленных файлов boost. Можно ли использовать «fetch_add» (значение считывается в определенной позиции, затем добавляется к другой и атомарно записывается обратно в ту же самую позицию) в этом сопоставленном файле?

Если несколько потоков пишут в него параллельно, могут возникнуть проблемы без использования атомарности.

Файл имеет двоичный формат и содержит целые или двойные числа (зависит от конкретного файла).

Я также пробовал блокировки/мьютексы, но они всегда замедляют мою программу при использовании нескольких потоков. Время, проведенное в заблокированных областях, слишком велико по сравнению с остальной частью алгоритма, и потоки блокируют друг друга.

Есть ли лучшие способы, чтобы несколько потоков могли записывать в сопоставленный файл с высокой производительностью?

Спасибо. Лаз


person Lazarus535    schedule 20.10.2014    source источник


Ответы (2)


Есть ли несколько процессов, отображающих этот файл, или просто несколько потоков?

Если несколько процессов одновременно обращаются к этому отображаемому в память файлу, вам придется выполнить собственную (межпроцессную) синхронизацию.

Если это всего лишь несколько потоков, вы можете атомарно обновить память так же, как вы делаете это для любого другого слова памяти, с оговоркой, что вы не можете использовать std::atomic (потому что, очевидно, байты соответствуют непосредственно разделу в файл, а не в std::atomic структуры). Таким образом, вы должны прибегнуть к поддержке вашей конкретной платформы для атомарного изменения памяти, а именно lock xadd на x86 через, например, InterlockedIncrement в Win32 (или __sync_fetch_and_add с g++). Убедитесь, что семантика порядка памяти (и возвращаемое значение!) соответствуют вашим ожиданиям.

Тем не менее, обертывание специфичных для платформы функций независимым от платформы способом (если вам это нужно) может быть немного хлопотным, и поэтому в этом случае я бы предложил хранить данные с одновременным доступом в отдельных переменных std::atomic, а затем обновлять соответствующие байты файла только один раз в конце.

Обратите внимание, что все это ортогонально отображению памяти — ОС поддерживает файл с отображением памяти со страницами, которые она заменяет по запросу, а модуль управления памятью, который управляет этими страницами, такой же, как и тот, который обрабатывает произвольные другие. (несопоставленные) страницы, поэтому сами страницы могут быть изменены несколькими потоками, не беспокоясь ни о чем, кроме обычных (на уровне приложения) гонок данных.

person Cameron    schedule 20.10.2014
comment
Есть только несколько потоков, обращающихся к сопоставлению. Итак, std::atomic предназначен только для одиночных значений, и нет возможности сделать блок памяти атомарным, например, массивом? За исключением того, что каждый элемент сам по себе является атомарным единичным значением? Не учитывая особенности платформы. Спасибо за быстрый ответ! - person Lazarus535; 21.10.2014
comment
виртуальный +1 за __sync_fetch_and_add! Слишком мало повторений :-( - person Lazarus535; 21.10.2014
comment
@ Lazarus535: std::atomic - это шаблон, поэтому теоретически у вас может быть атомарная переменная любого типа, включая массив или большую структуру. Однако, только определенные специализации не блокируются (в идеале все поддерживает аппаратное обеспечение, учитывая эффективную реализацию стандартной библиотеки), а атомарность достигается за счет обычного мьютекса (внутреннего по отношению к реализации) для всех остальных типов. Обратите также внимание, что массив атомарных переменных — это совсем не то же самое, что атомарная переменная, содержащая массив :-) - person Cameron; 21.10.2014
comment
Атомарная переменная, содержащая массив, по умолчанию будет мьютексом в случае контейнера (вектора и т. д.) или бесполезна в случае указателя на массив (выборка и добавление). Спасибо! Это было полезно! - person Lazarus535; 21.10.2014

Поскольку файл с отображением памяти ведет себя как обычная память [1], он будет работать так же хорошо, как и любой другой участок памяти.

Это компилируется (при использовании clang++3.6 -Wall -Wextra), но технически не определено (поскольку std::atomic<T> не гарантирует того же типа или правил выравнивания, что и T):

   std::atomic<uint64_t> *p; 
   p = reinterpret_cast<std::atomic<uint64_t>*>(&dest[index]);
   p->fetch_add(value);

Это должно быть хорошо в g++ и clang++:

   dest[index] = __sync_fetch_and_add(&dest[index], value);

(Оба генерируют почти идентичный ассемблерный код, - последний использует xaddq [который возвращает исходное значение], где первый использует addq - я ожидаю, что usign __sync_add_and_fetch сделает то же самое addq)

[1] Поскольку это обычная память - механизм отображения в Linux точно такой же, как и механизм, который обрабатывает обычную память, например, обмен данными в/из памяти, когда у вас недостаточно доступной памяти, или если вы используете код/данные в приложение, которое только что запустилось. Хотя у меня нет доступа к исходному коду Windows, я полагаю, что это верно и для него. Другие ОС вполне могут реализовать это немного по-другому, но нет причин полагать, что это помешает работе атомарных операций.

person Mats Petersson    schedule 20.10.2014
comment
Я действительно попытаюсь указать указатель на std::atomic, просто чтобы посмотреть, работает ли он. Мне кажется очень неприятным :-) Спасибо. - person Lazarus535; 21.10.2014
comment
Просто имейте в виду, что нет никакой гарантии, что это не сломается в какой-то момент в будущем, или если вы скомпилируете для другого процессора и т. д. - person Mats Petersson; 21.10.2014