From 3f49fca252a0a7ddc5524c0daba04c0ddb86bda2 Mon Sep 17 00:00:00 2001 From: Tor Egge Date: Wed, 10 Feb 2021 14:48:36 +0100 Subject: Add memory allocator backed by a file. --- vespalib/src/tests/alloc/alloc_test.cpp | 28 +++++++ vespalib/src/vespa/vespalib/util/CMakeLists.txt | 1 + .../vespa/vespalib/util/mmap_file_allocator.cpp | 87 ++++++++++++++++++++++ .../src/vespa/vespalib/util/mmap_file_allocator.h | 33 ++++++++ 4 files changed, 149 insertions(+) create mode 100644 vespalib/src/vespa/vespalib/util/mmap_file_allocator.cpp create mode 100644 vespalib/src/vespa/vespalib/util/mmap_file_allocator.h (limited to 'vespalib') diff --git a/vespalib/src/tests/alloc/alloc_test.cpp b/vespalib/src/tests/alloc/alloc_test.cpp index 4dbeba62ee1..056bac58d28 100644 --- a/vespalib/src/tests/alloc/alloc_test.cpp +++ b/vespalib/src/tests/alloc/alloc_test.cpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include using namespace vespalib; using namespace vespalib::alloc; @@ -279,4 +281,30 @@ TEST("auto alloced mmap alloc can not be shrinked below HUGEPAGE_SIZE/2 + 1 ") { EXPECT_EQUAL(SZ, buf.size()); } +TEST("mmap file allocator works") +{ + MmapFileAllocator allocator("mmap-file-allocator-dir"); + auto alloc = Alloc::alloc_with_allocator(&allocator); + auto buf = alloc.create(0); + EXPECT_EQUAL(0u, allocator.get_end_offset()); + EXPECT_EQUAL(0u, buf.size()); + EXPECT_TRUE(buf.get() == nullptr); + buf = alloc.create(4); + EXPECT_LESS_EQUAL(4u, buf.size()); + EXPECT_TRUE(buf.get() != nullptr); + memcpy(buf.get(), "1st", 4); + auto buf2 = alloc.create(5); + EXPECT_LESS_EQUAL(5u, buf2.size()); + EXPECT_TRUE(buf2.get() != nullptr); + EXPECT_TRUE(buf.get() != buf2.get()); + memcpy(buf2.get(), "fine", 5); + EXPECT_FALSE(buf.resize_inplace(5)); + EXPECT_FALSE(buf.resize_inplace(3)); + EXPECT_NOT_EQUAL(0u, allocator.get_end_offset()); + int result = msync(buf.get(), buf.size(), MS_SYNC); + EXPECT_EQUAL(0, result); + result = msync(buf2.get(), buf2.size(), MS_SYNC); + EXPECT_EQUAL(0, result); +} + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/vespa/vespalib/util/CMakeLists.txt b/vespalib/src/vespa/vespalib/util/CMakeLists.txt index 1e3f7dc44e7..bee95ad22dc 100644 --- a/vespalib/src/vespa/vespalib/util/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/util/CMakeLists.txt @@ -31,6 +31,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT left_right_heap.cpp lz4compressor.cpp md5.c + mmap_file_allocator.cpp printable.cpp priority_queue.cpp random.cpp diff --git a/vespalib/src/vespa/vespalib/util/mmap_file_allocator.cpp b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.cpp new file mode 100644 index 00000000000..821e3c86a67 --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.cpp @@ -0,0 +1,87 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "mmap_file_allocator.h" +#include +#include +#include + +namespace vespalib::alloc { + +namespace { + +const size_t page_size = std::max(getpagesize(), 4096); + +size_t round_to_page_size(size_t size) { + return (size + ((-size) & (page_size - 1))); +} + +} + +MmapFileAllocator::MmapFileAllocator(const vespalib::string& dir_name) + : _dir_name(dir_name), + _file(_dir_name + "/swapfile"), + _end_offset(0) +{ + mkdir(_dir_name, true); + _file.open(O_RDWR | O_CREAT | O_TRUNC, false); +} + +MmapFileAllocator::~MmapFileAllocator() +{ + assert(_allocations.empty()); + _file.close(); + _file.unlink(); + rmdir(_dir_name, true); +} + +MmapFileAllocator::PtrAndSize +MmapFileAllocator::alloc(size_t sz) const +{ + if (sz == 0) { + return PtrAndSize(nullptr, 0); // empty allocation + } + uint64_t offset = _end_offset; + sz = round_to_page_size(sz); + _end_offset += sz; + _file.resize(_end_offset); + void *buf = mmap(nullptr, sz, + PROT_READ | PROT_WRITE, + MAP_SHARED, + _file.getFileDescriptor(), + offset); + assert(buf != MAP_FAILED); + assert(buf != nullptr); + // Register allocation + auto ins_res = _allocations.emplace(buf, sz); + assert(ins_res.second); + return PtrAndSize(buf, sz); +} + + +void +MmapFileAllocator::free(PtrAndSize alloc) const +{ + if (alloc.second == 0) { + assert(alloc.first == nullptr); + return; // empty allocation + } + assert(alloc.first != nullptr); + // Check that matching allocation is registered + auto itr = _allocations.find(alloc.first); + assert(itr != _allocations.end()); + assert(itr->first == alloc.first); + assert(itr->second == alloc.second); + _allocations.erase(itr); + int retval = madvise(alloc.first, alloc.second, MADV_DONTNEED); + assert(retval == 0); + retval = munmap(alloc.first, alloc.second); + assert(retval == 0); +} + +size_t +MmapFileAllocator::resize_inplace(PtrAndSize, size_t) const +{ + return 0; +} + +} diff --git a/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h new file mode 100644 index 00000000000..84f63c8fc61 --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/mmap_file_allocator.h @@ -0,0 +1,33 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include "alloc.h" +#include +#include +#include + +namespace vespalib::alloc { + +/* + * Class handling memory allocations backed by one or more files. + * Not reentant. Should not be destructed before all allocations + * have been freed. + */ +class MmapFileAllocator : public MemoryAllocator { + vespalib::string _dir_name; + mutable File _file; + mutable uint64_t _end_offset; + mutable std::map _allocations; +public: + MmapFileAllocator(const vespalib::string& dir_name); + ~MmapFileAllocator(); + PtrAndSize alloc(size_t sz) const override; + void free(PtrAndSize alloc) const override; + size_t resize_inplace(PtrAndSize, size_t) const override; + + // For unit test + size_t get_end_offset() const noexcept { return _end_offset; } +}; + +} -- cgit v1.2.3