wip(assembler): round L4 -- lazy per-thread caches: 17.3 MiB -> 43 KiB per thread, view 7% faster
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
This commit is contained in:
1 parent
046f2af495
commit
7770074503
15 files changed
+361
-48
No files matched your search
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "StandaloneWorkerThread.h"
|
||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@@ -10,12 +11,15 @@
|
||||
#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
|
||||
@@ -38,14 +42,58 @@ namespace StandaloneTlsProbe
|
||||
residentPages*static_cast<std::uint64_t>(pageSize)/1024};
|
||||
}
|
||||
|
||||
inline int Run(int workerCount)
|
||||
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",
|
||||
@@ -55,10 +103,47 @@ namespace StandaloneTlsProbe
|
||||
for(int index=1;index<=workerCount;++index)
|
||||
{
|
||||
const auto start=std::chrono::steady_clock::now();
|
||||
workers.emplace_back([&]
|
||||
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;});
|
||||
});
|
||||
@@ -76,6 +161,33 @@ namespace StandaloneTlsProbe
|
||||
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;
|
||||
@@ -86,7 +198,7 @@ namespace StandaloneTlsProbe
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
inline int Run(int)
|
||||
inline int Run(int,std::string_view="none")
|
||||
{
|
||||
std::fprintf(stderr,"TLS probe is Linux-only.\n");
|
||||
return 2;
|
||||
|
||||
Reference in new issue
Block a user