From f448bfd3f3348c6b5823714fa1b4c07406fd30f5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 12 Sep 2023 20:13:00 +0200 Subject: [PATCH] util/IntrusiveForwardList: add option `cache_last` --- src/util/IntrusiveForwardList.hxx | 71 ++++++++++++++++++++- test/util/TestIntrusiveForwardList.cxx | 88 +++++++++++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/util/IntrusiveForwardList.hxx b/src/util/IntrusiveForwardList.hxx index 2b792fee6..770608dd8 100644 --- a/src/util/IntrusiveForwardList.hxx +++ b/src/util/IntrusiveForwardList.hxx @@ -8,6 +8,7 @@ #include "Concepts.hxx" #include "MemberPointer.hxx" #include "OptionalCounter.hxx" +#include "OptionalField.hxx" #include "ShallowCopy.hxx" #include @@ -16,6 +17,12 @@ struct IntrusiveForwardListOptions { bool constant_time_size = false; + + /** + * Cache a pointer to the last item? This makes back() and + * push_back() run in constant time. + */ + bool cache_last = false; }; struct IntrusiveForwardListNode { @@ -103,6 +110,9 @@ class IntrusiveForwardList { IntrusiveForwardListNode head{nullptr}; + [[no_unique_address]] + OptionalField last_cache{&head}; + [[no_unique_address]] OptionalCounter counter; @@ -144,6 +154,7 @@ public: :head{std::exchange(src.head.next, nullptr)} { using std::swap; + swap(last_cache, src.last_cache); swap(counter, src.counter); } @@ -152,11 +163,13 @@ public: { // shallow copies mess with the counter static_assert(!options.constant_time_size); + static_assert(!options.cache_last); } IntrusiveForwardList &operator=(IntrusiveForwardList &&src) noexcept { using std::swap; swap(head, src.head); + swap(last_cache, src.last_cache); swap(counter, src.counter); return *this; } @@ -174,6 +187,7 @@ public: void clear() noexcept { head = {}; + last_cache = {}; counter.reset(); } @@ -183,6 +197,8 @@ public: pop_front(); disposer(item); } + + last_cache = {}; } const_reference front() const noexcept { @@ -196,6 +212,11 @@ public: reference pop_front() noexcept { auto &i = front(); head.next = head.next->next; + + if constexpr (options.cache_last) + if (head.next == nullptr) + last_cache.value = &head; + --counter; return i; } @@ -205,6 +226,16 @@ public: disposer(&i); } + const_reference back() const noexcept + requires(options.cache_last) { + return *Cast(last_cache.value); + } + + reference back() noexcept + requires(options.cache_last) { + return *Cast(last_cache.value); + } + class const_iterator; class iterator final { @@ -265,6 +296,11 @@ public: return {nullptr}; } + constexpr iterator last() noexcept + requires(options.cache_last) { + return {last_cache.value}; + } + class const_iterator final { friend IntrusiveForwardList; @@ -317,15 +353,35 @@ public: return {head.next}; } + constexpr const_iterator last() const noexcept + requires(options.cache_last) { + return {last_cache.value}; + } + void push_front(reference t) noexcept { auto &new_node = ToNode(t); + + if constexpr (options.cache_last) + if (empty()) + last_cache.value = &new_node; + new_node.next = head.next; head.next = &new_node; ++counter; } + void push_back(reference t) noexcept + requires(options.cache_last) { + auto &new_node = ToNode(t); + new_node.next = nullptr; + last_cache.value->next = &new_node; + last_cache.value = &new_node; + + ++counter; + } + static iterator insert_after(iterator pos, reference t) noexcept - requires(!constant_time_size) { + requires(!constant_time_size && !options.cache_last) { /* if we have no counter, then this method is allowed to be static */ @@ -336,9 +392,15 @@ public: return &new_node; } - iterator insert_after(iterator pos, reference t) noexcept { + iterator insert_after(iterator pos, reference t) noexcept + requires(constant_time_size || options.cache_last) { auto &pos_node = *pos.cursor; auto &new_node = ToNode(t); + + if constexpr (options.cache_last) + if (pos_node.next == nullptr) + last_cache.value = &new_node; + new_node.next = pos_node.next; pos_node.next = &new_node; ++counter; @@ -347,6 +409,11 @@ public: void erase_after(iterator pos) noexcept { pos.cursor->next = pos.cursor->next->next; + + if constexpr (options.cache_last) + if (pos.cursor->next == nullptr) + last_cache.value = pos.cursor; + --counter; } diff --git a/test/util/TestIntrusiveForwardList.cxx b/test/util/TestIntrusiveForwardList.cxx index 5d1bdbe6e..361dec95f 100644 --- a/test/util/TestIntrusiveForwardList.cxx +++ b/test/util/TestIntrusiveForwardList.cxx @@ -16,7 +16,7 @@ struct CharItem final : IntrusiveForwardListHook { }; static std::string -ToString(const IntrusiveForwardList &list) noexcept +ToString(const auto &list) noexcept { std::string result; for (const auto &i : list) @@ -50,3 +50,89 @@ TEST(IntrusiveForwardList, Basic) list.reverse(); ASSERT_EQ(ToString(list), "cb"); } + +TEST(IntrusiveForwardList, CacheLast) +{ + using Item = CharItem; + + Item items[]{'a', 'b', 'c'}; + + using List =IntrusiveForwardList< + CharItem, IntrusiveForwardListBaseHookTraits, + IntrusiveForwardListOptions{.cache_last = true}>; + List list; + ASSERT_EQ(ToString(list), ""); + list.reverse(); + ASSERT_EQ(ToString(list), ""); + + for (auto &i : items) + list.push_front(i); + + ASSERT_EQ(ToString(list), "cba"); + ASSERT_EQ(&list.back(), &items[0]); + + list.erase_after(list.begin()); + ASSERT_EQ(ToString(list), "ca"); + ASSERT_EQ(&list.back(), &items[0]); + + list.reverse(); + ASSERT_EQ(ToString(list), "ac"); + ASSERT_EQ(&list.back(), &items[2]); + + list.erase_after(list.begin()); + ASSERT_EQ(ToString(list), "a"); + ASSERT_EQ(&list.back(), &items[0]); + + list.reverse(); + ASSERT_EQ(ToString(list), "a"); + ASSERT_EQ(&list.back(), &items[0]); + + list.pop_front(); + ASSERT_EQ(ToString(list), ""); + + list.insert_after(list.before_begin(), items[0]); + ASSERT_EQ(ToString(list), "a"); + ASSERT_EQ(&list.back(), &items[0]); + + list.insert_after(list.before_begin(), items[1]); + ASSERT_EQ(ToString(list), "ba"); + ASSERT_EQ(&list.back(), &items[0]); + + list.pop_front(); + ASSERT_EQ(ToString(list), "a"); + ASSERT_EQ(&list.back(), &items[0]); + + list.pop_front(); + ASSERT_EQ(ToString(list), ""); + + for (auto &i : items) + list.push_back(i); + + ASSERT_EQ(ToString(list), "abc"); + ASSERT_EQ(&list.back(), &items[2]); + + /* move constructor */ + auto list2 = std::move(list); + ASSERT_EQ(ToString(list2), "abc"); + ASSERT_EQ(&list2.back(), &items[2]); + ASSERT_EQ(ToString(list), ""); + + /* move operator */ + list = std::move(list2); + ASSERT_EQ(ToString(list), "abc"); + ASSERT_EQ(&list.back(), &items[2]); + ASSERT_EQ(ToString(list2), ""); + + list.erase_after(list.begin()); + ASSERT_EQ(ToString(list), "ac"); + ASSERT_EQ(&list.back(), &items[2]); + + list.erase_after(list.before_begin()); + ASSERT_EQ(ToString(list), "c"); + ASSERT_EQ(&list.back(), &items[2]); + + // insert_after() + list.insert_after(list.begin(), items[0]); + ASSERT_EQ(ToString(list), "ca"); + ASSERT_EQ(&list.back(), &items[0]); +}