Date: Sun, 5 Oct 2025 20:38:38 +0000
On Sun, Oct 5, 2025 at 5:06 PM Walt Karas <wkaras_at_[hidden]> wrote:
>
> Response: In your approach, a count for one counter is a count for all counters: https://gcc.godbolt.org/z/czovra5cd
Again this code hasn't been ruminated over again and again, but
something along the lines of this:
https://gcc.godbolt.org/z/6KK8oafcd
And here it is copy-pasted:
#include <cassert>
#include <array>
#include <atomic>
#include <optional>
#include <set>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include <mutex>
#include <boost/lockfree/queue.hpp>
template<typename T, unsigned max_instances, unsigned max_threads>
class ThreadLocal final {
std::array<std::optional<T>, max_threads> storage{};
std::atomic<unsigned> nextIndex{0u};
boost::lockfree::queue<unsigned> freeList{max_threads};
unsigned const instance_id;
static unsigned next_instance_id(void) noexcept(false)
{
static std::atomic<unsigned> counter{0u};
unsigned id = counter.fetch_add(1, std::memory_order_relaxed);
if ( id >= max_instances ) throw std::runtime_error("Too many
ThreadLocal instances");
return id;
}
unsigned acquireSlot(void) noexcept(false)
{
unsigned index;
if (freeList.pop(index)) return index;
index = nextIndex.fetch_add(1u, std::memory_order_relaxed);
if ( index >= max_threads ) throw std::runtime_error("Exceeded
max_threads limit!");
return index;
}
void releaseSlot(unsigned index) noexcept
{
storage[index].reset();
freeList.push(index);
}
template<typename... Params>
unsigned registerThread(Params&&... args) noexcept(false)
{
static thread_local std::array<std::optional<unsigned>, max_instances>
slot_array{};
std::optional<unsigned> &entry = slot_array[instance_id];
if ( false == entry.has_value() )
{
unsigned index = acquireSlot();
entry = index;
// Ensure proper release when thread dies
struct Reclaimer {
ThreadLocal* owner;
unsigned index;
~Reclaimer() noexcept { owner->releaseSlot(index); }
};
static thread_local std::array<std::optional<Reclaimer>,
max_instances> reclaimers;
reclaimers[instance_id].emplace(Reclaimer{this, index});
}
unsigned const slot = *entry;
if ( false == storage[slot].has_value() ) storage[slot].emplace(
std::forward<Params>(args)... );
return slot;
}
public:
ThreadLocal(void) : instance_id( next_instance_id() ) {}
template<typename... Params>
T& operator()(Params&&... args) noexcept(false)
{
unsigned slot = registerThread( std::forward<Params>(args)... );
return *storage[slot];
}
template<typename... Params>
T const& operator()(Params&&... args) const noexcept(false)
{
return const_cast<ThreadLocal*>(this)->operator()(
std::forward<Params>(args)... );
}
};
// ===================
// Example Usage
// ===================
class Counter {
std::atomic<unsigned> total{0u};
std::atomic<unsigned> num_threads{0u};
std::mutex mtx;
struct PerThread {
std::atomic<unsigned> count{0u};
Counter &parent;
explicit PerThread(Counter *const c) noexcept(false) : parent(*c)
{
std::lock_guard lock{parent.mtx};
parent.pt_active.insert(this);
parent.num_threads.fetch_add(1u, std::memory_order_relaxed);
}
~PerThread(void) noexcept // we want std::terminate called
{
{
std::lock_guard lock{parent.mtx};
parent.pt_active.erase(this);
}
if ( unsigned const leftover =
count.load(std::memory_order_relaxed) )
parent.total.fetch_add(leftover, std::memory_order_relaxed);
parent.num_threads.fetch_sub(1u, std::memory_order_relaxed);
}
};
std::set<PerThread*> pt_active;
ThreadLocal<PerThread, 64u, 32u> pt;
public:
void incr(void) noexcept
{
pt(this).count.fetch_add(1u, std::memory_order_relaxed);
}
unsigned collect(void) noexcept(false)
{
unsigned sum = 0u;
{
std::lock_guard lock{mtx};
for ( PerThread *const p : pt_active )
sum += p->count.exchange(0u, std::memory_order_relaxed);
}
if ( sum ) total.fetch_add(sum, std::memory_order_relaxed);
return total.load(std::memory_order_relaxed);
}
};
#include <iostream>
auto main(void) -> int
{
Counter counter, counter2;
constexpr unsigned numThreads = 18u;
constexpr unsigned iterations = 200u;
const auto work = [&](unsigned)
{
for ( unsigned i = 0; i < iterations; ++i )
{
counter.incr();
counter2.incr();
}
};
std::vector<std::thread> threads;
threads.reserve(numThreads);
for ( unsigned i = 0; i < numThreads; ++i ) threads.emplace_back(work, i);
for ( auto &t : threads ) t.join();
std::cout << "Collected total = " << counter.collect() << std::endl;
return 0;
}
>
> Response: In your approach, a count for one counter is a count for all counters: https://gcc.godbolt.org/z/czovra5cd
Again this code hasn't been ruminated over again and again, but
something along the lines of this:
https://gcc.godbolt.org/z/6KK8oafcd
And here it is copy-pasted:
#include <cassert>
#include <array>
#include <atomic>
#include <optional>
#include <set>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
#include <mutex>
#include <boost/lockfree/queue.hpp>
template<typename T, unsigned max_instances, unsigned max_threads>
class ThreadLocal final {
std::array<std::optional<T>, max_threads> storage{};
std::atomic<unsigned> nextIndex{0u};
boost::lockfree::queue<unsigned> freeList{max_threads};
unsigned const instance_id;
static unsigned next_instance_id(void) noexcept(false)
{
static std::atomic<unsigned> counter{0u};
unsigned id = counter.fetch_add(1, std::memory_order_relaxed);
if ( id >= max_instances ) throw std::runtime_error("Too many
ThreadLocal instances");
return id;
}
unsigned acquireSlot(void) noexcept(false)
{
unsigned index;
if (freeList.pop(index)) return index;
index = nextIndex.fetch_add(1u, std::memory_order_relaxed);
if ( index >= max_threads ) throw std::runtime_error("Exceeded
max_threads limit!");
return index;
}
void releaseSlot(unsigned index) noexcept
{
storage[index].reset();
freeList.push(index);
}
template<typename... Params>
unsigned registerThread(Params&&... args) noexcept(false)
{
static thread_local std::array<std::optional<unsigned>, max_instances>
slot_array{};
std::optional<unsigned> &entry = slot_array[instance_id];
if ( false == entry.has_value() )
{
unsigned index = acquireSlot();
entry = index;
// Ensure proper release when thread dies
struct Reclaimer {
ThreadLocal* owner;
unsigned index;
~Reclaimer() noexcept { owner->releaseSlot(index); }
};
static thread_local std::array<std::optional<Reclaimer>,
max_instances> reclaimers;
reclaimers[instance_id].emplace(Reclaimer{this, index});
}
unsigned const slot = *entry;
if ( false == storage[slot].has_value() ) storage[slot].emplace(
std::forward<Params>(args)... );
return slot;
}
public:
ThreadLocal(void) : instance_id( next_instance_id() ) {}
template<typename... Params>
T& operator()(Params&&... args) noexcept(false)
{
unsigned slot = registerThread( std::forward<Params>(args)... );
return *storage[slot];
}
template<typename... Params>
T const& operator()(Params&&... args) const noexcept(false)
{
return const_cast<ThreadLocal*>(this)->operator()(
std::forward<Params>(args)... );
}
};
// ===================
// Example Usage
// ===================
class Counter {
std::atomic<unsigned> total{0u};
std::atomic<unsigned> num_threads{0u};
std::mutex mtx;
struct PerThread {
std::atomic<unsigned> count{0u};
Counter &parent;
explicit PerThread(Counter *const c) noexcept(false) : parent(*c)
{
std::lock_guard lock{parent.mtx};
parent.pt_active.insert(this);
parent.num_threads.fetch_add(1u, std::memory_order_relaxed);
}
~PerThread(void) noexcept // we want std::terminate called
{
{
std::lock_guard lock{parent.mtx};
parent.pt_active.erase(this);
}
if ( unsigned const leftover =
count.load(std::memory_order_relaxed) )
parent.total.fetch_add(leftover, std::memory_order_relaxed);
parent.num_threads.fetch_sub(1u, std::memory_order_relaxed);
}
};
std::set<PerThread*> pt_active;
ThreadLocal<PerThread, 64u, 32u> pt;
public:
void incr(void) noexcept
{
pt(this).count.fetch_add(1u, std::memory_order_relaxed);
}
unsigned collect(void) noexcept(false)
{
unsigned sum = 0u;
{
std::lock_guard lock{mtx};
for ( PerThread *const p : pt_active )
sum += p->count.exchange(0u, std::memory_order_relaxed);
}
if ( sum ) total.fetch_add(sum, std::memory_order_relaxed);
return total.load(std::memory_order_relaxed);
}
};
#include <iostream>
auto main(void) -> int
{
Counter counter, counter2;
constexpr unsigned numThreads = 18u;
constexpr unsigned iterations = 200u;
const auto work = [&](unsigned)
{
for ( unsigned i = 0; i < iterations; ++i )
{
counter.incr();
counter2.incr();
}
};
std::vector<std::thread> threads;
threads.reserve(numThreads);
for ( unsigned i = 0; i < numThreads; ++i ) threads.emplace_back(work, i);
for ( auto &t : threads ) t.join();
std::cout << "Collected total = " << counter.collect() << std::endl;
return 0;
}
Received on 2025-10-05 20:07:49