From 0587129fc6bfcdc7f2b4e288b6be68234bcb0f3e Mon Sep 17 00:00:00 2001 From: Richard Thier Date: Tue, 3 Sep 2024 16:00:58 +0200 Subject: [PATCH] more vector compatibility --- TurboList.hpp | 87 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/TurboList.hpp b/TurboList.hpp index fdb93ca..794cfcd 100644 --- a/TurboList.hpp +++ b/TurboList.hpp @@ -8,6 +8,9 @@ #ifndef TL_NOINLINE #define TL_NOINLINE __attribute__((noinline)) #endif /* TL_NOINLINE */ +#ifndef TL_INLINE +#define TL_INLINE __attribute__((always_inline)) +#endif /* TL_INLINE */ #ifndef TL_LIKELY #define TL_LIKELY(x) __builtin_expect(!!(x), 1) @@ -16,6 +19,10 @@ #define TL_UNLIKELY(x) __builtin_expect(!!(x), 0) #endif /* TL_UNLIKELY */ +#ifndef TL_GROWTH_RATE +#define TL_GROWTH_RATE 2 +#endif /* TL_GROWTH_RATE */ + typedef void*(MallocLike)(size_t); typedef void(FreeLike)(void*); @@ -29,20 +36,32 @@ class TurboList { uint32_t capacity; - TL_NOINLINE T& grow_and_insert(T elem) noexcept { + TL_NOINLINE void grow_and_insert(T elem) noexcept { // assert(mid == 0); if(old) FREE(old); old = nex; mid = end; - capacity *= 2; + capacity *= TL_GROWTH_RATE; nex = (T *) MALLOC(this->capacity * sizeof(T)); // Will go into the INSERT code path here - return insert(elem); + insert(elem); } + template + TL_NOINLINE T& grow_and_emplace(Args&&... args) noexcept { + // assert(mid == 0); + if(old) FREE(old); + old = nex; + mid = end; + capacity *= TL_GROWTH_RATE; + nex = (T *) MALLOC(this->capacity * sizeof(T)); + + // Will go into the INSERT code path here + return emplace_back(std::forward(args)...); + } public: - inline TurboList(uint32_t initial_size = 0, uint32_t initial_cap = 16) noexcept : + TL_INLINE TurboList(uint32_t initial_size = 0, uint32_t initial_cap = 16) noexcept : old(nullptr), mid(0), end(initial_size), @@ -51,20 +70,29 @@ public: nex = (T *) MALLOC(this->capacity * sizeof(T)); } - inline ~TurboList() noexcept { + TL_INLINE ~TurboList() noexcept { if(nex) FREE(nex); if(old) FREE(old); } - inline T& operator[](uint32_t i) noexcept { + TL_INLINE T& operator[](uint32_t i) const noexcept { // This seem to be more often compiled to cmov // branchless conditional codes this way.. + // T *base = (i < mid) ? old : nex; return base[i]; + // + // if(i < mid) return old[i]; + // else return nex[i]; + // + // T* loc = (T*) ((i < mid) * (size_t)old + + // (i>=mid) * (size_t)nex + + // i* sizeof(T)); + // return *loc; } /** This is much faster than operator[] if you do small amounts of work per access */ - inline void iterate(void(callback)(T&)) noexcept { + TL_INLINE void iterate(void(callback)(T&)) noexcept { // old for(uint32_t i = 0; i < mid; ++i) { callback(old[i]); @@ -75,35 +103,54 @@ public: } } - inline T& insert(T elem) noexcept { + /** Vector compatibility: Use insert() if you want the inserted thing as reference too */ + TL_INLINE void push_back(T elem) noexcept { + this->insert(elem); + } + + /** Vector compatibility: Use pop() if you want the popped thing out as copy too */ + TL_INLINE void pop_back() noexcept { + if(end > 0) { + --end; + if(end < mid) { // end > 0 here! + end = mid; + mid = 0; + FREE(nex); + nex = old; + old = nullptr; + } + } + } + + TL_INLINE void insert(T elem) noexcept { if(TL_LIKELY(end < capacity)) { // INSERT - /* Same as this: + /* Same as this - but in this case it measures as faster: if(mid > 0) { - nex[mid - 1] = old[mid - 1]; --mid; + nex[mid] = old[mid]; } */ bool hasmid = (mid > 0); mid -= hasmid; nex[mid] = hasmid ? old[mid] : nex[mid]; - return (nex[end++] = elem); + nex[end++] = elem; } else { // GROW - return grow_and_insert(elem); + grow_and_insert(elem); } } template - inline T& emplace(Args&&... args) { + TL_INLINE T& emplace_back(Args&&... args) { if(TL_LIKELY(end < capacity)) { // INSERT - /* Same as this: + /* Same as this - but in this case it measures as faster: if(mid > 0) { nex[mid - 1] = old[mid - 1]; --mid; @@ -117,21 +164,13 @@ public: return *new (nex + end++) T(std::forward(args)...); } else { // GROW - // - // Rem.: I just chose this to be less optimized than - // it is possible by making a copy and reusing - // the existing grow and insert code instead of - // writing a new "grow_and_emplace" again. - // - // This happens rarely so its probably fine and - // makes less template instantiations, smaller binary. - return grow_and_insert(T(std::forward(args)...)); + return grow_and_emplace(std::forward(args)...); } } // TODO: finalize() call which memcpy remaining elements of old into nex and then frees old and sets nullptr + mid = 0; - inline uint32_t size() noexcept { + TL_INLINE uint32_t size() noexcept { return end; } };