more vector compatibility

This commit is contained in:
Richard Thier 2024-09-03 16:00:58 +02:00
parent 04964cd2a0
commit 0587129fc6

View File

@ -8,6 +8,9 @@
#ifndef TL_NOINLINE #ifndef TL_NOINLINE
#define TL_NOINLINE __attribute__((noinline)) #define TL_NOINLINE __attribute__((noinline))
#endif /* TL_NOINLINE */ #endif /* TL_NOINLINE */
#ifndef TL_INLINE
#define TL_INLINE __attribute__((always_inline))
#endif /* TL_INLINE */
#ifndef TL_LIKELY #ifndef TL_LIKELY
#define TL_LIKELY(x) __builtin_expect(!!(x), 1) #define TL_LIKELY(x) __builtin_expect(!!(x), 1)
@ -16,6 +19,10 @@
#define TL_UNLIKELY(x) __builtin_expect(!!(x), 0) #define TL_UNLIKELY(x) __builtin_expect(!!(x), 0)
#endif /* TL_UNLIKELY */ #endif /* TL_UNLIKELY */
#ifndef TL_GROWTH_RATE
#define TL_GROWTH_RATE 2
#endif /* TL_GROWTH_RATE */
typedef void*(MallocLike)(size_t); typedef void*(MallocLike)(size_t);
typedef void(FreeLike)(void*); typedef void(FreeLike)(void*);
@ -29,20 +36,32 @@ class TurboList {
uint32_t capacity; uint32_t capacity;
TL_NOINLINE T& grow_and_insert(T elem) noexcept { TL_NOINLINE void grow_and_insert(T elem) noexcept {
// assert(mid == 0); // assert(mid == 0);
if(old) FREE(old); if(old) FREE(old);
old = nex; old = nex;
mid = end; mid = end;
capacity *= 2; capacity *= TL_GROWTH_RATE;
nex = (T *) MALLOC(this->capacity * sizeof(T)); nex = (T *) MALLOC(this->capacity * sizeof(T));
// Will go into the INSERT code path here // Will go into the INSERT code path here
return insert(elem); insert(elem);
} }
template<typename... Args>
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>(args)...);
}
public: 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), old(nullptr),
mid(0), mid(0),
end(initial_size), end(initial_size),
@ -51,20 +70,29 @@ public:
nex = (T *) MALLOC(this->capacity * sizeof(T)); nex = (T *) MALLOC(this->capacity * sizeof(T));
} }
inline ~TurboList() noexcept { TL_INLINE ~TurboList() noexcept {
if(nex) FREE(nex); if(nex) FREE(nex);
if(old) FREE(old); 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 // This seem to be more often compiled to cmov
// branchless conditional codes this way.. // branchless conditional codes this way..
//
T *base = (i < mid) ? old : nex; T *base = (i < mid) ? old : nex;
return base[i]; 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 */ /** 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 // old
for(uint32_t i = 0; i < mid; ++i) { for(uint32_t i = 0; i < mid; ++i) {
callback(old[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)) { if(TL_LIKELY(end < capacity)) {
// INSERT // INSERT
/* Same as this: /* Same as this - but in this case it measures as faster:
if(mid > 0) { if(mid > 0) {
nex[mid - 1] = old[mid - 1];
--mid; --mid;
nex[mid] = old[mid];
} }
*/ */
bool hasmid = (mid > 0); bool hasmid = (mid > 0);
mid -= hasmid; mid -= hasmid;
nex[mid] = hasmid ? old[mid] : nex[mid]; nex[mid] = hasmid ? old[mid] : nex[mid];
return (nex[end++] = elem); nex[end++] = elem;
} else { } else {
// GROW // GROW
return grow_and_insert(elem); grow_and_insert(elem);
} }
} }
template<typename... Args> template<typename... Args>
inline T& emplace(Args&&... args) { TL_INLINE T& emplace_back(Args&&... args) {
if(TL_LIKELY(end < capacity)) { if(TL_LIKELY(end < capacity)) {
// INSERT // INSERT
/* Same as this: /* Same as this - but in this case it measures as faster:
if(mid > 0) { if(mid > 0) {
nex[mid - 1] = old[mid - 1]; nex[mid - 1] = old[mid - 1];
--mid; --mid;
@ -117,21 +164,13 @@ public:
return *new (nex + end++) T(std::forward<Args>(args)...); return *new (nex + end++) T(std::forward<Args>(args)...);
} else { } else {
// GROW // GROW
// return grow_and_emplace(std::forward<Args>(args)...);
// 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>(args)...));
} }
} }
// TODO: finalize() call which memcpy remaining elements of old into nex and then frees old and sets nullptr + mid = 0; // 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; return end;
} }
}; };