diff --git a/include/engine/datafacade/mmap_memory_allocator.hpp b/include/engine/datafacade/mmap_memory_allocator.hpp new file mode 100644 index 000000000..429928794 --- /dev/null +++ b/include/engine/datafacade/mmap_memory_allocator.hpp @@ -0,0 +1,45 @@ +#ifndef OSRM_ENGINE_DATAFACADE_MMAP_MEMORY_ALLOCATOR_HPP_ +#define OSRM_ENGINE_DATAFACADE_MMAP_MEMORY_ALLOCATOR_HPP_ + +#include "engine/datafacade/contiguous_block_allocator.hpp" + +#include "storage/storage_config.hpp" + +#include "util/vector_view.hpp" + +#include + +#include + +namespace osrm +{ +namespace engine +{ +namespace datafacade +{ + +/** + * This allocator uses file backed mmap memory block as the data location. + */ +class MMapMemoryAllocator : public ContiguousBlockAllocator +{ + public: + explicit MMapMemoryAllocator(const storage::StorageConfig &config, + const boost::filesystem::path &memory_file); + ~MMapMemoryAllocator() override final; + + // interface to give access to the datafacades + storage::DataLayout &GetLayout() override final; + char *GetMemory() override final; + + private: + storage::DataLayout *data_layout; + util::vector_view mapped_memory; + boost::iostreams::mapped_file mapped_memory_file; +}; + +} // namespace datafacade +} // namespace engine +} // namespace osrm + +#endif // OSRM_ENGINE_DATAFACADE_SHARED_MEMORY_ALLOCATOR_HPP_ diff --git a/include/engine/datafacade_provider.hpp b/include/engine/datafacade_provider.hpp index 99e5fad58..ee2f310a7 100644 --- a/include/engine/datafacade_provider.hpp +++ b/include/engine/datafacade_provider.hpp @@ -4,6 +4,7 @@ #include "engine/data_watchdog.hpp" #include "engine/datafacade.hpp" #include "engine/datafacade/contiguous_internalmem_datafacade.hpp" +#include "engine/datafacade/mmap_memory_allocator.hpp" #include "engine/datafacade/process_memory_allocator.hpp" #include "engine/datafacade_factory.hpp" @@ -25,6 +26,31 @@ template class FacadeT> class DataFa virtual std::shared_ptr Get(const api::TileParameters &) const = 0; }; +template class FacadeT> +class ExternalProvider final : public DataFacadeProvider +{ + public: + using Facade = typename DataFacadeProvider::Facade; + + ExternalProvider(const storage::StorageConfig &config, + const boost::filesystem::path &memory_file) + : facade_factory(std::make_shared(config, memory_file)) + { + } + + std::shared_ptr Get(const api::TileParameters ¶ms) const override final + { + return facade_factory.Get(params); + } + std::shared_ptr Get(const api::BaseParameters ¶ms) const override final + { + return facade_factory.Get(params); + } + + private: + DataFacadeFactory facade_factory; +}; + template class FacadeT> class ImmutableProvider final : public DataFacadeProvider { @@ -74,6 +100,8 @@ template using WatchingProvider = detail::WatchingProvider; template using ImmutableProvider = detail::ImmutableProvider; +template +using ExternalProvider = detail::ExternalProvider; } } diff --git a/include/engine/engine.hpp b/include/engine/engine.hpp index 42a64277e..4a07e9841 100644 --- a/include/engine/engine.hpp +++ b/include/engine/engine.hpp @@ -67,6 +67,13 @@ template class Engine final : public EngineInterface << routing_algorithms::name(); facade_provider = std::make_unique>(); } + else if (!config.memory_file.empty()) + { + util::Log(logDEBUG) << "Using memory mapped filed at " << config.memory_file + << " with algorithm " << routing_algorithms::name(); + facade_provider = std::make_unique>(config.storage_config, + config.memory_file); + } else { util::Log(logDEBUG) << "Using internal memory with algorithm " diff --git a/include/engine/engine_config.hpp b/include/engine/engine_config.hpp index f53db2129..dcb8b145e 100644 --- a/include/engine/engine_config.hpp +++ b/include/engine/engine_config.hpp @@ -88,6 +88,7 @@ struct EngineConfig final int max_results_nearest = -1; int max_alternatives = 3; // set an arbitrary upper bound; can be adjusted by user bool use_shared_memory = true; + boost::filesystem::path memory_file; Algorithm algorithm = Algorithm::CH; std::string verbosity; }; diff --git a/include/nodejs/node_osrm_support.hpp b/include/nodejs/node_osrm_support.hpp index 0d5bdb622..7554c816b 100644 --- a/include/nodejs/node_osrm_support.hpp +++ b/include/nodejs/node_osrm_support.hpp @@ -115,14 +115,31 @@ inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo if (path.IsEmpty()) return engine_config_ptr(); + auto memory_file = params->Get(Nan::New("memory_file").ToLocalChecked()); + if (memory_file.IsEmpty()) + return engine_config_ptr(); + auto shared_memory = params->Get(Nan::New("shared_memory").ToLocalChecked()); if (shared_memory.IsEmpty()) return engine_config_ptr(); + if (!memory_file->IsUndefined()) + { + if (path->IsUndefined()) + { + Nan::ThrowError("memory_file option requires a path to a file."); + return engine_config_ptr(); + } + + engine_config->memory_file = + *v8::String::Utf8Value(Nan::To(memory_file).ToLocalChecked()); + } + if (!path->IsUndefined()) { engine_config->storage_config = osrm::StorageConfig(*v8::String::Utf8Value(Nan::To(path).ToLocalChecked())); + engine_config->use_shared_memory = false; } if (!shared_memory->IsUndefined()) diff --git a/include/util/mmap_file.hpp b/include/util/mmap_file.hpp index 43325811b..46984d948 100644 --- a/include/util/mmap_file.hpp +++ b/include/util/mmap_file.hpp @@ -33,6 +33,32 @@ util::vector_view mmapFile(const boost::filesystem::path &file, RegionT ®i SOURCE_REF); } } + +template +util::vector_view +mmapFile(const boost::filesystem::path &file, RegionT ®ion, const std::size_t size) +{ + try + { + // Create a new file with the given size in bytes + boost::iostreams::mapped_file_params params; + params.path = file.string(); + params.flags = boost::iostreams::mapped_file::readwrite; + params.new_file_size = size; + region.open(params); + + std::size_t num_objects = size / sizeof(T); + auto data_ptr = region.data(); + BOOST_ASSERT(reinterpret_cast(data_ptr) % alignof(T) == 0); + return util::vector_view(reinterpret_cast(data_ptr), num_objects); + } + catch (const std::exception &exc) + { + throw exception( + boost::str(boost::format("File %1% mapping failed: %2%") % file % exc.what()) + + SOURCE_REF); + } +} } template @@ -48,6 +74,14 @@ util::vector_view mmapFile(const boost::filesystem::path &file, { return detail::mmapFile(file, region); } + +template +util::vector_view mmapFile(const boost::filesystem::path &file, + boost::iostreams::mapped_file ®ion, + std::size_t size) +{ + return detail::mmapFile(file, region, size); +} } } diff --git a/src/engine/datafacade/mmap_memory_allocator.cpp b/src/engine/datafacade/mmap_memory_allocator.cpp new file mode 100644 index 000000000..23bb0e766 --- /dev/null +++ b/src/engine/datafacade/mmap_memory_allocator.cpp @@ -0,0 +1,53 @@ +#include "engine/datafacade/mmap_memory_allocator.hpp" + +#include "storage/storage.hpp" + +#include "util/log.hpp" +#include "util/mmap_file.hpp" + +#include "boost/assert.hpp" + +namespace osrm +{ +namespace engine +{ +namespace datafacade +{ + +MMapMemoryAllocator::MMapMemoryAllocator(const storage::StorageConfig &config, + const boost::filesystem::path &memory_file) +{ + storage::Storage storage(config); + + if (!boost::filesystem::exists(memory_file)) + { + storage::DataLayout initial_layout; + storage.PopulateLayout(initial_layout); + + auto data_size = initial_layout.GetSizeOfLayout(); + auto total_size = data_size + sizeof(storage::DataLayout); + + mapped_memory = util::mmapFile(memory_file, mapped_memory_file, total_size); + + data_layout = reinterpret_cast(mapped_memory.data()); + *data_layout = initial_layout; + storage.PopulateData(*data_layout, GetMemory()); + } + else + { + mapped_memory = util::mmapFile(memory_file, mapped_memory_file); + data_layout = reinterpret_cast(mapped_memory.data()); + } +} + +MMapMemoryAllocator::~MMapMemoryAllocator() {} + +storage::DataLayout &MMapMemoryAllocator::GetLayout() { return *data_layout; } +char *MMapMemoryAllocator::GetMemory() +{ + return mapped_memory.data() + sizeof(storage::DataLayout); +} + +} // namespace datafacade +} // namespace engine +} // namespace osrm diff --git a/src/tools/routed.cpp b/src/tools/routed.cpp index 8f58ed473..15aea1cde 100644 --- a/src/tools/routed.cpp +++ b/src/tools/routed.cpp @@ -112,6 +112,9 @@ inline unsigned generateServerProgramOptions(const int argc, ("shared-memory,s", value(&config.use_shared_memory)->implicit_value(true)->default_value(false), "Load data from shared memory") // + ("memory_file", + value(&config.memory_file), + "Store data in a memory mapped file rather than in process memory.") // ("algorithm,a", value(&config.algorithm) ->default_value(EngineConfig::Algorithm::CH, "CH"), diff --git a/test/nodejs/constants.js b/test/nodejs/constants.js index 451fb73ea..370b7f6d3 100644 --- a/test/nodejs/constants.js +++ b/test/nodejs/constants.js @@ -17,9 +17,11 @@ if (process.env.OSRM_DATA_PATH !== undefined) { exports.data_path = path.join(path.resolve(process.env.OSRM_DATA_PATH), "ch/monaco.osrm"); exports.mld_data_path = path.join(path.resolve(process.env.OSRM_DATA_PATH), "mld/monaco.osrm"); exports.corech_data_path = path.join(path.resolve(process.env.OSRM_DATA_PATH), "corech/monaco.osrm"); + exports.test_memory_path = path.join(path.resolve(process.env.OSRM_DATA_PATH), "test_memory"); console.log('Setting custom data path to ' + exports.data_path); } else { exports.data_path = path.resolve(path.join(__dirname, "../data/ch/monaco.osrm")); exports.mld_data_path = path.resolve(path.join(__dirname, "../data/mld/monaco.osrm")); exports.corech_data_path = path.resolve(path.join(__dirname, "../data/corech/monaco.osrm")); + exports.test_memory_path = path.resolve(path.join(__dirname, "../data/test_memory")); } diff --git a/test/nodejs/index.js b/test/nodejs/index.js index 6d779115d..719f7edfa 100644 --- a/test/nodejs/index.js +++ b/test/nodejs/index.js @@ -1,6 +1,7 @@ var OSRM = require('../../'); var test = require('tape'); var monaco_path = require('./constants').data_path; +var test_memory_file = require('./constants').test_memory_file; var monaco_mld_path = require('./constants').mld_data_path; var monaco_corech_path = require('./constants').corech_data_path; @@ -37,6 +38,12 @@ test('constructor: takes a shared memory argument', function(assert) { assert.ok(osrm); }); +test('constructor: takes a memory file', function(assert) { + assert.plan(1); + var osrm = new OSRM({path: monaco_path, memory_file: test_memory_file}); + assert.ok(osrm); +}); + test('constructor: throws if shared_memory==false with no path defined', function(assert) { assert.plan(1); assert.throws(function() { new OSRM({shared_memory: false}); },