VoxelForgeCoreThreadLocal::FLazy<T> (new public header ThreadLocalStorage.h) makes every large thread_local cache a pointer that heap-allocates on that thread's first use (still worker-private, lock-free). Static TLS 17.33 MiB -> 43 KiB (initialised image 5.8 MiB -> 21 KiB); worker start 5.2 -> 0.12 ms; idle worker RSS 17.7 MiB -> 52 KiB. Linux workers back to 8 MiB stacks. World unchanged (L1 hashes Clang off/GCC default, W1/W12; FloatingIslands seed 0). Reviewer interleaved W12: 2.256 -> 2.091 s. MSVC/UBT not compiled here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ED33VnqepD4YY6gDAAA832
97 lines
2.9 KiB
C++
97 lines
2.9 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#if defined(__linux__)
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
// Keep host-owned Linux workers on the ordinary 8 MiB stack size. Core's large caches now live
|
|
// behind lazy thread-local handles, so they no longer need to be hidden by oversized stacks.
|
|
#if defined(__linux__)
|
|
class FStandaloneWorkerThread
|
|
{
|
|
public:
|
|
static constexpr std::size_t StackSizeBytes=8u*1024u*1024u;
|
|
|
|
FStandaloneWorkerThread()=default;
|
|
|
|
template<class F>
|
|
explicit FStandaloneWorkerThread(F&& function)
|
|
{
|
|
using FTask=std::decay_t<F>;
|
|
auto task=std::make_unique<FTask>(std::forward<F>(function));
|
|
pthread_attr_t attributes;
|
|
int result=pthread_attr_init(&attributes);
|
|
if(result!=0)throw std::system_error(result,std::generic_category(),"pthread_attr_init");
|
|
result=pthread_attr_setstacksize(&attributes,StackSizeBytes);
|
|
if(result==0)
|
|
{
|
|
result=pthread_create(&thread_,&attributes,&RunTask<FTask>,task.get());
|
|
if(result==0)task.release();
|
|
}
|
|
const int destroyResult=pthread_attr_destroy(&attributes);
|
|
if(result!=0)throw std::system_error(result,std::generic_category(),"pthread_create");
|
|
if(destroyResult!=0)
|
|
{
|
|
(void)pthread_join(thread_,nullptr);
|
|
throw std::system_error(destroyResult,std::generic_category(),"pthread_attr_destroy");
|
|
}
|
|
joinable_=true;
|
|
}
|
|
|
|
FStandaloneWorkerThread(const FStandaloneWorkerThread&)=delete;
|
|
FStandaloneWorkerThread& operator=(const FStandaloneWorkerThread&)=delete;
|
|
|
|
FStandaloneWorkerThread(FStandaloneWorkerThread&& other) noexcept
|
|
:thread_(other.thread_),joinable_(std::exchange(other.joinable_,false))
|
|
{}
|
|
|
|
FStandaloneWorkerThread& operator=(FStandaloneWorkerThread&& other) noexcept
|
|
{
|
|
if(this!=&other)
|
|
{
|
|
if(joinable_)(void)pthread_join(thread_,nullptr);
|
|
thread_=other.thread_;
|
|
joinable_=std::exchange(other.joinable_,false);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
~FStandaloneWorkerThread()
|
|
{
|
|
if(joinable_)(void)pthread_join(thread_,nullptr);
|
|
}
|
|
|
|
bool joinable()const noexcept{return joinable_;}
|
|
|
|
void join()
|
|
{
|
|
if(!joinable_)throw std::system_error(std::make_error_code(std::errc::invalid_argument));
|
|
const int result=pthread_join(thread_,nullptr);
|
|
if(result!=0)throw std::system_error(result,std::generic_category(),"pthread_join");
|
|
joinable_=false;
|
|
}
|
|
|
|
private:
|
|
template<class F>
|
|
static void* RunTask(void* raw)
|
|
{
|
|
std::unique_ptr<F> task(static_cast<F*>(raw));
|
|
(*task)();
|
|
return nullptr;
|
|
}
|
|
|
|
pthread_t thread_{};
|
|
bool joinable_=false;
|
|
};
|
|
#else
|
|
using FStandaloneWorkerThread=std::thread;
|
|
#endif
|