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
208 lines
8.6 KiB
C++
208 lines
8.6 KiB
C++
#pragma once
|
|
|
|
#include "StandaloneWorkerThread.h"
|
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <condition_variable>
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <fstream>
|
|
#include <mutex>
|
|
#include <numeric>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#if defined(__linux__)
|
|
#include <unistd.h>
|
|
#include <sys/resource.h>
|
|
#endif
|
|
|
|
namespace StandaloneTlsProbe
|
|
{
|
|
#if defined(__linux__)
|
|
struct FMemorySample
|
|
{
|
|
std::uint64_t virtualKiB=0;
|
|
std::uint64_t residentKiB=0;
|
|
};
|
|
|
|
inline FMemorySample ReadMemory()
|
|
{
|
|
std::ifstream statm("/proc/self/statm");
|
|
std::uint64_t virtualPages=0,residentPages=0;
|
|
statm>>virtualPages>>residentPages;
|
|
const long pageSize=sysconf(_SC_PAGESIZE);
|
|
if(!statm||pageSize<=0)return {};
|
|
return {virtualPages*static_cast<std::uint64_t>(pageSize)/1024,
|
|
residentPages*static_cast<std::uint64_t>(pageSize)/1024};
|
|
}
|
|
|
|
inline int Run(int workerCount,std::string_view touchKind="none")
|
|
{
|
|
workerCount=std::clamp(workerCount,0,12);
|
|
VoxelForgeCore::WorldAssemblyRecipeV1 surfaceRecipe;
|
|
VoxelForgeCore::WorldAssemblyStratumPlanV1 surfacePlan;
|
|
VoxelForgeCore::Heightfield2D surfaceField(surfaceRecipe,surfacePlan);
|
|
if(touchKind=="surface-world")
|
|
{
|
|
VoxelForgeCore::WorldAssemblyBiomeProfileV1 biome;
|
|
biome.stableId=1;
|
|
surfaceRecipe.biomes.push_back(biome);
|
|
surfacePlan.strateIndex=0;
|
|
surfacePlan.topChunkZ=1;
|
|
surfacePlan.bottomChunkZ=-1;
|
|
surfaceField=VoxelForgeCore::Heightfield2D(surfaceRecipe,surfacePlan);
|
|
if(!surfaceField.isValid())
|
|
{
|
|
std::fprintf(stderr,"[TlsProbe] invalid synthetic SurfaceWorld cache probe input\n");
|
|
return 2;
|
|
}
|
|
}
|
|
VoxelForgeCore::FloatingIslandRecipeV1 islandRecipe;
|
|
VoxelForgeCore::FloatingIslandPlanV1 islandPlan;
|
|
for(std::uint32_t index=0;index<islandRecipe.primaryIslandCount;++index)
|
|
{
|
|
VoxelForgeCore::FloatingIslandAnchorV1 island;
|
|
island.stableId=index+1;
|
|
island.ordinal=index;
|
|
island.centerSubvoxels={static_cast<std::int64_t>(index)*64*256,0,0};
|
|
island.radiusSubvoxels=20*256;
|
|
island.topHalfSubvoxels=20*256;
|
|
island.topZSubvoxels=0;
|
|
island.bottomZSubvoxels=-80*256;
|
|
island.taperEndQ16=islandRecipe.taperEndMinimumQ16;
|
|
islandPlan.islands.push_back(island);
|
|
}
|
|
VoxelForgeCore::FloatingIslandFieldV1 islandField(islandRecipe,islandPlan);
|
|
if(touchKind=="floating-islands"&&!islandField.isValid())
|
|
{
|
|
std::fprintf(stderr,"[TlsProbe] invalid synthetic FloatingIslands cache probe input\n");
|
|
return 2;
|
|
}
|
|
std::mutex mutex;
|
|
std::condition_variable changed;
|
|
bool release=false;
|
|
bool touchRequested=false;
|
|
std::atomic<int> ready{0};
|
|
std::atomic<int> touchReady{0};
|
|
std::vector<FStandaloneWorkerThread> workers;
|
|
std::vector<long> startupMinorFaults(static_cast<std::size_t>(workerCount),0);
|
|
std::vector<long> firstTouchMinorFaults(static_cast<std::size_t>(workerCount),0);
|
|
std::vector<std::int64_t> checksums(static_cast<std::size_t>(workerCount),0);
|
|
workers.reserve(static_cast<std::size_t>(workerCount));
|
|
FMemorySample previous=ReadMemory();
|
|
std::printf("[TlsProbe] workers=0 create_ms=0.000 rss_kib=%llu rss_delta_kib=0 vms_kib=%llu stack_bytes=%zu\n",
|
|
static_cast<unsigned long long>(previous.residentKiB),
|
|
static_cast<unsigned long long>(previous.virtualKiB),
|
|
FStandaloneWorkerThread::StackSizeBytes);
|
|
for(int index=1;index<=workerCount;++index)
|
|
{
|
|
const auto start=std::chrono::steady_clock::now();
|
|
workers.emplace_back([&,index]
|
|
{
|
|
#if defined(__linux__)
|
|
rusage startupUsage{};
|
|
getrusage(RUSAGE_THREAD,&startupUsage);
|
|
startupMinorFaults[static_cast<std::size_t>(index-1)]=startupUsage.ru_minflt;
|
|
#endif
|
|
ready.fetch_add(1,std::memory_order_release);
|
|
changed.notify_all();
|
|
{
|
|
std::unique_lock lock(mutex);
|
|
changed.wait(lock,[&]{return touchRequested||release;});
|
|
}
|
|
if(touchRequested)
|
|
{
|
|
#if defined(__linux__)
|
|
rusage beforeTouch{};
|
|
rusage afterTouch{};
|
|
getrusage(RUSAGE_THREAD,&beforeTouch);
|
|
#endif
|
|
std::int64_t checksum=0;
|
|
if(touchKind=="surface-world")
|
|
{
|
|
for(int sample=0;sample<4;++sample)
|
|
checksum+=surfaceField.point((index*4+sample)*256,0,0).densitySubvoxels;
|
|
}
|
|
else if(touchKind=="floating-islands")
|
|
{
|
|
for(int sample=0;sample<4;++sample)
|
|
checksum+=islandField.densitySubvoxels(
|
|
(index*4+sample)*256,0,-20*256);
|
|
}
|
|
checksums[static_cast<std::size_t>(index-1)]=checksum;
|
|
#if defined(__linux__)
|
|
getrusage(RUSAGE_THREAD,&afterTouch);
|
|
firstTouchMinorFaults[static_cast<std::size_t>(index-1)]=
|
|
afterTouch.ru_minflt-beforeTouch.ru_minflt;
|
|
#endif
|
|
touchReady.fetch_add(1,std::memory_order_release);
|
|
changed.notify_all();
|
|
}
|
|
std::unique_lock lock(mutex);
|
|
changed.wait(lock,[&]{return release;});
|
|
});
|
|
{
|
|
std::unique_lock lock(mutex);
|
|
changed.wait(lock,[&]{return ready.load(std::memory_order_acquire)>=index;});
|
|
}
|
|
const double createMs=std::chrono::duration<double,std::milli>(
|
|
std::chrono::steady_clock::now()-start).count();
|
|
const FMemorySample current=ReadMemory();
|
|
const std::int64_t rssDelta=static_cast<std::int64_t>(current.residentKiB)
|
|
-static_cast<std::int64_t>(previous.residentKiB);
|
|
std::printf("[TlsProbe] workers=%d create_ms=%.3f rss_kib=%llu rss_delta_kib=%lld vms_kib=%llu\n",
|
|
index,createMs,static_cast<unsigned long long>(current.residentKiB),
|
|
static_cast<long long>(rssDelta),static_cast<unsigned long long>(current.virtualKiB));
|
|
previous=current;
|
|
}
|
|
const FMemorySample beforeTouch=ReadMemory();
|
|
std::printf("[TlsProbeTouch] kind=%.*s rss_before_kib=%llu\n",
|
|
static_cast<int>(touchKind.size()),touchKind.data(),
|
|
static_cast<unsigned long long>(beforeTouch.residentKiB));
|
|
if(touchKind!="none")
|
|
{
|
|
{
|
|
std::lock_guard lock(mutex);
|
|
touchRequested=true;
|
|
}
|
|
changed.notify_all();
|
|
{
|
|
std::unique_lock lock(mutex);
|
|
changed.wait(lock,[&]{return touchReady.load(std::memory_order_acquire)==workerCount;});
|
|
}
|
|
const FMemorySample afterTouch=ReadMemory();
|
|
std::printf("[TlsProbeTouch] kind=%.*s rss_after_kib=%llu rss_delta_kib=%lld checksum_sum=%lld\n",
|
|
static_cast<int>(touchKind.size()),touchKind.data(),
|
|
static_cast<unsigned long long>(afterTouch.residentKiB),
|
|
static_cast<long long>(static_cast<std::int64_t>(afterTouch.residentKiB)
|
|
-static_cast<std::int64_t>(beforeTouch.residentKiB)),
|
|
static_cast<long long>(std::accumulate(checksums.begin(),checksums.end(),std::int64_t{0})));
|
|
for(int index=0;index<workerCount;++index)
|
|
std::printf("[TlsProbeTouch] worker=%d startup_minor_faults=%ld first_touch_minor_faults=%ld\n",
|
|
index+1,startupMinorFaults[static_cast<std::size_t>(index)],
|
|
firstTouchMinorFaults[static_cast<std::size_t>(index)]);
|
|
}
|
|
{
|
|
std::lock_guard lock(mutex);
|
|
release=true;
|
|
}
|
|
changed.notify_all();
|
|
for(FStandaloneWorkerThread& worker:workers)worker.join();
|
|
std::printf("[TlsProbe] status=PASS threads_joined=%d\n",workerCount);
|
|
return 0;
|
|
}
|
|
#else
|
|
inline int Run(int,std::string_view="none")
|
|
{
|
|
std::fprintf(stderr,"TLS probe is Linux-only.\n");
|
|
return 2;
|
|
}
|
|
#endif
|
|
}
|