Skip to content

Commit

Permalink
Finish implementing SharedPreHashedString
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkewley committed Jul 22, 2024
1 parent 9ea6edc commit 0c5dea4
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 288 deletions.
31 changes: 18 additions & 13 deletions src/oscar/Utils/SharedPreHashedString.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <functional>
#include <memory>
#include <new>
#include <ostream>
#include <span>
#include <string_view>
#include <utility>
Expand Down Expand Up @@ -65,7 +66,10 @@ namespace osc
using reverse_iterator = std::string_view::const_reverse_iterator;
using const_reverse_iterator = std::string_view::const_reverse_iterator;

explicit SharedPreHashedString() : SharedPreHashedString{std::string_view{}} {}
explicit SharedPreHashedString() :
SharedPreHashedString{SharedPreHashedString::static_default_instance()}
{}

explicit SharedPreHashedString(std::string_view str)
{
const size_t num_bytes_allocated =
Expand Down Expand Up @@ -95,26 +99,16 @@ namespace osc
static_cast<Metadata*>(ptr_)->num_owners.fetch_add(1, std::memory_order_relaxed);
}

SharedPreHashedString(SharedPreHashedString&& tmp) noexcept :
ptr_{std::exchange(tmp.ptr_, nullptr)}
{}

SharedPreHashedString& operator=(const SharedPreHashedString& src)
SharedPreHashedString& operator=(const SharedPreHashedString& src) noexcept
{
SharedPreHashedString copy{src};
swap(*this, copy);
return *this;
}

SharedPreHashedString& operator=(SharedPreHashedString&& tmp) noexcept
{
swap(*this, tmp);
return *this;
}

~SharedPreHashedString() noexcept
{
if (ptr_ and static_cast<Metadata*>(ptr_)->num_owners.fetch_sub(1, std::memory_order_relaxed) == 1) {
if (static_cast<Metadata*>(ptr_)->num_owners.fetch_sub(1, std::memory_order_relaxed) == 1) {
::operator delete(ptr_, std::align_val_t{alignof(Metadata)});
}
}
Expand Down Expand Up @@ -253,6 +247,11 @@ namespace osc
return lhs <=> std::string_view{rhs};
}

friend std::ostream& operator<<(std::ostream& lhs, const SharedPreHashedString& rhs)
{
return lhs << std::string_view{rhs};
}

// returns the number of different `SharedPreHashedString` instances (`this` included)
// managing the same underlying string data. In a multithreaded environment, the value
// returned by `use_count` is approximate (i.e. the implementation uses a
Expand All @@ -264,6 +263,12 @@ namespace osc
private:
friend struct std::hash<SharedPreHashedString>;

const SharedPreHashedString& static_default_instance()
{
static SharedPreHashedString s_default_instance{std::string_view{}};
return s_default_instance;
}

struct Metadata final {

Metadata(std::string_view str) noexcept :
Expand Down
102 changes: 18 additions & 84 deletions src/oscar/Utils/StringName.cpp
Original file line number Diff line number Diff line change
@@ -1,106 +1,40 @@
#include "StringName.h"

#include <ankerl/unordered_dense.h>

#include <oscar/Utils/SynchronizedValue.h>
#include <oscar/Utils/TransparentStringHasher.h>

#include <concepts>
#include <ankerl/unordered_dense.h>

#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>

using namespace osc;
using osc::detail::StringNameData;

namespace
{
// this is what's stored in the lookup table
//
// `StringNameData` _must_ be reference-stable w.r.t. downstream
// code because users are reading/reference incrementing raw
// pointers
struct StringNameDataPtr final {
public:
explicit StringNameDataPtr(std::string_view str) :
ptr_{std::make_unique<StringNameData>(str)}
{}

friend bool operator==(std::string_view lhs, const StringNameDataPtr& rhs)
{
return lhs == rhs.ptr_->value();
}

StringNameData* operator->() const
{
return ptr_.get();
}

StringNameData& operator*() const
{
return *ptr_;
}
private:
std::unique_ptr<StringNameData> ptr_;
};

struct TransparentHasher : public TransparentStringHasher {
using TransparentStringHasher::operator();

size_t operator()(const StringNameDataPtr& ptr) const noexcept
{
return ptr->hash();
}
};

using StringNameLookup = ankerl::unordered_dense::set<
StringNameDataPtr,
TransparentHasher,
std::equal_to<>
>;
using FastStringLookup = ankerl::unordered_dense::set<SharedPreHashedString, TransparentStringHasher, std::equal_to<>>;

SynchronizedValue<StringNameLookup>& get_global_string_name_lut()
SynchronizedValue<FastStringLookup>& get_global_lookup()
{
static SynchronizedValue<StringNameLookup> s_lut;
return s_lut;
static SynchronizedValue<FastStringLookup> s_lookup;
return s_lookup;
}
}

template<typename StringLike>
requires
std::constructible_from<std::string, StringLike&&> and
std::convertible_to<StringLike&&, std::string_view>
StringNameData& possibly_construct_then_get_data(StringLike&& input)
{
auto [it, inserted] = get_global_string_name_lut().lock()->emplace(std::forward<StringLike>(input));
if (not inserted) {
(*it)->increment_owner_count();
}
return **it;
}
osc::StringName::StringName(std::string_view sv) :
SharedPreHashedString{*get_global_lookup().lock()->emplace(sv).first}
{}

void decrement_then_possibly_destruct_data(StringNameData& data)
{
if (data.decrement_owner_count()) {
get_global_string_name_lut().lock()->erase(data.value());
}
osc::StringName::~StringName() noexcept
{
if (empty()) {
return; // special case: the default constructor doesn't use the global lookup
}

// edge-case for blank string (common)
const StringName& get_cached_blank_string_data()
{
static const StringName s_blank_string{std::string_view{}};
return s_blank_string;
if (use_count() > 2) {
return; // other `StringName`s with the same value are still using the data
}
}

osc::StringName::StringName() : StringName{get_cached_blank_string_data()} {}
osc::StringName::StringName(std::string&& tmp) : data_{&possibly_construct_then_get_data(std::move(tmp))} {}
osc::StringName::StringName(std::string_view sv) : data_{&possibly_construct_then_get_data(sv)} {}
osc::StringName::~StringName() noexcept { decrement_then_possibly_destruct_data(*data_); }

std::ostream& osc::operator<<(std::ostream& out, const StringName& s)
{
return out << std::string_view{s};
// else: clear it from the global table
get_global_lookup().lock()->erase(static_cast<const SharedPreHashedString&>(*this));
}
Loading

0 comments on commit 0c5dea4

Please sign in to comment.