diff --git a/include/nudb/basic_store.hpp b/include/nudb/basic_store.hpp index 9979441..ebaacb9 100644 --- a/include/nudb/basic_store.hpp +++ b/include/nudb/basic_store.hpp @@ -56,12 +56,6 @@ class basic_store using clock_type = std::chrono::steady_clock; - using shared_lock_type = - boost::shared_lock; - - using unique_lock_type = - boost::unique_lock; - struct state { File df; @@ -429,7 +423,7 @@ class basic_store bool exists(detail::nhash_t h, void const* key, - shared_lock_type* lock, detail::bucket b, error_code& ec); + detail::shared_lock_type* lock, detail::bucket b, error_code& ec); void split(detail::bucket& b1, detail::bucket& b2, diff --git a/include/nudb/detail/arena.hpp b/include/nudb/detail/arena.hpp index 7ef2705..71a07fa 100644 --- a/include/nudb/detail/arena.hpp +++ b/include/nudb/detail/arena.hpp @@ -8,12 +8,16 @@ #ifndef NUDB_DETAIL_ARENA_HPP #define NUDB_DETAIL_ARENA_HPP +#include #include #include +#include #include #include #include +#include + namespace nudb { namespace detail { @@ -29,11 +33,17 @@ namespace detail { template class arena_t { + using clock_type = std::chrono::steady_clock; + using time_point = typename clock_type::time_point; + + //using clock_type = class element; - std::size_t allocSize_; + std::size_t allocSize_ = 0; + std::size_t nalloc_ = 0; element* used_ = nullptr; element* free_ = nullptr; + time_point when_ = clock_type::now(); public: arena_t(arena_t const&) = delete; @@ -42,10 +52,9 @@ class arena_t ~arena_t(); - arena_t(arena_t&& other); + arena_t() = default; - explicit - arena_t(std::size_t allocSize); + arena_t(arena_t&& other); // Makes used blocks reusable void @@ -55,6 +64,11 @@ class arena_t void shrink_to_fit(); + // Called every so often + void + periodic_activity( + detail::unique_lock_type& m); + std::uint8_t* alloc(std::size_t n); @@ -73,7 +87,6 @@ class arena_t template class arena_t<_>::element { -private: std::size_t const capacity_; std::size_t used_ = 0; @@ -82,7 +95,7 @@ class arena_t<_>::element explicit element(std::size_t allocSize) - : capacity_(allocSize - sizeof(*this)) + : capacity_(allocSize) { } @@ -136,20 +149,16 @@ template arena_t<_>:: arena_t(arena_t&& other) : allocSize_(other.allocSize_) + , nalloc_(other.nalloc_) , used_(other.used_) , free_(other.free_) + , when_(other.when_) { + other.nalloc_ = 0; other.used_ = nullptr; other.free_ = nullptr; -} - -template -arena_t<_>:: -arena_t(std::size_t allocSize) - : allocSize_(allocSize) -{ - if(allocSize <= sizeof(element)) - throw std::domain_error("arena: bad alloc size"); + other.when_ = clock_type::now(); + other.allocSize_ = 0; } template @@ -162,8 +171,16 @@ clear() auto const e = used_; used_ = used_->next; e->clear(); - e->next = free_; - free_ = e; + if(e->remain() == allocSize_) + { + e->next = free_; + free_ = e; + } + else + { + e->~element(); + delete[] reinterpret_cast(e); + } } } @@ -173,6 +190,73 @@ arena_t<_>:: shrink_to_fit() { dealloc(free_); +beast::unit_test::dstream dout{std::cout}; + auto const size = + [](element* e) + { + std::size_t n = 0; + while(e) + { + ++n; + e = e->next; + } + return n; + }; +dout << "shrink_to_fit: alloc=" << allocSize_ << + ", nalloc=" << nalloc_ << ", used=" << size(used_) << "\n"; +} + +template +void +arena_t<_>:: +periodic_activity( + detail::unique_lock_type& m) +{ + using namespace std::chrono; + auto const now = clock_type::now(); + auto const elapsed = now - when_; + if(elapsed < seconds{1}) + return; + when_ = now; + if(! m.owns_lock()) + m.lock(); + auto const rate = static_cast(std::ceil( + nalloc_ / duration_cast>(elapsed).count())); +beast::unit_test::dstream dout{std::cout}; + + auto const size = + [](element* e) + { + std::size_t n = 0; + while(e) + { + ++n; + e = e->next; + } + return n; + }; + if(rate >= allocSize_ * 2) + { + // adjust up + allocSize_ = std::max(rate, allocSize_ * 2); + dealloc(free_); +dout << " rate=" << rate << ", alloc=" << allocSize_ << " UP, " + "nalloc=" << nalloc_ << ", used=" << size(used_) << ", free=" << size(free_) << "\n"; + } + else if(rate <= allocSize_ / 2) + { + // adjust down + allocSize_ /= 2; + dealloc(free_); +dout << " rate=" << rate << ", alloc=" << allocSize_ << " DOWN, " + "nalloc=" << nalloc_ << ", used=" << size(used_) << ", free=" << size(free_) << "\n"; + } + else + { +dout << " rate=" << rate << ", alloc=" << allocSize_ << ", " + "nalloc=" << nalloc_ << ", used=" << size(used_) << ", free=" << size(free_) << "\n"; + } + nalloc_ = 0; } template @@ -184,22 +268,26 @@ alloc(std::size_t n) BOOST_ASSERT(n != 0); n = 8 *((n + 7) / 8); if(used_ && used_->remain() >= n) + { + nalloc_ += n; return used_->alloc(n); + } if(free_ && free_->remain() >= n) { auto const e = free_; free_ = free_->next; e->next = used_; used_ = e; + nalloc_ += n; return used_->alloc(n); } - auto const size = std::max( - allocSize_, sizeof(element) + n); + auto const size = std::max(allocSize_, n); auto const e = reinterpret_cast( - new std::uint8_t[size]); - ::new(e) element(size); + new std::uint8_t[sizeof(element) + size]); + ::new(e) element{size}; e->next = used_; used_ = e; + nalloc_ += n; return used_->alloc(n); } @@ -208,9 +296,11 @@ void swap(arena_t<_>& lhs, arena_t<_>& rhs) { using std::swap; - swap(lhs.allocSize_, rhs.allocSize_); + swap(lhs.nalloc_, rhs.nalloc_); swap(lhs.used_, rhs.used_); swap(lhs.free_, rhs.free_); + swap(lhs.when_, rhs.when_); + // allocSize_ not swapped, by design } template diff --git a/include/nudb/detail/cache.hpp b/include/nudb/detail/cache.hpp index 0f33a0e..40a9844 100644 --- a/include/nudb/detail/cache.hpp +++ b/include/nudb/detail/cache.hpp @@ -32,13 +32,6 @@ class cache_t using value_type = std::pair; private: - enum - { - // The arena's alloc size will be this - // multiple of the block size. - factor = 64 - }; - #if 0 using map_type = std::unordered_map< nbuck_t, void*>; @@ -142,7 +135,6 @@ cache_t<_>:: cache_t() : key_size_(0) , block_size_(0) - , arena_(32) // arbitrary small number { } @@ -161,7 +153,6 @@ cache_t<_>:: cache_t(nsize_t key_size, nsize_t block_size) : key_size_(key_size) , block_size_(block_size) - , arena_(block_size * factor) { } diff --git a/include/nudb/detail/mutex.hpp b/include/nudb/detail/mutex.hpp new file mode 100644 index 0000000..779e39f --- /dev/null +++ b/include/nudb/detail/mutex.hpp @@ -0,0 +1,26 @@ +// +// Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef NUDB_DETAIL_MUTEX_HPP +#define NUDB_DETAIL_MUTEX_HPP + +#include +#include + +namespace nudb { +namespace detail { + +using shared_lock_type = + boost::shared_lock; + +using unique_lock_type = + boost::unique_lock; + +} // detail +} // nudb + +#endif diff --git a/include/nudb/detail/pool.hpp b/include/nudb/detail/pool.hpp index 11648a0..e2ec3be 100644 --- a/include/nudb/detail/pool.hpp +++ b/include/nudb/detail/pool.hpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -48,7 +50,8 @@ class pool_t pool_t(pool_t&& other); - pool_t(nsize_t key_size, std::size_t alloc_size); + explicit + pool_t(nsize_t key_size); iterator begin() @@ -88,6 +91,11 @@ class pool_t void shrink_to_fit(); + // Called every so often + void + periodic_activity( + detail::unique_lock_type& m); + iterator find(void const* key); @@ -166,9 +174,8 @@ pool_t(pool_t&& other) template pool_t<_>:: -pool_t(nsize_t key_size, std::size_t alloc_size) - : arena_(alloc_size) - , key_size_(key_size) +pool_t(nsize_t key_size) + : key_size_(key_size) , map_(compare{key_size}) { } @@ -191,6 +198,15 @@ shrink_to_fit() arena_.shrink_to_fit(); } +template +void +pool_t<_>:: +periodic_activity( + detail::unique_lock_type& m) +{ + arena_.periodic_activity(m); +} + template auto pool_t<_>:: diff --git a/include/nudb/impl/basic_store.ipp b/include/nudb/impl/basic_store.ipp index 88dc0b6..7ba4637 100644 --- a/include/nudb/impl/basic_store.ipp +++ b/include/nudb/impl/basic_store.ipp @@ -29,8 +29,8 @@ state(File&& df_, File&& kf_, File&& lf_, , kp(kp_) , lp(lp_) , hasher(kh_.salt) - , p0(kh_.key_size, arenaBlockSize) - , p1(kh_.key_size, arenaBlockSize) + , p0(kh_.key_size) + , p1(kh_.key_size) , c0(kh_.key_size, kh_.block_size) , c1(kh_.key_size, kh_.block_size) , kh(kh_) @@ -222,7 +222,7 @@ fetch( } auto const h = hash(key, s_->kh.key_size, s_->hasher); - shared_lock_type m{m_}; + detail::shared_lock_type m{m_}; { auto iter = s_->p1.find(key); if(iter == s_->p1.end()) @@ -273,7 +273,7 @@ insert( hash(key, s_->kh.key_size, s_->hasher); std::lock_guard u{u_}; { - shared_lock_type m{m_}; + detail::shared_lock_type m{m_}; if(s_->p1.find(key) != s_->p1.end() || s_->p0.find(key) != s_->p0.end()) { @@ -402,7 +402,7 @@ basic_store:: exists( detail::nhash_t h, void const* key, - shared_lock_type* lock, + detail::shared_lock_type* lock, detail::bucket b, error_code& ec) { @@ -746,37 +746,48 @@ run() s_->p1.data_size() >= commit_limit_; }; +using namespace std::chrono; +auto when = steady_clock::now(); while(open_) { for(;;) { using std::chrono::seconds; - unique_lock_type m{m_}; + detail::unique_lock_type m{m_}; auto const timeout = ! cond_.wait_for(m, seconds{1}, pred); if(! open_) break; m.unlock(); +beast::unit_test::dstream dout{std::cout}; +auto const now = steady_clock::now(); +auto const elapsed = now - when; +when = now; +dout << "commit: " << duration_cast>( + elapsed).count() << "s\n"; commit(ec_); if(ec_) { ecb_.store(true); return; } - // Reclaim some memory if - // we get a spare moment. if(timeout) { m.lock(); + // One second has passed with no inserts, + // reduce the commit threshold. s_->pool_thresh = std::max( 1, s_->pool_thresh / 2); + + // Return memory to the system. s_->p1.shrink_to_fit(); s_->p0.shrink_to_fit(); s_->c1.shrink_to_fit(); s_->c0.shrink_to_fit(); - m.unlock(); } + s_->p1.periodic_activity(m); + //s_->p0.periodic_activity(m); } } commit(ec_); diff --git a/test/basic_store.cpp b/test/basic_store.cpp index 1b82e09..6e267de 100644 --- a/test/basic_store.cpp +++ b/test/basic_store.cpp @@ -103,6 +103,7 @@ class basic_store_test : public beast::unit_test::suite if(! BEAST_EXPECTS(! ec, ec.message())) return; } +#if 0 // Fetch for(std::size_t n = 0; n < N; ++n) { @@ -166,6 +167,9 @@ class basic_store_test : public beast::unit_test::suite std::this_thread::sleep_for( std::chrono::milliseconds{2000}); } +#endif +std::this_thread::sleep_for( + std::chrono::seconds{5}); ts.close(ec); if(! BEAST_EXPECTS(! ec, ec.message())) return; @@ -175,6 +179,7 @@ class basic_store_test : public beast::unit_test::suite void test_insert_fetch() { +#if 0 for(auto const keySize : { 1, 2, 3, 31, 32, 33, 63, 64, 65, 95, 96, 97 }) { @@ -193,6 +198,10 @@ class basic_store_test : public beast::unit_test::suite do_insert_fetch(N, keySize, blockSize, loadFactor, keySize == 97); } +#endif + { + do_insert_fetch(10000000, 8, 4096, 0.5f, false); + } } void