Added custom allocator for subsonic responses

This commit is contained in:
emeric
2024-03-19 09:08:43 +01:00
parent 5bfd31ff8a
commit 78384d5d3f
8 changed files with 324 additions and 26 deletions
+3
View File
@@ -58,3 +58,6 @@ target_link_libraries(lmssubsonic PUBLIC
install(TARGETS lmssubsonic DESTINATION ${CMAKE_INSTALL_LIBDIR})
if (BUILD_BENCHMARKS)
add_subdirectory(bench)
endif()
+14
View File
@@ -0,0 +1,14 @@
add_executable(bench-subsonic
SubsonicBench.cpp
)
target_include_directories(bench-subsonic PRIVATE
../impl
)
target_link_libraries(bench-subsonic PRIVATE
lmscore
lmsdatabase
lmssubsonic
benchmark
)
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2024 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <thread>
#include <benchmark/benchmark.h>
#include "SubsonicResponse.hpp"
namespace lms::api::subsonic::benchs
{
namespace
{
Response generateFakeResponse()
{
Response response{ Response::createOkResponse(defaultServerProtocolVersion) };
Response::Node& node{ response.createNode("MyNode") };
node.setAttribute("Attr1", "value1");
node.setAttribute("Attr2", "value2");
for (std::size_t i{}; i < 100; ++i)
{
Response::Node& childNode{ node.createArrayChild("MyArrayChild") };
childNode.setAttribute("Attr42", i);
node.addArrayValue("MyArray1", "value1");
node.addArrayValue("MyArray1", "value2");
for (std::size_t j{}; j < i; ++j)
node.addArrayValue("MyArray2", j);
}
return response;
}
}
static void BM_SubsonicResponse_generate(benchmark::State& state)
{
for (auto _ : state)
{
Response response{ generateFakeResponse() };
benchmark::DoNotOptimize(response);
TLSMonotonicMemoryResource::getInstance().reset();
}
}
template <ResponseFormat responseFormat>
static void BM_SubsonicResponse_serialize(benchmark::State& state)
{
const Response response{ generateFakeResponse() };
for (auto _ : state)
{
std::ostringstream oss; // TODO find something more optimized
response.write(oss, responseFormat);
}
}
BENCHMARK(BM_SubsonicResponse_generate)->Threads(1)->Threads(std::thread::hardware_concurrency());
BENCHMARK(BM_SubsonicResponse_serialize<ResponseFormat::json>);
BENCHMARK(BM_SubsonicResponse_serialize<ResponseFormat::xml>);
}
BENCHMARK_MAIN();
+17 -3
View File
@@ -159,7 +159,7 @@ namespace lms::api::subsonic
CheckImplementedFunc checkFunc{};
};
static const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints
const std::unordered_map<core::LiteralString, RequestEntryPointInfo, core::LiteralStringHash, core::LiteralStringEqual> requestEntryPoints
{
// System
{"/ping", {handlePingRequest}},
@@ -266,13 +266,26 @@ namespace lms::api::subsonic
};
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
static std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers
const std::unordered_map<core::LiteralString, MediaRetrievalHandlerFunc, core::LiteralStringHash, core::LiteralStringEqual> mediaRetrievalHandlers
{
// Media retrieval
{"/download", handleDownload},
{"/stream", handleStream},
{"/getCoverArt", handleGetCoverArt},
};
struct TLSMonotonicMemoryResourceCleaner
{
TLSMonotonicMemoryResourceCleaner() = default;
~TLSMonotonicMemoryResourceCleaner()
{
TLSMonotonicMemoryResource::getInstance().reset();
}
private:
TLSMonotonicMemoryResourceCleaner(const TLSMonotonicMemoryResourceCleaner&) = delete;
TLSMonotonicMemoryResourceCleaner& operator=(const TLSMonotonicMemoryResourceCleaner&) = delete;
};
}
SubsonicResource::SubsonicResource(db::Db& db)
@@ -287,7 +300,8 @@ namespace lms::api::subsonic
{
static std::atomic<std::size_t> curRequestId{};
const std::size_t requestId{ curRequestId++ };
const std::size_t requestId{ curRequestId++ };
TLSMonotonicMemoryResourceCleaner memoryResourceCleaner;
LMS_LOG(API_SUBSONIC, DEBUG, "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()));
+15 -15
View File
@@ -44,7 +44,7 @@ namespace lms::api::subsonic
void Response::Node::setValue(std::string_view value)
{
assert(_children.empty() && _childrenArrays.empty() && _childrenValues.empty());
_value = std::string{ value };
_value = string{ value };
}
void Response::Node::setValue(long long value)
@@ -55,13 +55,13 @@ namespace lms::api::subsonic
void Response::Node::setAttribute(Key key, std::string_view value)
{
_attributes[key] = std::string{ value };
_attributes[key] = string{ value };
}
void Response::Node::addChild(Key key, Node&& node)
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
assert(!_children.contains(key));
_children[key] = std::move(node);
}
@@ -69,29 +69,29 @@ namespace lms::api::subsonic
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
_childrenArrays.emplace(key, std::vector<Node>{});
_childrenArrays.emplace(key, vector<Node>{});
}
void Response::Node::addArrayChild(Key key, Node&& node)
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
assert(!_children.contains(key));
_childrenArrays[key].emplace_back(std::move(node));
}
void Response::Node::createEmptyArrayValue(Key key)
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
assert(!_children.contains(key));
_childrenValues.emplace(key, ValuesType{});
}
void Response::Node::addArrayValue(Key key, std::string_view value)
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
assert(!_children.contains(key));
auto& values{ _childrenValues[key] };
values.push_back(std::string{ value });
values.emplace_back(string{ value });
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
}
@@ -99,7 +99,7 @@ namespace lms::api::subsonic
{
assert(!_value);
auto& values{ _childrenValues[key] };
values.push_back(value);
values.emplace_back(value);
assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();}));
}
@@ -112,7 +112,7 @@ namespace lms::api::subsonic
Response::Node& Response::Node::createArrayChild(Key key)
{
assert(!_value);
assert(_children.find(key) == std::cend(_children));
assert(!_children.contains(key));
_childrenArrays[key].emplace_back();
return _childrenArrays[key].back();
}
@@ -192,8 +192,8 @@ namespace lms::api::subsonic
for (const auto& [key, value] : node._attributes)
{
if (std::holds_alternative<std::string>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<std::string>(value));
if (std::holds_alternative<Node::string>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<Node::string>(value));
else if (std::holds_alternative<bool>(value))
res.put("<xmlattr>." + std::string{ key.str() }, std::get<bool>(value));
else if (std::holds_alternative<float>(value))
@@ -240,7 +240,7 @@ namespace lms::api::subsonic
return res;
};
boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
const boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
boost::property_tree::write_xml(os, root);
}
@@ -337,9 +337,9 @@ namespace lms::api::subsonic
void Response::JsonSerializer::serializeValue(std::ostream& os, const Node::ValueType& value)
{
if (std::holds_alternative<std::string>(value))
if (std::holds_alternative<Node::string>(value))
{
serializeEscapedString(os, std::get<std::string>(value));
serializeEscapedString(os, std::get<Node::string>(value));
}
else if (std::holds_alternative<bool>(value))
{
+18 -8
View File
@@ -27,6 +27,7 @@
#include "core/LiteralString.hpp"
#include "RequestContext.hpp"
#include "SubsonicResponseAllocator.hpp"
namespace lms::api::subsonic
{
@@ -240,14 +241,23 @@ namespace lms::api::subsonic
void setVersionAttribute(ProtocolVersion version);
friend class Response;
using ValueType = std::variant<std::string, bool, float, long long>;
std::map<Key, ValueType> _attributes;
std::optional<ValueType> _value;
std::map<Key, Node> _children;
std::map<Key, std::vector<Node>> _childrenArrays;
using ValuesType = std::vector<ValueType>;
std::map<Key, ValuesType> _childrenValues;
template <typename Key, typename Value>
using map = std::map< Key, Value, std::less<Key>, ResponseAllocator<std::pair<const Key, Value>>>;
template <typename T>
using vector = std::vector<T, ResponseAllocator<T>>;
using string = std::basic_string<char, std::char_traits<char>, ResponseAllocator<char>>;
using ValueType = std::variant<string, bool, float, long long>;
map<Key, ValueType> _attributes;
std::optional<ValueType> _value;
map<Key, Node> _children;
map<Key, vector<Node>> _childrenArrays;
using ValuesType = vector<ValueType>;
map<Key, ValuesType> _childrenValues;
};
static Response createOkResponse(ProtocolVersion protocolVersion);
@@ -272,7 +282,7 @@ namespace lms::api::subsonic
{
public:
void serializeNode(std::ostream& os, const Node& node);
void serializeValue(std::ostream& os, const Node::ValueType& node);
void serializeValue(std::ostream& os, const Node::ValueType& value);
void serializeEscapedString(std::ostream&, std::string_view str);
};
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2024 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "TLSMonotonicMemoryResource.hpp"
namespace lms::api::subsonic
{
// Stateless allocator that uses a shared MemoryResource
template <typename MemoryResource, typename T>
class Allocator
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
constexpr Allocator() noexcept = default;
template <typename U>
constexpr Allocator(const Allocator<MemoryResource, U>&) noexcept {}
template <typename V>
struct rebind
{
using other = Allocator<MemoryResource, V>;
};
[[nodiscard]] pointer allocate(size_type n)
{
return reinterpret_cast<pointer>(MemoryResource::getInstance().allocate(n * sizeof(T), alignof(T)));
}
// Deallocate memory pointed to by p
void deallocate(pointer p, std::size_t) noexcept
{
MemoryResource::getInstance().deallocate(reinterpret_cast<std::byte*>(p));
}
};
template<class MemoryResource, class T, class U>
bool operator==(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
{
return true;
}
template<class MemoryResource, class T, class U>
bool operator!=(const Allocator <MemoryResource, T>&, const Allocator <MemoryResource, U>&)
{
return false;
}
template <typename T>
using ResponseAllocator = Allocator<TLSMonotonicMemoryResource, T>;
}
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2024 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <array>
#include <list>
namespace lms::api::subsonic
{
class TLSMonotonicMemoryResource
{
public:
static TLSMonotonicMemoryResource& getInstance()
{
static thread_local TLSMonotonicMemoryResource instance;
return instance;
}
TLSMonotonicMemoryResource()
{
allocateNewBlock();
}
[[nodiscard]] std::byte* allocate(std::size_t byteCount, std::size_t alignment)
{
std::byte* currentAddrAligned{ computeAlignedAddr(_currentAddr, alignment) };
if (currentAddrAligned + byteCount > &_currentBlock->back() + 1)
{
allocateNewBlock();
currentAddrAligned = computeAlignedAddr(_currentAddr, alignment);
}
// Requested too many bytes for blockSize!
if (currentAddrAligned + byteCount > &_currentBlock->back() + 1)
throw std::bad_alloc{};
assert(currentAddrAligned >= &_currentBlock->front());
std::byte* res{ currentAddrAligned };
_currentAddr = currentAddrAligned + byteCount;
return res;
}
void deallocate(std::byte*)
{
// nothing to do!
}
void reset()
{
// always keep at least one block
if (_blocks.size() > 1)
_blocks.erase(std::next(std::cbegin(_blocks), 1), std::cend(_blocks));
_currentBlock = &_blocks.front();
_currentAddr = _currentBlock->data();
}
private:
void allocateNewBlock()
{
_currentBlock = &_blocks.emplace_back();
_currentAddr = _currentBlock->data();
}
static std::byte* computeAlignedAddr(std::byte* addr, std::size_t alignment) noexcept
{
const std::uintptr_t addrValue{ reinterpret_cast<std::uintptr_t>(addr) };
const std::uintptr_t mask{ alignment - 1 };
const std::uintptr_t adjustment{ (alignment - (addrValue & mask)) & mask };
return reinterpret_cast<std::byte*>(addr + adjustment);
}
static constexpr std::size_t blockSize{ static_cast<std::size_t>(1 * 1024 * 1024) };
using BlockType = std::array<std::byte, blockSize>;
std::list<BlockType> _blocks;
BlockType* _currentBlock{};
std::byte* _currentAddr{};
};
}