Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 73 additions & 0 deletions cpp/deglib/include/intrusive_list.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#pragma once

#include <utility>

namespace deglib::graph {

/*
Usage:

```
struct Entry {
Entry* next_; // list hooks
Entry* prev_; // list hooks
// ... other members
};

using List = IntrusiveList<Entry, &Entry::next_, &Entry::prev_>;
```

Make sure entries added to the list have a stable address.


Implementation adapted from
https://github.com/facebookexperimental/libunifex/blob/main/include/unifex/detail/intrusive_list.hpp
*/
template <class T, T* T::*Next, T* T::*Prev>
class IntrusiveList
{
private:
T* head_{};
T* tail_{};

public:
IntrusiveList() = default;

IntrusiveList(const IntrusiveList&) = delete;

IntrusiveList(IntrusiveList&& other) noexcept
: head_(std::exchange(other.head_, nullptr)), tail_(std::exchange(other.tail_, nullptr))
{
}

~IntrusiveList() = default;

IntrusiveList& operator=(const IntrusiveList&) = delete;
IntrusiveList& operator=(IntrusiveList&&) = delete;

[[nodiscard]] bool empty() const noexcept { return head_ == nullptr; }

void push_back(T* item) noexcept
{
item->*Prev = tail_;
item->*Next = nullptr;
if (tail_ == nullptr)
head_ = item;
else
tail_->*Next = item;
tail_ = item;
}

[[nodiscard]] T* pop_front() noexcept
{
T* item = head_;
head_ = item->*Next;
if (head_ != nullptr)
head_->*Prev = nullptr;
else
tail_ = nullptr;
return item;
}
};

} // namespace deglib::graph
64 changes: 37 additions & 27 deletions cpp/deglib/include/visited_list_pool.h
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
#pragma once

#include "intrusive_list.h"

#include <algorithm>
#include <mutex>
#include <vector>
#include <deque>
#include <memory>
#include <cstdint>

/**
* Ref https://raw.githubusercontent.com/nmslib/hnswlib/master/hnswlib/visited_list_pool.h
*/
namespace deglib::graph {

class VisitedListPool;

class VisitedList {
private:
uint16_t current_tag_{1};
friend VisitedListPool;

VisitedList* next_;
VisitedList* prev_;
std::unique_ptr<uint16_t[]> slots_;
unsigned int num_elements_;
uint32_t num_elements_;
uint16_t current_tag_{1};

public:
explicit VisitedList(int numelements1) : slots_(std::make_unique<uint16_t[]>(numelements1)), num_elements_(numelements1) {}
explicit VisitedList(uint32_t numelements1) : slots_(std::make_unique<uint16_t[]>(numelements1)), num_elements_(numelements1) {}

[[nodiscard]] auto* get_visited() const {
return slots_.get();
Expand All @@ -31,34 +38,38 @@ class VisitedList {
void reset() {
++current_tag_;
if (current_tag_ == 0) {
std::fill_n(slots_.get(), num_elements_, 0);
std::fill_n(slots_.get(), num_elements_, uint16_t{});
++current_tag_;
}
}
};

class VisitedListPool {
private:
using ListPtr = std::unique_ptr<VisitedList>;

std::deque<ListPtr> pool_;
IntrusiveList<VisitedList, &VisitedList::next_, &VisitedList::prev_> pool_;
std::mutex pool_guard_;
int num_elements_;
uint32_t num_elements_;

public:
VisitedListPool(int initmaxpools, int numelements) : num_elements_(numelements) {
for (int i = 0; i < initmaxpools; i++)
pool_.push_front(std::make_unique<VisitedList>(numelements));
VisitedListPool(uint32_t initmaxpools, uint32_t numelements) : num_elements_(numelements) {
for (uint32_t i = 0; i < initmaxpools; i++)
pool_.push_back(new VisitedList(numelements));
}

~VisitedListPool() noexcept {
while (!pool_.empty()) {
delete pool_.pop_front();
}
}

class FreeVisitedList {
private:
friend VisitedListPool;

VisitedListPool& pool_;
ListPtr list_;
VisitedList& list_;

FreeVisitedList(VisitedListPool& pool, ListPtr list) : pool_(pool), list_(std::move(list)) {}
FreeVisitedList(VisitedListPool& pool, VisitedList& list) : pool_(pool), list_(list) {}

public:
FreeVisitedList(const FreeVisitedList& other) = delete;
Expand All @@ -67,36 +78,35 @@ class VisitedListPool {
FreeVisitedList& operator=(FreeVisitedList&& other) = delete;

~FreeVisitedList() noexcept {
pool_.releaseVisitedList(std::move(list_));
pool_.releaseVisitedList(list_);
}

auto operator->() const {
return list_.get();
return &list_;
}
};

FreeVisitedList getFreeVisitedList() {
ListPtr rez = popVisitedList();
[[nodiscard]] FreeVisitedList getFreeVisitedList() {
auto rez = popVisitedList();
if (rez) {
rez->reset();
} else {
rez = std::make_unique<VisitedList>(num_elements_);
rez = new VisitedList(num_elements_);
}
return {*this, std::move(rez)};
return {*this, *rez};
}

private:
void releaseVisitedList(ListPtr vl) {
void releaseVisitedList(VisitedList& vl) {
std::unique_lock <std::mutex> lock(pool_guard_);
pool_.push_back(std::move(vl));
pool_.push_back(&vl);
}

ListPtr popVisitedList() {
ListPtr rez;
VisitedList* popVisitedList() {
VisitedList* rez{};
std::unique_lock <std::mutex> lock(pool_guard_);
if (!pool_.empty()) {
rez = std::move(pool_.front());
pool_.pop_front();
rez = pool_.pop_front();
}
return rez;
}
Expand Down