|
5 | 5 |
|
6 | 6 | #include "extractor_helpers.h" |
7 | 7 | #include <cmath> |
| 8 | +#include <cstdint> |
| 9 | +#include <cstdlib> |
8 | 10 | #include <cstring> |
| 11 | +#include <mutex> |
| 12 | +#include <vector> |
9 | 13 |
|
10 | 14 | namespace dcperf { |
11 | 15 | namespace feature_extractors { |
@@ -280,6 +284,84 @@ float cappedConvertFloat(double value, float min_val, float max_val) { |
280 | 284 | return result; |
281 | 285 | } |
282 | 286 |
|
| 287 | +// ====================================================================== |
| 288 | +// Memory-streaming stride sweep (backend/DRAM-pressure lever) |
| 289 | +// ====================================================================== |
| 290 | + |
| 291 | +namespace { |
| 292 | + |
| 293 | +struct SweepConfig { |
| 294 | + std::vector<float> buf; // process-wide, read-only after init |
| 295 | + size_t size = 0; // element count |
| 296 | + int n = 0; // reads per extractor call (FEEDSIM_SWEEP_N) |
| 297 | + size_t stride = 16; // element stride (FEEDSIM_SWEEP_STRIDE); 16 = 64B line |
| 298 | +}; |
| 299 | + |
| 300 | +SweepConfig g_sweep; |
| 301 | +std::once_flag g_sweep_once; |
| 302 | + |
| 303 | +int envInt(const char* name, int fallback) { |
| 304 | + const char* v = std::getenv(name); |
| 305 | + if (v == nullptr || v[0] == '\0') { |
| 306 | + return fallback; |
| 307 | + } |
| 308 | + int parsed = std::atoi(v); |
| 309 | + return parsed; |
| 310 | +} |
| 311 | + |
| 312 | +void initSweep() { |
| 313 | + // Defaults are the validated winning config (CPL/BGM/Grace, 2026-08-04): |
| 314 | + // 16 strided reads/call over a 64 MB DRAM-resident buffer at a 64 B (1 cache |
| 315 | + // line) stride. This closes most of the DRAM-bandwidth / LLC / L1-D / IPC gap |
| 316 | + // to prod (BGM mean uArch err 27%->18%, Grace 34%->23%). Override any knob via |
| 317 | + // the FEEDSIM_SWEEP_* env vars; set FEEDSIM_SWEEP_N=0 to disable entirely. |
| 318 | + int mb = envInt("FEEDSIM_SWEEP_MB", 64); |
| 319 | + if (mb < 1) { |
| 320 | + mb = 1; |
| 321 | + } |
| 322 | + g_sweep.n = envInt("FEEDSIM_SWEEP_N", 16); |
| 323 | + if (g_sweep.n < 0) { |
| 324 | + g_sweep.n = 0; |
| 325 | + } |
| 326 | + int stride = envInt("FEEDSIM_SWEEP_STRIDE", 16); |
| 327 | + g_sweep.stride = stride < 1 ? 1 : static_cast<size_t>(stride); |
| 328 | + g_sweep.size = static_cast<size_t>(mb) * 1024 * 1024 / sizeof(float); |
| 329 | + g_sweep.buf.resize(g_sweep.size); |
| 330 | + // Fill with pseudo-random data so the compiler can't fold the buffer away. |
| 331 | + uint64_t s = 0x9E3779B97F4A7C15ULL; |
| 332 | + for (size_t i = 0; i < g_sweep.size; ++i) { |
| 333 | + s = s * 6364136223846793005ULL + 1442695040888963407ULL; |
| 334 | + g_sweep.buf[i] = static_cast<float>((s >> 40) & 0xFFFF) * 1e-3f; |
| 335 | + } |
| 336 | +} |
| 337 | + |
| 338 | +} // namespace |
| 339 | + |
| 340 | +int sweepReadsPerCall() { |
| 341 | + std::call_once(g_sweep_once, initSweep); |
| 342 | + return g_sweep.n; |
| 343 | +} |
| 344 | + |
| 345 | +float runStrideSweep(uint64_t seed) { |
| 346 | + const SweepConfig& c = g_sweep; |
| 347 | + if (c.n == 0 || c.size == 0) { |
| 348 | + return 0.0f; |
| 349 | + } |
| 350 | + // Rotate the start offset per call so successive calls cover the whole |
| 351 | + // buffer rather than re-touching one region (that was the flaw in the |
| 352 | + // earlier size-only bumps and the B3 random gather). |
| 353 | + size_t off = (seed * 2654435761ULL) % c.size; |
| 354 | + float acc = 0.0f; |
| 355 | + for (int i = 0; i < c.n; ++i) { |
| 356 | + acc += c.buf[off]; |
| 357 | + off += c.stride; |
| 358 | + if (off >= c.size) { |
| 359 | + off -= c.size; |
| 360 | + } |
| 361 | + } |
| 362 | + return acc; |
| 363 | +} |
| 364 | + |
283 | 365 | } // namespace helpers |
284 | 366 | } // namespace feature_extractors |
285 | 367 | } // namespace dcperf |
0 commit comments