Detect CPU cache sizes via sysfs on aarch64 Linux#12233
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends CacheManager CPU cache size detection beyond x86_64 by adding a non-x86 Linux fallback that reads data/unified cache sizes from Linux sysfs (/sys/devices/system/cpu/cpu0/cache/index*/) and maps them into the existing L1/L2/L3 slots used by histogram tiling heuristics.
Changes:
- Add a Linux sysfs-based cache detector for non-x86_64 builds and wire it into
CacheManager::CacheManager(). - Introduce parsing for sysfs cache size strings (e.g.,
64K,2M) and store sizes by reported cache level.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const std::string base = "/sys/devices/system/cpu/cpu0/cache/index"; | ||
|
|
||
| for (int i = 0; i < 16; ++i) { | ||
| const std::string dir = base + std::to_string(i); |
| // Parse a sysfs cache "size" string like "64K", "2048K", "36864K", "2M". | ||
| int64_t ParseCacheSize(const std::string& s) { | ||
| if (s.empty()) return -1; // kUninitCache | ||
| int64_t mult = 1; | ||
| std::string num = s; | ||
| switch (num.back()) { | ||
| case 'K': | ||
| case 'k': | ||
| mult = 1024; | ||
| num.pop_back(); | ||
| break; | ||
| case 'M': | ||
| case 'm': | ||
| mult = 1024 * 1024; | ||
| num.pop_back(); | ||
| break; | ||
| case 'G': | ||
| case 'g': | ||
| mult = 1024 * 1024 * 1024; | ||
| num.pop_back(); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| try { | ||
| return static_cast<int64_t>(std::stoll(num)) * mult; | ||
| } catch (...) { | ||
| return -1; // kUninitCache | ||
| } | ||
| } |
|
@siqi-he Thank you for the optimization. May I ask why x86 isn't using the same cache detection and resorts to CPU instructions? Is there any caveat to using sysfs? note: I can reproduce the speedup: |
|
If memory serves, at the time when the detection was added for x86, there were all sorts of weirdnesses like inaccurate size in VMs. |
|
i believe the existing cpuid approach could work for majority of os (e.g. linux, windows and mac), whereas the sysfs approach only works for linux based system. i did not touch the existing logic for handling x86 linux as i was under the assumption it's working as expected. |
Summary
CacheManageronly detects L1/L2/L3 sizes via CPUID, which is x86-only. This adds a Linux sysfs detector for the non-x86_64 path: it walks/sys/devices/system/cpu/cpu0/cache/index*/, keeps data/unified caches, and stores each at the slot for its reported level (L1→[0], L2→[1], L3→[2]).Benchmark methodology
All benchmarks use
tree_method='hist',max_depth=8,max_bin=256, 100 rounds, 3 repeats (average of runs 2-3). CPU pinning viataskset. The benchmarks were run using aws ec2 c8g.24xl instance, using all physical cores (i.e. nthread=96).Datasets include real (Epsilon, Bosch, Santander) and synthetic (HIGGS with PolynomialFeatures expansion, various sparsity levels via injected NaN at fixed seed). Sparse datasets force the row-wise kernel path (
IsDense()=false). Predictions were verified to be identical (measured bynp.allclose) between master (60ae480) and this branch across all datasets at 100 rounds.Results
The gains scale with feature width (+24% to +42% at ≥2000 features); datasets around ~1000 features and below are within noise, with a few small regressions (worst: −5.8%). Net positive for wide workloads, roughly neutral for narrow ones.
full benchmark script