Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database optimizations. #489

Merged
merged 22 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
324907b
Revert "Fix deadlocks resulting from iterator memory object retention."
evoskuil Jun 10, 2024
536f13c
Guard iterator::is_match() against invalid member position.
evoskuil Jun 10, 2024
c5734b6
Retain iterator key by reference.
evoskuil Jun 10, 2024
80d5874
Expose iterator.get() and hashmap.get(iterator) for perf/safety.
evoskuil Jun 10, 2024
2d9f2d8
Style.
evoskuil Jun 10, 2024
8f24439
Style, comments, optimize copy elision.
evoskuil Jun 10, 2024
6622ab7
Add storage::get_raw() for raw pointer access to fixed-size files.
evoskuil Jun 10, 2024
7eec4c8
Refactor iterator for optimization.
evoskuil Jun 10, 2024
7099a1b
Remove inlining from iterator methods.
evoskuil Jun 10, 2024
0c88f8b
Add iterator::operator bool() and apply.
evoskuil Jun 10, 2024
e35091a
Comments, use iterator bool() and avoid deadlock iterations.
evoskuil Jun 11, 2024
4782934
Whitespace.
evoskuil Jun 11, 2024
3deb38f
Fix unchecked iterators.
evoskuil Jun 11, 2024
6fb504d
Fix hashmap::get(it,...) buffer sizing.
evoskuil Jun 11, 2024
df05f40
Style, optimize link copy elision.
evoskuil Jun 11, 2024
06c4177
use tx.get_hash(), comments.
evoskuil Jun 11, 2024
c7c3006
Optimize hashmap::first() by avoiding iterator cons, comments, style.
evoskuil Jun 11, 2024
dcef21c
Use tx/block/header ::get_hash() to avoid copies.
evoskuil Jun 11, 2024
069301c
Revert iterator referenced key_ member.
evoskuil Jun 11, 2024
5a6b256
Comments, style.
evoskuil Jun 11, 2024
af584ec
Add hashmap::find(key, ...) to optimize non-multiples.
evoskuil Jun 11, 2024
51ab282
Optimize using new hashmap.find(key).
evoskuil Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 112 additions & 35 deletions include/bitcoin/database/impl/primitives/hashmap.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#ifndef LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP
#define LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_IPP

#include <utility>
#include <algorithm>
#include <bitcoin/system.hpp>
#include <bitcoin/database/define.hpp>

Expand All @@ -39,8 +39,8 @@ TEMPLATE
bool CLASS::create() NOEXCEPT
{
Link count{};
return head_.create() &&
head_.get_body_count(count) && manager_.truncate(count);
return head_.create() && head_.get_body_count(count) &&
manager_.truncate(count);
}

TEMPLATE
Expand All @@ -59,16 +59,16 @@ TEMPLATE
bool CLASS::restore() NOEXCEPT
{
Link count{};
return head_.verify() &&
head_.get_body_count(count) && manager_.truncate(count);
return head_.verify() && head_.get_body_count(count) &&
manager_.truncate(count);
}

TEMPLATE
bool CLASS::verify() const NOEXCEPT
{
Link count{};
return head_.verify() &&
head_.get_body_count(count) && count == manager_.count();
return head_.verify() && head_.get_body_count(count) &&
(count == manager_.count());
}

// sizing
Expand Down Expand Up @@ -131,7 +131,10 @@ code CLASS::reload() NOEXCEPT
TEMPLATE
Link CLASS::top(const Link& link) const NOEXCEPT
{
return link < head_.buckets() ? head_.top(link) : Link{};
if (link >= head_.buckets())
return {};

return head_.top(link);
}

TEMPLATE
Expand All @@ -143,16 +146,16 @@ bool CLASS::exists(const Key& key) const NOEXCEPT
TEMPLATE
Link CLASS::first(const Key& key) const NOEXCEPT
{
return it(key).self();
////return it(key).self();
// Copied from iterator::to_match(link), avoids normal form it() construct.
return first(manager_.get(), head_.top(key), key);
}

