mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
Merge fe3b3a24f2
into b3e0bcfb31
This commit is contained in:
commit
c2180dab5b
5 changed files with 1460 additions and 1 deletions
|
@ -21,7 +21,7 @@ add_subdirectory(json)
|
|||
set(SEARCH_LIB query_parser)
|
||||
|
||||
add_library(dfly_core allocation_tracker.cc bloom.cc compact_object.cc dense_set.cc
|
||||
dragonfly_core.cc extent_tree.cc
|
||||
dragonfly_core.cc extent_tree.cc intrusive_string_list.cc
|
||||
interpreter.cc glob_matcher.cc mi_memory_resource.cc qlist.cc sds_utils.cc
|
||||
segment_allocator.cc score_map.cc small_string.cc sorted_map.cc task_queue.cc
|
||||
tx_queue.cc string_set.cc string_map.cc top_keys.cc detail/bitpacking.cc)
|
||||
|
@ -40,6 +40,7 @@ cxx_test(interpreter_test dfly_core LABELS DFLY)
|
|||
|
||||
cxx_test(string_set_test dfly_core LABELS DFLY)
|
||||
cxx_test(string_map_test dfly_core LABELS DFLY)
|
||||
cxx_test(intrusive_string_set_test dfly_core LABELS DFLY)
|
||||
cxx_test(sorted_map_test dfly_core redis_test_lib LABELS DFLY)
|
||||
cxx_test(bptree_set_test dfly_core LABELS DFLY)
|
||||
cxx_test(score_map_test dfly_core LABELS DFLY)
|
||||
|
|
10
src/core/intrusive_string_list.cc
Normal file
10
src/core/intrusive_string_list.cc
Normal file
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2025, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#include "intrusive_string_list.h"
|
||||
|
||||
namespace dfly {
|
||||
ISLEntry IntrusiveStringList::end_;
|
||||
|
||||
} // namespace dfly
|
367
src/core/intrusive_string_list.h
Normal file
367
src/core/intrusive_string_list.h
Normal file
|
@ -0,0 +1,367 @@
|
|||
// Copyright 2024, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// #include <cassert>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
extern "C" {
|
||||
#include "redis/zmalloc.h"
|
||||
}
|
||||
|
||||
namespace dfly {
|
||||
// doesn't possess memory, it should be created and release manually
|
||||
class ISLEntry {
|
||||
friend class IntrusiveStringList;
|
||||
|
||||
// we can assume that high 12 bits of user address space
|
||||
// can be used for tagging. At most 52 bits of address are reserved for
|
||||
// some configurations, and usually it's 48 bits.
|
||||
// https://docs.kernel.org/arch/arm64/memory.html
|
||||
static constexpr size_t kTtlBit = 1ULL << 55;
|
||||
static constexpr size_t kTagMask = 4095ULL << 52; // we reserve 12 high bits.
|
||||
|
||||
public:
|
||||
ISLEntry() = default;
|
||||
|
||||
ISLEntry(char* data) {
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
ISLEntry(const ISLEntry& e) = default;
|
||||
ISLEntry(ISLEntry&& e) {
|
||||
data_ = e.data_;
|
||||
e.data_ = nullptr;
|
||||
}
|
||||
|
||||
ISLEntry& operator=(const ISLEntry& e) = default;
|
||||
ISLEntry& operator=(ISLEntry&& e) {
|
||||
data_ = e.data_;
|
||||
e.data_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
std::string_view Key() const {
|
||||
return {GetKeyData(), GetKeySize()};
|
||||
}
|
||||
|
||||
bool HasExpiry() const {
|
||||
return HasTtl();
|
||||
}
|
||||
|
||||
// returns the expiry time of the current entry or UINT32_MAX if no ttl is set.
|
||||
uint32_t ExpiryTime() const {
|
||||
std::uint32_t res = UINT32_MAX;
|
||||
if (HasTtl()) {
|
||||
std::memcpy(&res, Raw() + sizeof(ISLEntry*), sizeof(res));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO consider another option to implement iterator
|
||||
ISLEntry* operator->() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected:
|
||||
static ISLEntry Create(std::string_view key, char* next = nullptr,
|
||||
uint32_t ttl_sec = UINT32_MAX) {
|
||||
uint32_t key_size = key.size();
|
||||
|
||||
bool has_ttl = ttl_sec != UINT32_MAX;
|
||||
|
||||
auto size = sizeof(next) + sizeof(key_size) + key_size + has_ttl * sizeof(ttl_sec);
|
||||
|
||||
char* data = (char*)zmalloc(size);
|
||||
ISLEntry res(data);
|
||||
|
||||
std::memcpy(data, &next, sizeof(next));
|
||||
|
||||
auto* ttl_pos = data + sizeof(next);
|
||||
if (has_ttl) {
|
||||
res.SetTtlBit(true);
|
||||
std::memcpy(ttl_pos, &ttl_sec, sizeof(ttl_sec));
|
||||
}
|
||||
|
||||
auto* key_size_pos = ttl_pos + res.GetTtlSize();
|
||||
std::memcpy(key_size_pos, &key_size, sizeof(key_size));
|
||||
|
||||
auto* key_pos = key_size_pos + sizeof(key_size);
|
||||
std::memcpy(key_pos, key.data(), key_size);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void Destroy(ISLEntry& entry) {
|
||||
zfree(entry.Raw());
|
||||
}
|
||||
|
||||
ISLEntry Next() const {
|
||||
ISLEntry next;
|
||||
std::memcpy(&next.data_, Raw(), sizeof(next));
|
||||
return next;
|
||||
}
|
||||
|
||||
ISLEntry FakePrev() {
|
||||
return ISLEntry(reinterpret_cast<char*>(&data_));
|
||||
}
|
||||
|
||||
void SetNext(ISLEntry next) {
|
||||
std::memcpy(Raw(), &next, sizeof(next));
|
||||
}
|
||||
|
||||
const char* GetKeyData() const {
|
||||
return Raw() + sizeof(ISLEntry*) + sizeof(uint32_t) + GetTtlSize();
|
||||
}
|
||||
|
||||
uint32_t GetKeySize() const {
|
||||
uint32_t size = 0;
|
||||
std::memcpy(&size, Raw() + sizeof(ISLEntry*) + GetTtlSize(), sizeof(size));
|
||||
return size;
|
||||
}
|
||||
|
||||
uint64_t uptr() const {
|
||||
return uint64_t(data_);
|
||||
}
|
||||
|
||||
char* Raw() const {
|
||||
return (char*)(uptr() & ~kTagMask);
|
||||
}
|
||||
|
||||
void SetTtlBit(bool b) {
|
||||
if (b)
|
||||
data_ = (char*)(uptr() | kTtlBit);
|
||||
else
|
||||
data_ = (char*)(uptr() & (~kTtlBit));
|
||||
}
|
||||
|
||||
bool HasTtl() const {
|
||||
return (uptr() & kTtlBit) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool UpdateTtl(uint32_t ttl_sec) {
|
||||
if (HasTtl()) {
|
||||
auto* ttl_pos = Raw() + sizeof(char*);
|
||||
std::memcpy(ttl_pos, &ttl_sec, sizeof(ttl_sec));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t GetTtlSize() const {
|
||||
return HasTtl() ? sizeof(std::uint32_t) : 0;
|
||||
}
|
||||
|
||||
// TODO consider use SDS strings or other approach
|
||||
// TODO add optimization for big keys
|
||||
// memory daya layout [ISLEntry*, key_size, key]
|
||||
char* data_ = nullptr;
|
||||
};
|
||||
|
||||
class UniqueISLEntry : private ISLEntry {
|
||||
public:
|
||||
~UniqueISLEntry() {
|
||||
Destroy(*this);
|
||||
}
|
||||
|
||||
UniqueISLEntry() = default;
|
||||
UniqueISLEntry(ISLEntry e) : ISLEntry(e) {
|
||||
}
|
||||
UniqueISLEntry(UniqueISLEntry&& e) = default;
|
||||
UniqueISLEntry& operator=(UniqueISLEntry&& e) = default;
|
||||
|
||||
using ISLEntry::ExpiryTime;
|
||||
using ISLEntry::HasExpiry;
|
||||
using ISLEntry::Key;
|
||||
using ISLEntry::operator bool;
|
||||
|
||||
ISLEntry Release() {
|
||||
ISLEntry res = *this;
|
||||
data_ = nullptr;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
UniqueISLEntry(const UniqueISLEntry&) = delete;
|
||||
};
|
||||
|
||||
class IntrusiveStringList {
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = ISLEntry;
|
||||
using pointer = ISLEntry*;
|
||||
using reference = ISLEntry&;
|
||||
|
||||
iterator(ISLEntry prev = end_.FakePrev()) : prev_(prev) {
|
||||
DCHECK(prev);
|
||||
}
|
||||
|
||||
uint32_t ExpiryTime() const {
|
||||
return prev_.Next().ExpiryTime();
|
||||
}
|
||||
|
||||
void SetExpiryTime(uint32_t ttl_sec) {
|
||||
auto entry = prev_.Next();
|
||||
|
||||
if (!entry.UpdateTtl(ttl_sec)) {
|
||||
ISLEntry new_entry = ISLEntry::Create(entry.Key(), entry.Next().data_, ttl_sec);
|
||||
ISLEntry::Destroy(entry);
|
||||
prev_.SetNext(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasExpiry() const {
|
||||
return prev_.Next().HasExpiry();
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
prev_ = prev_.Next();
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return prev_.Next();
|
||||
}
|
||||
|
||||
value_type operator*() {
|
||||
return prev_.Next();
|
||||
}
|
||||
|
||||
value_type operator->() {
|
||||
return prev_.Next();
|
||||
}
|
||||
|
||||
bool operator==(const iterator& r) {
|
||||
return prev_.Next() == r.prev_.Next();
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& r) {
|
||||
return !operator==(r);
|
||||
}
|
||||
|
||||
private:
|
||||
ISLEntry prev_;
|
||||
};
|
||||
|
||||
~IntrusiveStringList() {
|
||||
while (start_) {
|
||||
auto next = start_.Next();
|
||||
ISLEntry::Destroy(start_);
|
||||
start_ = next;
|
||||
}
|
||||
}
|
||||
|
||||
IntrusiveStringList() = default;
|
||||
IntrusiveStringList(IntrusiveStringList&& r) {
|
||||
start_ = r.start_;
|
||||
r.start_ = {};
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
return start_.FakePrev();
|
||||
}
|
||||
|
||||
static iterator end() {
|
||||
return end_.FakePrev();
|
||||
}
|
||||
|
||||
ISLEntry Insert(ISLEntry e) {
|
||||
e.SetNext(start_);
|
||||
start_ = e;
|
||||
return start_;
|
||||
}
|
||||
|
||||
UniqueISLEntry Pop(uint32_t curr_time) {
|
||||
for (auto it = start_; it && it.ExpiryTime() < curr_time; it = start_) {
|
||||
start_ = it.Next();
|
||||
ISLEntry::Destroy(it);
|
||||
}
|
||||
auto res = start_;
|
||||
if (start_) {
|
||||
start_ = start_.Next();
|
||||
// TODO consider to res.SetNext(nullptr); for now it looks superfluous
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Empty() {
|
||||
return !start_;
|
||||
}
|
||||
|
||||
// TODO consider to wrap ISLEntry to prevent usage out of the list
|
||||
ISLEntry Emplace(std::string_view key, uint32_t ttl_sec = UINT32_MAX) {
|
||||
return Insert(ISLEntry::Create(key, nullptr, ttl_sec));
|
||||
}
|
||||
|
||||
// TODO consider to wrap ISLEntry to prevent usage out of the list
|
||||
IntrusiveStringList::iterator Find(std::string_view str) {
|
||||
auto it = begin();
|
||||
for (; it && it->Key() != str; ++it)
|
||||
;
|
||||
return it;
|
||||
}
|
||||
|
||||
bool Erase(std::string_view str) {
|
||||
auto cond = [str](const ISLEntry e) { return str == e.Key(); };
|
||||
return Erase(cond);
|
||||
}
|
||||
|
||||
template <class T, std::enable_if_t<std::is_invocable_v<T, ISLEntry>>* = nullptr>
|
||||
bool Erase(const T& cond) {
|
||||
if (!start_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto it = start_; cond(it)) {
|
||||
start_ = it.Next();
|
||||
ISLEntry::Destroy(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto prev = start_, it = start_.Next(); it; prev = it, it = it.Next()) {
|
||||
if (cond(it)) {
|
||||
prev.SetNext(it.Next());
|
||||
ISLEntry::Destroy(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class T, std::enable_if_t<std::is_invocable_v<T, std::string_view>>* = nullptr>
|
||||
bool Scan(const T& cb, uint32_t curr_time) {
|
||||
for (auto it = start_; it && it.ExpiryTime() < curr_time; it = start_) {
|
||||
start_ = it.Next();
|
||||
ISLEntry::Destroy(it);
|
||||
}
|
||||
|
||||
for (auto curr = start_, next = start_; curr; curr = next) {
|
||||
cb(curr.Key());
|
||||
next = curr.Next();
|
||||
for (auto tmp = next; tmp && tmp.ExpiryTime() < curr_time; tmp = next) {
|
||||
next = next.Next();
|
||||
ISLEntry::Destroy(tmp);
|
||||
}
|
||||
curr.SetNext(next);
|
||||
}
|
||||
return start_;
|
||||
}
|
||||
|
||||
private:
|
||||
ISLEntry start_;
|
||||
static ISLEntry end_;
|
||||
};
|
||||
|
||||
} // namespace dfly
|
281
src/core/intrusive_string_set.h
Normal file
281
src/core/intrusive_string_set.h
Normal file
|
@ -0,0 +1,281 @@
|
|||
// Copyright 2024, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <absl/numeric/bits.h>
|
||||
#include <absl/types/span.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "base/hash.h"
|
||||
#include "base/pmr/memory_resource.h"
|
||||
#include "intrusive_string_list.h"
|
||||
|
||||
namespace dfly {
|
||||
|
||||
class IntrusiveStringSet {
|
||||
using Buckets =
|
||||
std::vector<IntrusiveStringList, PMR_NS::polymorphic_allocator<IntrusiveStringList>>;
|
||||
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = ISLEntry;
|
||||
using pointer = ISLEntry*;
|
||||
using reference = ISLEntry&;
|
||||
|
||||
iterator(Buckets::iterator it, Buckets::iterator end, IntrusiveStringList::iterator entry)
|
||||
: buckets_it_(it), end_(end), entry_(entry) {
|
||||
}
|
||||
|
||||
iterator(Buckets::iterator it, Buckets::iterator end) : buckets_it_(it), end_(end) {
|
||||
SetEntryIt();
|
||||
}
|
||||
|
||||
// uint32_t ExpiryTime() const {
|
||||
// return prev.Next()->ExpiryTime();
|
||||
// }
|
||||
|
||||
void SetExpiryTime(uint32_t ttl_sec) {
|
||||
entry_.SetExpiryTime(ttl_sec);
|
||||
}
|
||||
|
||||
// bool HasExpiry() const {
|
||||
// return curr_entry_.HasExpiry();
|
||||
// }
|
||||
|
||||
// void Advance();
|
||||
|
||||
iterator& operator++() {
|
||||
// TODO add expiration logic
|
||||
if (entry_)
|
||||
++entry_;
|
||||
if (!entry_) {
|
||||
++buckets_it_;
|
||||
SetEntryIt();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& r) const {
|
||||
return buckets_it_ == r.buckets_it_ && entry_ == r.entry_;
|
||||
}
|
||||
|
||||
bool operator!=(const iterator& r) const {
|
||||
return !operator==(r);
|
||||
}
|
||||
|
||||
IntrusiveStringList::iterator::value_type operator*() {
|
||||
return *entry_;
|
||||
}
|
||||
|
||||
IntrusiveStringList::iterator operator->() {
|
||||
return entry_;
|
||||
}
|
||||
|
||||
private:
|
||||
// find valid entry_ iterator starting from buckets_it_ and set it
|
||||
void SetEntryIt() {
|
||||
for (; buckets_it_ != end_; ++buckets_it_) {
|
||||
if (!buckets_it_->Empty()) {
|
||||
entry_ = buckets_it_->begin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Buckets::iterator buckets_it_;
|
||||
Buckets::iterator end_;
|
||||
IntrusiveStringList::iterator entry_;
|
||||
};
|
||||
|
||||
iterator begin() {
|
||||
return iterator(entries_.begin(), entries_.end());
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator(entries_.end(), entries_.end());
|
||||
}
|
||||
|
||||
explicit IntrusiveStringSet(PMR_NS::memory_resource* mr = PMR_NS::get_default_resource())
|
||||
: entries_(mr) {
|
||||
}
|
||||
|
||||
ISLEntry Add(std::string_view str, uint32_t ttl_sec = UINT32_MAX) {
|
||||
if (entries_.empty() || size_ >= entries_.size()) {
|
||||
Reserve(Capacity() * 2);
|
||||
}
|
||||
const auto bucket_id = BucketId(Hash(str));
|
||||
auto& bucket = entries_[bucket_id];
|
||||
|
||||
if (auto existed_item = bucket.Find(str); existed_item) {
|
||||
// TODO consider common implementation for key value pair
|
||||
return ISLEntry();
|
||||
}
|
||||
|
||||
return AddUnique(str, bucket, ttl_sec);
|
||||
}
|
||||
|
||||
void Reserve(size_t sz) {
|
||||
sz = absl::bit_ceil(sz);
|
||||
if (sz > entries_.size()) {
|
||||
size_t prev_size = entries_.size();
|
||||
capacity_log_ = absl::bit_width(sz) - 1;
|
||||
entries_.resize(sz);
|
||||
Rehash(prev_size);
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
capacity_log_ = 0;
|
||||
entries_.resize(0);
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
ISLEntry AddUnique(std::string_view str, IntrusiveStringList& bucket,
|
||||
uint32_t ttl_sec = UINT32_MAX) {
|
||||
++size_;
|
||||
return bucket.Emplace(str, ttl_sec);
|
||||
}
|
||||
|
||||
unsigned AddMany(absl::Span<std::string_view> span, uint32_t ttl_sec, bool keepttl) {
|
||||
Reserve(span.size());
|
||||
unsigned res = 0;
|
||||
for (auto& s : span) {
|
||||
const auto bucket_id = BucketId(Hash(s));
|
||||
auto& bucket = entries_[bucket_id];
|
||||
if (auto existed_item = bucket.Find(s); existed_item) {
|
||||
// TODO update TTL
|
||||
} else {
|
||||
++res;
|
||||
AddUnique(s, bucket, ttl_sec);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* stable scanning api. has the same guarantees as redis scan command.
|
||||
* we avoid doing bit-reverse by using a different function to derive a bucket id
|
||||
* from hash values. By using msb part of hash we make it "stable" with respect to
|
||||
* rehashes. For example, with table log size 4 (size 16), entries in bucket id
|
||||
* 1110 come from hashes 1110XXXXX.... When a table grows to log size 5,
|
||||
* these entries can move either to 11100 or 11101. So if we traversed with our cursor
|
||||
* range [0000-1110], it's guaranteed that in grown table we do not need to cover again
|
||||
* [00000-11100]. Similarly with shrinkage, if a table is shrunk to log size 3,
|
||||
* keys from 1110 and 1111 will move to bucket 111. Again, it's guaranteed that we
|
||||
* covered the range [000-111] (all keys in that case).
|
||||
* Returns: next cursor or 0 if reached the end of scan.
|
||||
* cursor = 0 - initiates a new scan.
|
||||
*/
|
||||
|
||||
using ItemCb = std::function<void(std::string_view)>;
|
||||
|
||||
uint32_t Scan(uint32_t cursor, const ItemCb& cb) {
|
||||
uint32_t entries_idx = cursor >> (32 - capacity_log_);
|
||||
|
||||
// First find the bucket to scan, skip empty buckets.
|
||||
for (; entries_idx < entries_.size(); ++entries_idx) {
|
||||
if (entries_[entries_idx].Scan(cb, time_now_)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (++entries_idx >= entries_.size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return entries_idx << (32 - capacity_log_);
|
||||
}
|
||||
|
||||
UniqueISLEntry Pop() {
|
||||
for (auto& bucket : entries_) {
|
||||
if (auto res = bucket.Pop(time_now_); res) {
|
||||
--size_;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Erase(std::string_view str) {
|
||||
if (entries_.empty())
|
||||
return false;
|
||||
auto bucket_id = BucketId(Hash(str));
|
||||
return entries_[bucket_id].Erase(str);
|
||||
}
|
||||
|
||||
iterator Find(std::string_view member) {
|
||||
if (entries_.empty())
|
||||
return end();
|
||||
auto bucket_id = BucketId(Hash(member));
|
||||
auto entry_it = entries_.begin() + bucket_id;
|
||||
auto res = entry_it->Find(member);
|
||||
return iterator(res ? entry_it : entries_.end(), entries_.end(), res);
|
||||
}
|
||||
|
||||
bool Contains(std::string_view member) {
|
||||
return Find(member) != end();
|
||||
}
|
||||
|
||||
// Returns the number of elements in the map. Note that it might be that some of these elements
|
||||
// have expired and can't be accessed.
|
||||
size_t UpperBoundSize() const {
|
||||
return size_;
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return size_ == 0;
|
||||
}
|
||||
|
||||
std::uint32_t Capacity() const {
|
||||
return 1 << capacity_log_;
|
||||
}
|
||||
|
||||
// set an abstract time that allows expiry.
|
||||
void set_time(uint32_t val) {
|
||||
time_now_ = val;
|
||||
}
|
||||
|
||||
uint32_t time_now() const {
|
||||
return time_now_;
|
||||
}
|
||||
|
||||
private:
|
||||
// was Grow in StringSet
|
||||
void Rehash(size_t prev_size) {
|
||||
for (int64_t i = prev_size - 1; i >= 0; --i) {
|
||||
auto list = std::move(entries_[i]);
|
||||
for (auto entry = list.Pop(time_now_); entry; entry = list.Pop(time_now_)) {
|
||||
auto bucket_id = BucketId(Hash(entry.Key()));
|
||||
entries_[bucket_id].Insert(entry.Release());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t BucketId(uint64_t hash) const {
|
||||
assert(capacity_log_ > 0);
|
||||
return hash >> (64 - capacity_log_);
|
||||
}
|
||||
|
||||
uint64_t Hash(std::string_view str) const {
|
||||
constexpr XXH64_hash_t kHashSeed = 24061983;
|
||||
return XXH3_64bits_withSeed(str.data(), str.size(), kHashSeed);
|
||||
}
|
||||
|
||||
private:
|
||||
std::uint32_t capacity_log_ = 0;
|
||||
std::uint32_t size_ = 0; // number of elements in the set.
|
||||
std::uint32_t time_now_ = 0;
|
||||
|
||||
static_assert(sizeof(IntrusiveStringList) == sizeof(void*),
|
||||
"IntrusiveStringList should be just a pointer");
|
||||
Buckets entries_;
|
||||
};
|
||||
|
||||
} // namespace dfly
|
800
src/core/intrusive_string_set_test.cc
Normal file
800
src/core/intrusive_string_set_test.cc
Normal file
|
@ -0,0 +1,800 @@
|
|||
// Copyright 2022, DragonflyDB authors. All rights reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
//
|
||||
|
||||
#include "core/intrusive_string_set.h"
|
||||
|
||||
#include <mimalloc.h>
|
||||
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "base/gtest.h"
|
||||
#include "core/mi_memory_resource.h"
|
||||
#include "glog/logging.h"
|
||||
|
||||
extern "C" {
|
||||
#include "redis/zmalloc.h"
|
||||
}
|
||||
|
||||
namespace dfly {
|
||||
|
||||
using namespace std;
|
||||
|
||||
class ISSAllocator : public PMR_NS::memory_resource {
|
||||
public:
|
||||
bool all_freed() const {
|
||||
return alloced_ == 0;
|
||||
}
|
||||
|
||||
void* do_allocate(size_t bytes, size_t alignment) override {
|
||||
alloced_ += bytes;
|
||||
void* p = PMR_NS::new_delete_resource()->allocate(bytes, alignment);
|
||||
return p;
|
||||
}
|
||||
|
||||
void do_deallocate(void* p, size_t bytes, size_t alignment) override {
|
||||
alloced_ -= bytes;
|
||||
return PMR_NS::new_delete_resource()->deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
bool do_is_equal(const PMR_NS::memory_resource& other) const noexcept override {
|
||||
return PMR_NS::new_delete_resource()->is_equal(other);
|
||||
}
|
||||
|
||||
private:
|
||||
size_t alloced_ = 0;
|
||||
};
|
||||
|
||||
class IntrusiveStringSetTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() {
|
||||
auto* tlh = mi_heap_get_backing();
|
||||
init_zmalloc_threadlocal(tlh);
|
||||
}
|
||||
|
||||
static void TearDownTestSuite() {
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
ss_ = new IntrusiveStringSet(&alloc_);
|
||||
generator_.seed(0);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
delete ss_;
|
||||
|
||||
// ensure there are no memory leaks after every test
|
||||
EXPECT_TRUE(alloc_.all_freed());
|
||||
EXPECT_EQ(zmalloc_used_memory_tl, 0);
|
||||
}
|
||||
|
||||
IntrusiveStringSet* ss_;
|
||||
ISSAllocator alloc_;
|
||||
mt19937 generator_;
|
||||
};
|
||||
|
||||
static string random_string(mt19937& rand, unsigned len) {
|
||||
const string_view alpanum = "1234567890abcdefghijklmnopqrstuvwxyz";
|
||||
string ret;
|
||||
ret.reserve(len);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
ret += alpanum[rand() % alpanum.size()];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, IntrusiveStringListTest) {
|
||||
IntrusiveStringList isl;
|
||||
ISLEntry test = isl.Emplace("0123456789");
|
||||
|
||||
EXPECT_EQ(test.Key(), "0123456789"sv);
|
||||
|
||||
test = isl.Emplace("123456789");
|
||||
|
||||
EXPECT_EQ(test.Key(), "123456789"sv);
|
||||
|
||||
test = isl.Emplace("23456789");
|
||||
|
||||
EXPECT_EQ(isl.Find("0123456789")->Key(), "0123456789"sv);
|
||||
EXPECT_EQ(isl.Find("23456789")->Key(), "23456789"sv);
|
||||
EXPECT_EQ(isl.Find("123456789")->Key(), "123456789"sv);
|
||||
EXPECT_FALSE(isl.Find("test"));
|
||||
|
||||
EXPECT_TRUE(isl.Erase("23456789"));
|
||||
EXPECT_FALSE(isl.Find("23456789"));
|
||||
EXPECT_FALSE(isl.Erase("test"));
|
||||
EXPECT_FALSE(isl.Find("test"));
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, IntrusiveStringSetAddFindTest) {
|
||||
IntrusiveStringSet ss;
|
||||
std::set<std::string> test_set;
|
||||
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
test_set.insert(base::RandStr(20));
|
||||
}
|
||||
|
||||
for (const auto& s : test_set) {
|
||||
auto e = ss.Add(s);
|
||||
EXPECT_EQ(e.Key(), s);
|
||||
}
|
||||
|
||||
for (const auto& s : test_set) {
|
||||
auto e = ss.Find(s);
|
||||
EXPECT_EQ(e->Key(), s);
|
||||
}
|
||||
|
||||
EXPECT_EQ(ss.Capacity(), 16384);
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, Basic) {
|
||||
EXPECT_TRUE(ss_->Add("foo"sv));
|
||||
EXPECT_TRUE(ss_->Add("bar"sv));
|
||||
EXPECT_FALSE(ss_->Add("foo"sv));
|
||||
EXPECT_FALSE(ss_->Add("bar"sv));
|
||||
EXPECT_TRUE(ss_->Contains("foo"sv));
|
||||
EXPECT_TRUE(ss_->Contains("bar"sv));
|
||||
EXPECT_EQ(2, ss_->UpperBoundSize());
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, StandardAddErase) {
|
||||
EXPECT_TRUE(ss_->Add("@@@@@@@@@@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("A@@@@@@@@@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("AA@@@@@@@@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("AAA@@@@@@@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("AAAAAAAAA@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("AAAAAAAAAA@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Add("AAAAAAAAAAAAAAA@"));
|
||||
EXPECT_TRUE(ss_->Add("AAAAAAAAAAAAAAAA"));
|
||||
EXPECT_TRUE(ss_->Add("AAAAAAAAAAAAAAAD"));
|
||||
EXPECT_TRUE(ss_->Add("BBBBBAAAAAAAAAAA"));
|
||||
EXPECT_TRUE(ss_->Add("BBBBBBBBAAAAAAAA"));
|
||||
EXPECT_TRUE(ss_->Add("CCCCCBBBBBBBBBBB"));
|
||||
|
||||
// Remove link in the middle of chain
|
||||
EXPECT_TRUE(ss_->Erase("BBBBBBBBAAAAAAAA"));
|
||||
// Remove start of a chain
|
||||
EXPECT_TRUE(ss_->Erase("CCCCCBBBBBBBBBBB"));
|
||||
// Remove end of link
|
||||
EXPECT_TRUE(ss_->Erase("AAA@@@@@@@@@@@@@"));
|
||||
// Remove only item in chain
|
||||
EXPECT_TRUE(ss_->Erase("AA@@@@@@@@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Erase("AAAAAAAAA@@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Erase("AAAAAAAAAA@@@@@@"));
|
||||
EXPECT_TRUE(ss_->Erase("AAAAAAAAAAAAAAA@"));
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, DisplacedBug) {
|
||||
string_view vals[] = {"imY", "OVl", "NhH", "BCe", "YDL", "lpb",
|
||||
"nhF", "xod", "zYR", "PSa", "hce", "cTR"};
|
||||
ss_->AddMany(absl::MakeSpan(vals), UINT32_MAX, false);
|
||||
|
||||
ss_->Add("fIc");
|
||||
ss_->Erase("YDL");
|
||||
ss_->Add("fYs");
|
||||
ss_->Erase("hce");
|
||||
ss_->Erase("nhF");
|
||||
ss_->Add("dye");
|
||||
ss_->Add("xZT");
|
||||
ss_->Add("LVK");
|
||||
ss_->Erase("zYR");
|
||||
ss_->Erase("fYs");
|
||||
ss_->Add("ueB");
|
||||
ss_->Erase("PSa");
|
||||
ss_->Erase("OVl");
|
||||
ss_->Add("cga");
|
||||
ss_->Add("too");
|
||||
ss_->Erase("ueB");
|
||||
ss_->Add("HZe");
|
||||
ss_->Add("oQn");
|
||||
ss_->Erase("too");
|
||||
ss_->Erase("HZe");
|
||||
ss_->Erase("xZT");
|
||||
ss_->Erase("cga");
|
||||
ss_->Erase("cTR");
|
||||
ss_->Erase("BCe");
|
||||
ss_->Add("eua");
|
||||
ss_->Erase("lpb");
|
||||
ss_->Add("OXK");
|
||||
ss_->Add("QmO");
|
||||
ss_->Add("SzV");
|
||||
ss_->Erase("QmO");
|
||||
ss_->Add("jbe");
|
||||
ss_->Add("BPN");
|
||||
ss_->Add("OfH");
|
||||
ss_->Add("Muf");
|
||||
ss_->Add("CwP");
|
||||
ss_->Erase("Muf");
|
||||
ss_->Erase("xod");
|
||||
ss_->Add("Cis");
|
||||
ss_->Add("Xvd");
|
||||
ss_->Erase("SzV");
|
||||
ss_->Erase("eua");
|
||||
ss_->Add("DGb");
|
||||
ss_->Add("leD");
|
||||
ss_->Add("MVX");
|
||||
ss_->Add("HPq");
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, Resizing) {
|
||||
constexpr size_t num_strs = 4096;
|
||||
unordered_set<string> strs;
|
||||
while (strs.size() != num_strs) {
|
||||
auto str = random_string(generator_, 10);
|
||||
strs.insert(str);
|
||||
}
|
||||
|
||||
unsigned size = 0;
|
||||
for (auto it = strs.begin(); it != strs.end(); ++it) {
|
||||
const auto& str = *it;
|
||||
EXPECT_TRUE(ss_->Add(str, 1));
|
||||
EXPECT_EQ(ss_->UpperBoundSize(), size + 1);
|
||||
|
||||
// make sure we haven't lost any items after a grow
|
||||
// which happens every power of 2
|
||||
if ((size & (size - 1)) == 0) {
|
||||
for (auto j = strs.begin(); j != it; ++j) {
|
||||
const auto& str = *j;
|
||||
auto it = ss_->Find(str);
|
||||
ASSERT_NE(it, ss_->end());
|
||||
EXPECT_TRUE(it->HasExpiry());
|
||||
EXPECT_EQ(it->ExpiryTime(), ss_->time_now() + 1);
|
||||
}
|
||||
}
|
||||
++size;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, SimpleScan) {
|
||||
unordered_set<string_view> info = {"foo", "bar"};
|
||||
unordered_set<string_view> seen;
|
||||
|
||||
for (auto str : info) {
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
uint32_t cursor = 0;
|
||||
do {
|
||||
cursor = ss_->Scan(cursor, [&](std::string_view str) {
|
||||
EXPECT_TRUE(info.count(str));
|
||||
seen.insert(str);
|
||||
});
|
||||
} while (cursor != 0);
|
||||
|
||||
EXPECT_EQ(seen.size(), info.size());
|
||||
EXPECT_TRUE(equal(seen.begin(), seen.end(), info.begin()));
|
||||
}
|
||||
|
||||
// Ensure REDIS scan guarantees are met
|
||||
TEST_F(IntrusiveStringSetTest, ScanGuarantees) {
|
||||
unordered_set<string_view> to_be_seen = {"foo", "bar"};
|
||||
unordered_set<string_view> not_be_seen = {"AAA", "BBB"};
|
||||
unordered_set<string_view> maybe_seen = {"AA@@@@@@@@@@@@@@", "AAA@@@@@@@@@@@@@",
|
||||
"AAAAAAAAA@@@@@@@", "AAAAAAAAAA@@@@@@"};
|
||||
unordered_set<string_view> seen;
|
||||
|
||||
auto scan_callback = [&](std::string_view str) {
|
||||
EXPECT_TRUE(to_be_seen.count(str) || maybe_seen.count(str));
|
||||
EXPECT_FALSE(not_be_seen.count(str));
|
||||
if (to_be_seen.count(str)) {
|
||||
seen.insert(str);
|
||||
}
|
||||
};
|
||||
|
||||
EXPECT_EQ(ss_->Scan(0, scan_callback), 0);
|
||||
|
||||
for (auto str : not_be_seen) {
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
for (auto str : not_be_seen) {
|
||||
EXPECT_TRUE(ss_->Erase(str));
|
||||
}
|
||||
|
||||
for (auto str : to_be_seen) {
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
// should reach at least the first item in the set
|
||||
uint32_t cursor = ss_->Scan(0, scan_callback);
|
||||
|
||||
for (auto str : maybe_seen) {
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
while (cursor != 0) {
|
||||
cursor = ss_->Scan(cursor, scan_callback);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(seen.size() == to_be_seen.size());
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, IntOnly) {
|
||||
constexpr size_t num_ints = 8192;
|
||||
unordered_set<unsigned int> numbers;
|
||||
for (size_t i = 0; i < num_ints; ++i) {
|
||||
numbers.insert(i);
|
||||
EXPECT_TRUE(ss_->Add(to_string(i)));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_ints; ++i) {
|
||||
ASSERT_FALSE(ss_->Add(to_string(i)));
|
||||
}
|
||||
|
||||
size_t num_remove = generator_() % 4096;
|
||||
unordered_set<string> removed;
|
||||
|
||||
for (size_t i = 0; i < num_remove; ++i) {
|
||||
auto remove_int = generator_() % num_ints;
|
||||
auto remove = to_string(remove_int);
|
||||
if (numbers.count(remove_int)) {
|
||||
ASSERT_TRUE(ss_->Contains(remove)) << remove_int;
|
||||
EXPECT_TRUE(ss_->Erase(remove));
|
||||
numbers.erase(remove_int);
|
||||
} else {
|
||||
EXPECT_FALSE(ss_->Erase(remove));
|
||||
}
|
||||
|
||||
EXPECT_FALSE(ss_->Contains(remove));
|
||||
removed.insert(remove);
|
||||
}
|
||||
|
||||
size_t expected_seen = 0;
|
||||
auto scan_callback = [&](std::string_view str_v) {
|
||||
std::string str(str_v);
|
||||
EXPECT_FALSE(removed.count(str));
|
||||
|
||||
if (numbers.count(std::atoi(str.data()))) {
|
||||
++expected_seen;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t cursor = 0;
|
||||
do {
|
||||
cursor = ss_->Scan(cursor, scan_callback);
|
||||
// randomly throw in some new numbers
|
||||
uint32_t val = generator_();
|
||||
ss_->Add(to_string(val));
|
||||
} while (cursor != 0);
|
||||
|
||||
EXPECT_GE(expected_seen + removed.size(), num_ints);
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, XtremeScanGrow) {
|
||||
unordered_set<string> to_see, force_grow, seen;
|
||||
|
||||
while (to_see.size() != 8) {
|
||||
to_see.insert(random_string(generator_, 10));
|
||||
}
|
||||
|
||||
while (force_grow.size() != 8192) {
|
||||
string str = random_string(generator_, 10);
|
||||
|
||||
if (to_see.count(str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
force_grow.insert(random_string(generator_, 10));
|
||||
}
|
||||
|
||||
for (auto& str : to_see) {
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
auto scan_callback = [&](string_view strv) {
|
||||
std::string str(strv);
|
||||
if (to_see.count(str)) {
|
||||
seen.insert(str);
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t cursor = ss_->Scan(0, scan_callback);
|
||||
|
||||
// force approx 10 grows
|
||||
for (auto& s : force_grow) {
|
||||
EXPECT_TRUE(ss_->Add(s));
|
||||
}
|
||||
|
||||
while (cursor != 0) {
|
||||
cursor = ss_->Scan(cursor, scan_callback);
|
||||
}
|
||||
|
||||
EXPECT_EQ(seen.size(), to_see.size());
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, Pop) {
|
||||
constexpr size_t num_items = 8;
|
||||
unordered_set<string> to_insert;
|
||||
|
||||
while (to_insert.size() != num_items) {
|
||||
auto str = random_string(generator_, 10);
|
||||
if (to_insert.count(str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_insert.insert(str);
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
while (!ss_->Empty()) {
|
||||
size_t size = ss_->UpperBoundSize();
|
||||
auto str = ss_->Pop();
|
||||
DCHECK(ss_->UpperBoundSize() == to_insert.size() - 1);
|
||||
DCHECK(str);
|
||||
DCHECK(to_insert.count(std::string(str.Key())));
|
||||
DCHECK_EQ(ss_->UpperBoundSize(), size - 1);
|
||||
to_insert.erase(std::string(str.Key()));
|
||||
}
|
||||
|
||||
DCHECK(ss_->Empty());
|
||||
DCHECK(to_insert.empty());
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, Iteration) {
|
||||
ss_->Add("foo");
|
||||
for (const auto& ptr : *ss_) {
|
||||
LOG(INFO) << ptr;
|
||||
}
|
||||
ss_->Clear();
|
||||
constexpr size_t num_items = 8192;
|
||||
unordered_set<string> to_insert;
|
||||
|
||||
while (to_insert.size() != num_items) {
|
||||
auto str = random_string(generator_, 10);
|
||||
if (to_insert.count(str)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_insert.insert(str);
|
||||
EXPECT_TRUE(ss_->Add(str));
|
||||
}
|
||||
|
||||
for (const auto& ptr : *ss_) {
|
||||
std::string str(ptr.Key());
|
||||
EXPECT_TRUE(to_insert.count(str));
|
||||
to_insert.erase(str);
|
||||
}
|
||||
|
||||
EXPECT_EQ(to_insert.size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, SetFieldExpireHasExpiry) {
|
||||
EXPECT_TRUE(ss_->Add("k1", 100));
|
||||
auto k = ss_->Find("k1");
|
||||
EXPECT_TRUE(k->HasExpiry());
|
||||
EXPECT_EQ(k->ExpiryTime(), 100);
|
||||
k.SetExpiryTime(1);
|
||||
EXPECT_TRUE(k->HasExpiry());
|
||||
EXPECT_EQ(k->ExpiryTime(), 1);
|
||||
}
|
||||
|
||||
TEST_F(IntrusiveStringSetTest, SetFieldExpireNoHasExpiry) {
|
||||
EXPECT_TRUE(ss_->Add("k1"));
|
||||
auto k = ss_->Find("k1");
|
||||
EXPECT_FALSE(k->HasExpiry());
|
||||
k.SetExpiryTime(10);
|
||||
EXPECT_TRUE(k->HasExpiry());
|
||||
EXPECT_EQ(k->ExpiryTime(), 10);
|
||||
}
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, Ttl) {
|
||||
// EXPECT_TRUE(ss_->Add("bla"sv, 1));
|
||||
// EXPECT_FALSE(ss_->Add("bla"sv, 1));
|
||||
// auto it = ss_->Find("bla"sv);
|
||||
// EXPECT_EQ(1u, it.ExpiryTime());
|
||||
|
||||
// ss_->set_time(1);
|
||||
// EXPECT_TRUE(ss_->Add("bla"sv, 1));
|
||||
// EXPECT_EQ(1u, ss_->UpperBoundSize());
|
||||
|
||||
// for (unsigned i = 0; i < 100; ++i) {
|
||||
// EXPECT_TRUE(ss_->Add(StrCat("foo", i), 1));
|
||||
// }
|
||||
// EXPECT_EQ(101u, ss_->UpperBoundSize());
|
||||
// it = ss_->Find("foo50");
|
||||
// EXPECT_STREQ("foo50", *it);
|
||||
// EXPECT_EQ(2u, it.ExpiryTime());
|
||||
|
||||
// ss_->set_time(2);
|
||||
// for (unsigned i = 0; i < 100; ++i) {
|
||||
// EXPECT_TRUE(ss_->Add(StrCat("bar", i)));
|
||||
// }
|
||||
// it = ss_->Find("bar50");
|
||||
// EXPECT_FALSE(it.HasExpiry());
|
||||
|
||||
// for (auto it = ss_->begin(); it != ss_->end(); ++it) {
|
||||
// ASSERT_TRUE(absl::StartsWith(*it, "bar")) << *it;
|
||||
// string str = *it;
|
||||
// VLOG(1) << *it;
|
||||
// }
|
||||
// }
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, Grow) {
|
||||
// for (size_t j = 0; j < 10; ++j) {
|
||||
// for (size_t i = 0; i < 4098; ++i) {
|
||||
// ss_->Reserve(generator_() % 256);
|
||||
// auto str = random_string(generator_, 3);
|
||||
// ss_->Add(str);
|
||||
// }
|
||||
// ss_->Clear();
|
||||
// }
|
||||
// }
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, Reserve) {
|
||||
// vector<string> strs;
|
||||
|
||||
// for (size_t i = 0; i < 10; ++i) {
|
||||
// strs.push_back(random_string(generator_, 10));
|
||||
// ss_->Add(strs.back());
|
||||
// }
|
||||
|
||||
// for (size_t j = 2; j < 20; j += 3) {
|
||||
// ss_->Reserve(j * 20);
|
||||
// for (size_t i = 0; i < 10; ++i) {
|
||||
// ASSERT_TRUE(ss_->Contains(strs[i]));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, Fill) {
|
||||
// for (size_t i = 0; i < 100; ++i) {
|
||||
// ss_->Add(random_string(generator_, 10));
|
||||
// }
|
||||
// StringSet s2;
|
||||
// ss_->Fill(&s2);
|
||||
// EXPECT_EQ(s2.UpperBoundSize(), ss_->UpperBoundSize());
|
||||
// for (sds str : *ss_) {
|
||||
// EXPECT_TRUE(s2.Contains(str));
|
||||
// }
|
||||
// }
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, IterateEmpty) {
|
||||
// for (const auto& s : *ss_) {
|
||||
// // We're iterating to make sure there is no crash. However, if we got here, it's a bug
|
||||
// CHECK(false) << "Found entry " << s << " in empty set";
|
||||
// }
|
||||
// }
|
||||
|
||||
// size_t memUsed(IntrusiveStringSet& obj) {
|
||||
// return obj.ObjMallocUsed() + obj.SetMallocUsed();
|
||||
// }
|
||||
|
||||
// void BM_Clone(benchmark::State& state) {
|
||||
// vector<string> strs;
|
||||
// mt19937 generator(0);
|
||||
// IntrusiveStringSet ss1, ss2;
|
||||
// unsigned elems = state.range(0);
|
||||
// for (size_t i = 0; i < elems; ++i) {
|
||||
// string str = random_string(generator, 10);
|
||||
// ss1.Add(str);
|
||||
// }
|
||||
// ss2.Reserve(ss1.UpperBoundSize());
|
||||
// while (state.KeepRunning()) {
|
||||
// for (auto src : ss1) {
|
||||
// ss2.Add(src);
|
||||
// }
|
||||
// state.PauseTiming();
|
||||
// ss2.Clear();
|
||||
// ss2.Reserve(ss1.UpperBoundSize());
|
||||
// state.ResumeTiming();
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Clone)->ArgName("elements")->Arg(32000);
|
||||
|
||||
// void BM_Fill(benchmark::State& state) {
|
||||
// unsigned elems = state.range(0);
|
||||
// vector<string> strs;
|
||||
// mt19937 generator(0);
|
||||
// IntrusiveStringSet ss1, ss2;
|
||||
// for (size_t i = 0; i < elems; ++i) {
|
||||
// string str = random_string(generator, 10);
|
||||
// ss1.Add(str);
|
||||
// }
|
||||
|
||||
// while (state.KeepRunning()) {
|
||||
// ss1.Fill(&ss2);
|
||||
// state.PauseTiming();
|
||||
// ss2.Clear();
|
||||
// state.ResumeTiming();
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Fill)->ArgName("elements")->Arg(32000);
|
||||
|
||||
// void BM_Clear(benchmark::State& state) {
|
||||
// unsigned elems = state.range(0);
|
||||
// mt19937 generator(0);
|
||||
// StringSet ss;
|
||||
// while (state.KeepRunning()) {
|
||||
// state.PauseTiming();
|
||||
// for (size_t i = 0; i < elems; ++i) {
|
||||
// string str = random_string(generator, 16);
|
||||
// ss.Add(str);
|
||||
// }
|
||||
// state.ResumeTiming();
|
||||
// ss.Clear();
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Clear)->ArgName("elements")->Arg(32000);
|
||||
|
||||
void BM_Add(benchmark::State& state) {
|
||||
vector<string> strs;
|
||||
mt19937 generator(0);
|
||||
IntrusiveStringSet ss;
|
||||
unsigned elems = state.range(0);
|
||||
unsigned keySize = state.range(1);
|
||||
for (size_t i = 0; i < elems; ++i) {
|
||||
string str = random_string(generator, keySize);
|
||||
strs.push_back(str);
|
||||
}
|
||||
ss.Reserve(elems);
|
||||
while (state.KeepRunning()) {
|
||||
for (auto& str : strs)
|
||||
ss.Add(str);
|
||||
state.PauseTiming();
|
||||
// state.counters["Memory_Used"] = memUsed(ss);
|
||||
ss.Clear();
|
||||
ss.Reserve(elems);
|
||||
state.ResumeTiming();
|
||||
}
|
||||
}
|
||||
BENCHMARK(BM_Add)
|
||||
->ArgNames({"elements", "Key Size"})
|
||||
->ArgsProduct({{1000, 10000, 100000}, {10, 100, 1000}});
|
||||
|
||||
// void BM_AddMany(benchmark::State& state) {
|
||||
// vector<string> strs;
|
||||
// mt19937 generator(0);
|
||||
// StringSet ss;
|
||||
// unsigned elems = state.range(0);
|
||||
// unsigned keySize = state.range(1);
|
||||
// for (size_t i = 0; i < elems; ++i) {
|
||||
// string str = random_string(generator, keySize);
|
||||
// strs.push_back(str);
|
||||
// }
|
||||
// ss.Reserve(elems);
|
||||
// vector<string_view> svs;
|
||||
// for (const auto& str : strs) {
|
||||
// svs.push_back(str);
|
||||
// }
|
||||
// while (state.KeepRunning()) {
|
||||
// ss.AddMany(absl::MakeSpan(svs), UINT32_MAX, false);
|
||||
// state.PauseTiming();
|
||||
// CHECK_EQ(ss.UpperBoundSize(), elems);
|
||||
// state.counters["Memory_Used"] = memUsed(ss);
|
||||
// ss.Clear();
|
||||
// ss.Reserve(elems);
|
||||
// state.ResumeTiming();
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_AddMany)
|
||||
// ->ArgNames({"elements", "Key Size"})
|
||||
// ->ArgsProduct({{1000, 10000, 100000}, {10, 100, 1000}});
|
||||
|
||||
// void BM_Erase(benchmark::State& state) {
|
||||
// std::vector<std::string> strs;
|
||||
// mt19937 generator(0);
|
||||
// StringSet ss;
|
||||
// auto elems = state.range(0);
|
||||
// auto keySize = state.range(1);
|
||||
// for (long int i = 0; i < elems; ++i) {
|
||||
// std::string str = random_string(generator, keySize);
|
||||
// strs.push_back(str);
|
||||
// ss.Add(str);
|
||||
// }
|
||||
// state.counters["Memory_Before_Erase"] = memUsed(ss);
|
||||
// while (state.KeepRunning()) {
|
||||
// for (auto& str : strs) {
|
||||
// ss.Erase(str);
|
||||
// }
|
||||
// state.PauseTiming();
|
||||
// state.counters["Memory_After_Erase"] = memUsed(ss);
|
||||
// for (auto& str : strs) {
|
||||
// ss.Add(str);
|
||||
// }
|
||||
// state.ResumeTiming();
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Erase)
|
||||
// ->ArgNames({"elements", "Key Size"})
|
||||
// ->ArgsProduct({{1000, 10000, 100000}, {10, 100, 1000}});
|
||||
|
||||
// void BM_Get(benchmark::State& state) {
|
||||
// std::vector<std::string> strs;
|
||||
// mt19937 generator(0);
|
||||
// StringSet ss;
|
||||
// auto elems = state.range(0);
|
||||
// auto keySize = state.range(1);
|
||||
// for (long int i = 0; i < elems; ++i) {
|
||||
// std::string str = random_string(generator, keySize);
|
||||
// strs.push_back(str);
|
||||
// ss.Add(str);
|
||||
// }
|
||||
// while (state.KeepRunning()) {
|
||||
// for (auto& str : strs) {
|
||||
// ss.Find(str);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Get)
|
||||
// ->ArgNames({"elements", "Key Size"})
|
||||
// ->ArgsProduct({{1000, 10000, 100000}, {10, 100, 1000}});
|
||||
|
||||
// void BM_Grow(benchmark::State& state) {
|
||||
// vector<string> strs;
|
||||
// mt19937 generator(0);
|
||||
// StringSet src;
|
||||
// unsigned elems = 1 << 18;
|
||||
// for (size_t i = 0; i < elems; ++i) {
|
||||
// src.Add(random_string(generator, 16), UINT32_MAX);
|
||||
// strs.push_back(random_string(generator, 16));
|
||||
// }
|
||||
|
||||
// while (state.KeepRunning()) {
|
||||
// state.PauseTiming();
|
||||
// StringSet tmp;
|
||||
// src.Fill(&tmp);
|
||||
// CHECK_EQ(tmp.BucketCount(), elems);
|
||||
// state.ResumeTiming();
|
||||
// for (const auto& str : strs) {
|
||||
// tmp.Add(str);
|
||||
// if (tmp.BucketCount() > elems) {
|
||||
// break; // we grew
|
||||
// }
|
||||
// }
|
||||
|
||||
// CHECK_GT(tmp.BucketCount(), elems);
|
||||
// }
|
||||
// }
|
||||
// BENCHMARK(BM_Grow);
|
||||
|
||||
// unsigned total_wasted_memory = 0;
|
||||
|
||||
// TEST_F(IntrusiveStringSetTest, ReallocIfNeeded) {
|
||||
// auto build_str = [](size_t i) { return to_string(i) + string(131, 'a'); };
|
||||
|
||||
// auto count_waste = [](const mi_heap_t* heap, const mi_heap_area_t* area, void* block,
|
||||
// size_t block_size, void* arg) {
|
||||
// size_t used = block_size * area->used;
|
||||
// total_wasted_memory += area->committed - used;
|
||||
// return true;
|
||||
// };
|
||||
|
||||
// for (size_t i = 0; i < 10'000; i++)
|
||||
// ss_->Add(build_str(i));
|
||||
|
||||
// for (size_t i = 0; i < 10'000; i++) {
|
||||
// if (i % 10 == 0)
|
||||
// continue;
|
||||
// ss_->Erase(build_str(i));
|
||||
// }
|
||||
|
||||
// mi_heap_collect(mi_heap_get_backing(), true);
|
||||
// mi_heap_visit_blocks(mi_heap_get_backing(), false, count_waste, nullptr);
|
||||
// size_t wasted_before = total_wasted_memory;
|
||||
|
||||
// size_t underutilized = 0;
|
||||
// for (auto it = ss_->begin(); it != ss_->end(); ++it) {
|
||||
// underutilized += zmalloc_page_is_underutilized(*it, 0.9);
|
||||
// it.ReallocIfNeeded(0.9);
|
||||
// }
|
||||
// // Check there are underutilized pages
|
||||
// CHECK_GT(underutilized, 0u);
|
||||
|
||||
// total_wasted_memory = 0;
|
||||
// mi_heap_collect(mi_heap_get_backing(), true);
|
||||
// mi_heap_visit_blocks(mi_heap_get_backing(), false, count_waste, nullptr);
|
||||
// size_t wasted_after = total_wasted_memory;
|
||||
|
||||
// // Check we waste significanlty less now
|
||||
// EXPECT_GT(wasted_before, wasted_after * 2);
|
||||
|
||||
// EXPECT_EQ(ss_->UpperBoundSize(), 1000);
|
||||
// for (size_t i = 0; i < 1000; i++)
|
||||
// EXPECT_EQ(*ss_->Find(build_str(i * 10)), build_str(i * 10));
|
||||
// }
|
||||
|
||||
} // namespace dfly
|
Loading…
Add table
Add a link
Reference in a new issue