Skip to content

Use intrusive list in VisitedListPool #5

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

Open
wants to merge 1 commit into
base: SISAP
Choose a base branch
from
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