TEMPLATE
typename CLASS::iterator CLASS::it(const Key& key) const NOEXCEPT
{
// TODO: key is copied into iterator, ref may be better for big keys.
// manager.get() is called for each memory access, avoiding deadlock
// risk that would arise if the memory accessor was held.
return { manager_, head_.top(key), key };
// Expensive construction, avoid unless iteration is necessary.
return { manager_.get(), head_.top(key), key };
}

TEMPLATE
Expand All @@ -164,32 +167,38 @@ Link CLASS::allocate(const Link& size) NOEXCEPT
TEMPLATE
Key CLASS::get_key(const Link& link) NOEXCEPT
{
constexpr auto key_size = array_count<Key>;
const auto ptr = manager_.get(link);

// As with link, search key is presumed valid (otherwise null array).
if (!ptr || system::is_lesser(ptr->size(), Link::size + key_size))
if (!ptr || system::is_lesser(ptr->size(), index_size))
return {};

return system::unsafe_array_cast<uint8_t, key_size>(std::next(
return system::unsafe_array_cast<uint8_t, array_count<Key>>(std::next(
ptr->begin(), Link::size));
}

TEMPLATE
template <typename Element, if_equal<Element::size, Size>>
bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
bool CLASS::find(const Key& key, Element& element) const NOEXCEPT
{
using namespace system;
const auto ptr = manager_.get(link);
if (!ptr)
return false;
// Renamed to "find()" to avoid collision over link/key.
// This override avoids duplicated memory_ptr construct in get(first()).
const auto ptr = manager_.get();
return read(ptr, first(ptr, head_.top(key), key), element);
}

iostream stream{ *ptr };
reader source{ stream };
source.skip_bytes(Link::size + array_count<Key>);
TEMPLATE
template <typename Element, if_equal<Element::size, Size>>
bool CLASS::get(const Link& link, Element& element) const NOEXCEPT
{
// This override is the normal form.
return read(manager_.get(), link, element);
}

if constexpr (!is_slab) { source.set_limit(Size); }
return element.from_data(source);
TEMPLATE
template <typename Element, if_equal<Element::size, Size>>
bool CLASS::get(const iterator& it, Element& element) const NOEXCEPT
{
// This override avoids deadlock when holding iterator to the same table.
return read(it.get(), it.self(), element);
}

TEMPLATE
Expand All @@ -203,7 +212,7 @@ bool CLASS::set(const Link& link, const Element& element) NOEXCEPT

iostream stream{ *ptr };
finalizer sink{ stream };
sink.skip_bytes(Link::size + array_count<Key>);
sink.skip_bytes(index_size);

if constexpr (!is_slab) { sink.set_limit(Size); }
return element.to_data(sink);
Expand All @@ -214,7 +223,10 @@ template <typename Element, if_equal<Element::size, Size>>
Link CLASS::set_link(const Element& element) NOEXCEPT
{
Link link{};
return set_link(link, element) ? link : Link{};
if (!set_link(link, element))
return {};

return link;
}

TEMPLATE
Expand All @@ -230,7 +242,10 @@ template <typename Element, if_equal<Element::size, Size>>
Link CLASS::put_link(const Key& key, const Element& element) NOEXCEPT
{
Link link{};
return put_link(link, key, element) ? link : Link{};
if (!put_link(link, key, element))
return {};

return link;
}

TEMPLATE
Expand Down Expand Up @@ -281,6 +296,8 @@ bool CLASS::put(const Link& link, const Key& key,
finalizer sink{ stream };
sink.skip_bytes(Link::size);
sink.write_bytes(key);

// The finalizer provides deferred index commit following serialization.
sink.set_finalizer([this, link, index = head_.index(key), ptr]() NOEXCEPT
{
auto& next = unsafe_array_cast<uint8_t, Link::size>(ptr->begin());
Expand All @@ -289,7 +306,6 @@ bool CLASS::put(const Link& link, const Key& key,

if constexpr (!is_slab) { sink.set_limit(Size * count); }
return element.to_data(sink) && sink.finalize();
return false;
}

TEMPLATE
Expand All @@ -300,8 +316,8 @@ bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
return false;

// Set element search key.
system::unsafe_array_cast<uint8_t, array_count<Key>>(std::next(
ptr->begin(), Link::size)) = key;
system::unsafe_array_cast<uint8_t, array_count<Key>>(
std::next(ptr->begin(), Link::size)) = key;

// Commit element to search index.
auto& next = system::unsafe_array_cast<uint8_t, Link::size>(ptr->begin());
Expand All @@ -311,7 +327,68 @@ bool CLASS::commit(const Link& link, const Key& key) NOEXCEPT
TEMPLATE
Link CLASS::commit_link(const Link& link, const Key& key) NOEXCEPT
{
return commit(link, key) ? link : Link{};
if (!commit(link, key))
return {};

return link;
}

// protected/static
// ----------------------------------------------------------------------------

TEMPLATE
template <typename Element, if_equal<Element::size, Size>>
bool CLASS::read(const memory_ptr& ptr, const Link& link,
Element& element) NOEXCEPT
{
if (!ptr || link.is_terminal())
return false;

using namespace system;
const auto start = iterator::link_to_position(link);
if (is_limited<ptrdiff_t>(start))
return false;

const auto size = ptr->size();
const auto position = possible_narrow_and_sign_cast<ptrdiff_t>(start);
if (position > size)
return false;

const auto offset = ptr->offset(position);
if (is_null(offset))
return false;

// Stream starts at record and the index is skipped for reader convenience.
iostream stream{ offset, size - position };
reader source{ stream };
source.skip_bytes(index_size);
if constexpr (!is_slab) { source.set_limit(Size); }
return element.from_data(source);
}

TEMPLATE
Link CLASS::first(const memory_ptr& ptr, Link link, const Key& key) NOEXCEPT
{
if (!ptr)
return {};

while (!link.is_terminal())
{
// get element offset (fault)
const auto offset = ptr->offset(iterator::link_to_position(link));
if (is_null(offset))
return {};

// element key matches (found)
const auto key_ptr = std::next(offset, Link::size);
if (is_zero(std::memcmp(key.data(), key_ptr, array_count<Key>)))
return link;

// set next element link (loop)
link = system::unsafe_array_cast<uint8_t, Link::size>(offset);
}

return link;
}

} // namespace database
Expand Down
21 changes: 12 additions & 9 deletions include/bitcoin/database/impl/primitives/head.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#include <bitcoin/system.hpp>
#include <bitcoin/database/define.hpp>

// Heads are not subject to resize/remap and therefore do not require memory
// smart pointer with shared remap lock. Using get_raw() saves that allocation.

namespace libbitcoin {
namespace database {

Expand Down Expand Up @@ -100,7 +103,7 @@ bool CLASS::get_body_count(Link& count) const NOEXCEPT
if (!ptr)
return false;

count = array_cast<Link::size>(*ptr);
count = array_cast<Link::size>(ptr->begin());
return true;
}

Expand All @@ -111,7 +114,7 @@ bool CLASS::set_body_count(const Link& count) NOEXCEPT
if (!ptr)
return false;

array_cast<Link::size>(*ptr) = count;
array_cast<Link::size>(ptr->begin()) = count;
return true;
}

Expand All @@ -124,11 +127,11 @@ Link CLASS::top(const Key& key) const NOEXCEPT
TEMPLATE
Link CLASS::top(const Link& index) const NOEXCEPT
{
const auto ptr = file_.get(offset(index));
if (!ptr)
return Link::terminal;
const auto ptr = file_.get_raw(offset(index));
if (is_null(ptr))
return {};

const auto& head = array_cast<Link::size>(*ptr);
const auto& head = array_cast<Link::size>(ptr);

mutex_.lock_shared();
const auto top = head;
Expand All @@ -145,11 +148,11 @@ bool CLASS::push(const bytes& current, bytes& next, const Key& key) NOEXCEPT
TEMPLATE
bool CLASS::push(const bytes& current, bytes& next, const Link& index) NOEXCEPT
{
const auto ptr = file_.get(offset(index));
if (!ptr)
const auto ptr = file_.get_raw(offset(index));
if (is_null(ptr))
return false;

auto& head = array_cast<Link::size>(*ptr);
auto& head = array_cast<Link::size>(ptr);

mutex_.lock();
next = head;
Expand Down
Loading
Loading