UPDATE 2/TL; DR
Есть ли способ предотвратить загрязнение страниц из временных окон файл удаления-закрытия закрывается в результате закрытия карт памяти открыт в этих файлах.
Да. Если вам не нужно ничего делать с самими файлами после их первоначального создания, и вы реализуете некоторые соглашения об именах, это возможно благодаря стратегии, описанной в этот ответ.
Примечание: Я все еще очень заинтересован в том, чтобы выяснить причины того, почему так много различий в поведении, в зависимости от того, как создаются карты и порядок удаления/развязки.
Я изучал некоторые стратегии для структуры данных разделяемой памяти между процессами, которая позволяет увеличивать и сокращать свою установленную емкость на окнах, используя цепочку "кусков памяти".
Одним из возможных способов является использование файлов с подкачкой имен с именем памяти в качестве памяти блоков. Преимуществом этой стратегии является возможность использовать SEC_RESERVE
для резервирования большого фрагмента адресного пространства памяти и поэтапно распределить его с помощью VirtualAlloc
с помощью MEM_COMMIT
. Недостатками, по-видимому, являются (а) требование иметь разрешения SeCreateGlobalPrivilege
, чтобы разрешить использование общего имени в пространстве имен Global\
и (b) тот факт, что вся зафиксированная память вносит свой вклад в плату фиксации системы.
Чтобы обойти эти недостатки, я начал исследовать использование файлов с резервной памятью памяти. То есть карты памяти над файлами, создаваемыми с помощью комбинации флагов FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY
. Это, по-видимому, рекомендуемая стратегия, которая согласно, например, это сообщение в блоге должно предотвратить промывку отображаемой памяти на диск (если только давление памяти не вызывает высылку грязных отображаемых страниц).
Тем не менее, я наблюдаю, что закрытие дескриптора карты/файла перед тем, как процесс владения завершается, приводит к потере грязных страниц на диск. Это происходит, даже если дескриптор view/file не тот, через который были созданы грязные страницы, и когда эти представления/файлы были открыты после того, как страницы были "загрязнены" в другом представлении.
Похоже, что изменение порядка удаления (т.е. немедленное снятие вида или закрытие дескриптора файла) оказывает определенное влияние на то, когда инициируется флеш-диск, но не тот факт, что происходит промывка.
Итак, мои вопросы:
- Есть ли способ использовать карты памяти с временным файлом и предотвратить их очистку от грязных страниц при закрытии карты/файла, принимая во внимание, что несколько потоков в процессе/нескольких процессах могут иметь открытые дескрипторы/представления для такого файл?
- Если нет, то какая причина/может быть причиной наблюдаемого поведения?
- Знаете ли вы альтернативную стратегию, которую я, возможно, пропустил?
<ч/" > UPDATE Некоторая дополнительная информация: при запуске частей образца "arena1" и "arena2" приведенного ниже кода в двух отдельных (независимых) процессах, при этом "arena1" является процессом, который создает области разделяемой памяти и "arena2", тот, который их открывает, наблюдается следующее поведение для карт/кусков, имеющих грязные страницы:
- Если вы закрываете представление перед дескриптором файла в процессе "arena1", он сбрасывает каждый из этих фрагментов на диск в том, что кажется (частично) синхронным процессом (т.е. блокирует поток удаления в течение нескольких секунд), независимый того, был ли запущен процесс "arena2".
- Если вы закрываете дескриптор файла перед представлением, происходит только сброс дисков для тех карт/кусков, которые закрыты в процессе "arena1", в то время как процесс "arena2" по-прежнему имеет открытую ручку для этих фрагментов, и они, похоже, "асинхронный", т.е. не блокирует поток приложения.
Обратитесь к приведенному ниже примеру кода (С++), который позволяет воспроизвести проблему в моей системе (x64, Win7):
static uint64_t start_ts;
static uint64_t elapsed() {
return ::GetTickCount64() - start_ts;
}
class PageArena {
public:
typedef uint8_t* pointer;
PageArena(int id, const char* base_name, size_t page_sz, size_t chunk_sz, size_t n_chunks, bool dispose_handle_first) :
id_(id), base_name_(base_name), pg_sz_(page_sz), dispose_handle_first_(dispose_handle_first) {
for (size_t i = 0; i < n_chunks; i++)
chunks_.push_back(new Chunk(i, base_name_, chunk_sz, dispose_handle_first_));
}
~PageArena() {
for (auto i = 0; i < chunks_.size(); ++i) {
if (chunks_[i])
release_chunk(i);
}
std::cout << "[" << ::elapsed() << "] arena " << id_ << " destructed" << std::endl;
}
pointer alloc() {
auto ptr = chunks_.back()->alloc(pg_sz_);
if (!ptr) {
chunks_.push_back(new Chunk(chunks_.size(), base_name_, chunks_.back()->capacity(), dispose_handle_first_));
ptr = chunks_.back()->alloc(pg_sz_);
}
return ptr;
}
size_t num_chunks() {
return chunks_.size();
}
void release_chunk(size_t ndx) {
delete chunks_[ndx];
chunks_[ndx] = nullptr;
std::cout << "[" << ::elapsed() << "] chunk " << ndx << " released from arena " << id_ << std::endl;
}
private:
struct Chunk {
public:
Chunk(size_t ndx, const std::string& base_name, size_t size, bool dispose_handle_first) :
map_ptr_(nullptr), tail_(nullptr),
handle_(INVALID_HANDLE_VALUE), size_(0),
dispose_handle_first_(dispose_handle_first) {
name_ = name_for(base_name, ndx);
if ((handle_ = create_temp_file(name_, size)) == INVALID_HANDLE_VALUE)
handle_ = open_temp_file(name_, size);
if (handle_ != INVALID_HANDLE_VALUE) {
size_ = size;
auto map_handle = ::CreateFileMappingA(handle_, nullptr, PAGE_READWRITE, 0, 0, nullptr);
tail_ = map_ptr_ = (pointer)::MapViewOfFile(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
::CloseHandle(map_handle); // no longer needed.
}
}
~Chunk() {
if (dispose_handle_first_) {
close_file();
unmap_view();
} else {
unmap_view();
close_file();
}
}
size_t capacity() const {
return size_;
}
pointer alloc(size_t sz) {
pointer result = nullptr;
if (tail_ + sz <= map_ptr_ + size_) {
result = tail_;
tail_ += sz;
}
return result;
}
private:
static const DWORD kReadWrite = GENERIC_READ | GENERIC_WRITE;
static const DWORD kFileSharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
static const DWORD kTempFlags = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
static std::string name_for(const std::string& base_file_path, size_t ndx) {
std::stringstream ss;
ss << base_file_path << "." << ndx << ".chunk";
return ss.str();
}
static HANDLE create_temp_file(const std::string& name, size_t& size) {
auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, CREATE_NEW, kTempFlags, 0);
if (h != INVALID_HANDLE_VALUE) {
LARGE_INTEGER newpos;
newpos.QuadPart = size;
::SetFilePointerEx(h, newpos, 0, FILE_BEGIN);
::SetEndOfFile(h);
}
return h;
}
static HANDLE open_temp_file(const std::string& name, size_t& size) {
auto h = CreateFileA(name.c_str(), kReadWrite, kFileSharing, nullptr, OPEN_EXISTING, kTempFlags, 0);
if (h != INVALID_HANDLE_VALUE) {
LARGE_INTEGER sz;
::GetFileSizeEx(h, &sz);
size = sz.QuadPart;
}
return h;
}
void close_file() {
if (handle_ != INVALID_HANDLE_VALUE) {
std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closing" << std::endl;
::CloseHandle(handle_);
std::cout << "[" << ::elapsed() << "] " << name_ << " file handle closed" << std::endl;
}
}
void unmap_view() {
if (map_ptr_) {
std::cout << "[" << ::elapsed() << "] " << name_ << " view closing" << std::endl;
::UnmapViewOfFile(map_ptr_);
std::cout << "[" << ::elapsed() << "] " << name_ << " view closed" << std::endl;
}
}
HANDLE handle_;
std::string name_;
pointer map_ptr_;
size_t size_;
pointer tail_;
bool dispose_handle_first_;
};
int id_;
size_t pg_sz_;
std::string base_name_;
std::vector<Chunk*> chunks_;
bool dispose_handle_first_;
};
static void TempFileMapping(bool dispose_handle_first) {
const size_t chunk_size = 256 * 1024 * 1024;
const size_t pg_size = 8192;
const size_t n_pages = 100 * 1000;
const char* base_path = "data/page_pool";
start_ts = ::GetTickCount64();
if (dispose_handle_first)
std::cout << "Mapping with 2 arenas and closing file handles before unmapping views." << std::endl;
else
std::cout << "Mapping with 2 arenas and unmapping views before closing file handles." << std::endl;
{
std::cout << "[" << ::elapsed() << "] " << "allocating " << n_pages << " pages through arena 1." << std::endl;
PageArena arena1(1, base_path, pg_size, chunk_size, 1, dispose_handle_first);
for (size_t i = 0; i < n_pages; i++) {
auto ptr = arena1.alloc();
memset(ptr, (i + 1) % 256, pg_size); // ensure pages are dirty.
}
std::cout << "[" << elapsed() << "] " << arena1.num_chunks() << " chunks created." << std::endl;
{
PageArena arena2(2, base_path, pg_size, chunk_size, arena1.num_chunks(), dispose_handle_first);
std::cout << "[" << ::elapsed() << "] arena 2 loaded, going to release chunks 1 and 2 from arena 1" << std::endl;
arena1.release_chunk(1);
arena1.release_chunk(2);
}
}
}
Пожалуйста, обратитесь к этому gist, который содержит результат выполнения вышеуказанного кода и ссылки на снимки экрана система свободной памяти и активность диска при запуске TempFileMapping(false)
и TempFileMapping(true)
соответственно.