cache_size/x86.rs
1use raw_cpuid::{self, CpuId, CpuIdReaderNative};
2
3pub use raw_cpuid::CacheType;
4
5/// Uses the CPUID family info to detect Zen architecture CPUs.
6///
7/// Data pulled from https://en.wikichip.org/wiki/amd/cpuid.
8#[inline]
9fn amd_is_zen(cpuid: &CpuId<CpuIdReaderNative>) -> Option<bool> {
10 let info = cpuid.get_feature_info()?;
11 match (info.base_family_id(), info.extended_family_id()) {
12 (0xF, 0x8..=0xA) => Some(true),
13 _ => Some(false),
14 }
15}
16
17/// Uses cache parameters to get cache size at a given level with the provided cache type.
18#[inline]
19fn generic_cache_size(
20 cpuid: CpuId<CpuIdReaderNative>,
21 level: u8,
22 cache_type: CacheType,
23) -> Option<usize> {
24 cpuid
25 .get_cache_parameters()?
26 .filter(|c| c.level() == level && c.cache_type() == cache_type)
27 .map(|c| c.sets() * c.associativity() * c.coherency_line_size())
28 .min()
29}
30
31/// This is computed using tlb info. The values come back in kilobytes, so they are multiplied by
32/// 1024 to give the size in bytes to match the behaviour of other architectures.
33#[inline]
34fn amd_cache_size(
35 cpuid: CpuId<CpuIdReaderNative>,
36 level: u8,
37 cache_type: CacheType,
38) -> Option<usize> {
39 match (level, cache_type) {
40 (1, CacheType::Instruction) => cpuid
41 .get_l1_cache_and_tlb_info()
42 .map(|i| i.icache_size() as usize * 1024),
43 (1, CacheType::Data) => cpuid
44 .get_l1_cache_and_tlb_info()
45 .map(|i| i.icache_size() as usize * 1024),
46 (2, CacheType::Unified) => cpuid
47 .get_l2_l3_cache_and_tlb_info()
48 .map(|i| i.l2cache_size() as usize * 1024),
49 (3, CacheType::Unified) => cpuid
50 .get_l2_l3_cache_and_tlb_info()
51 .map(|i| i.l3cache_size() as usize * 1024),
52 _ => None,
53 }
54}
55
56/// Returns the total size in bytes of `level` cache with type `cache_type`.
57///
58/// The only possibilities for this returning `None` are if the system does not support cache
59/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
60/// fail, or if the selected cache level and/or type does not exist.
61///
62/// On an AMD Zen architecture this is computed using tlb info. The values come back in kilobytes,
63/// so they are multiplied by 1024 to give the size in bytes to match the behaviour of other
64/// architectures.
65///
66/// On other architectures this is computed as `associativity * line_size * sets`, and if there are multiple caches
67/// available, it returns the size of the **smallest** cache.
68#[inline]
69pub fn cache_size(level: u8, cache_type: CacheType) -> Option<usize> {
70 let cpuid = CpuId::new();
71 match cpuid.get_vendor_info()?.as_str() {
72 "AuthenticAMD" if amd_is_zen(&cpuid).unwrap_or(false) => {
73 amd_cache_size(cpuid, level, cache_type)
74 }
75 _ => generic_cache_size(cpuid, level, cache_type),
76 }
77}
78
79/// Uses cache parameters to get cache line size at a given level with the provided cache type.
80#[inline]
81fn generic_cache_line_size(
82 cpuid: CpuId<CpuIdReaderNative>,
83 level: u8,
84 cache_type: CacheType,
85) -> Option<usize> {
86 cpuid
87 .get_cache_parameters()?
88 .filter(|cparams| cparams.level() == level && cparams.cache_type() == cache_type)
89 .map(|cparams| cparams.coherency_line_size())
90 .min()
91}
92
93/// This is computed using tlb info. Instruction and data cache line sizes
94/// are available separately for the L1 cache, but only unified is available for L2 and L3 caches.
95#[inline]
96fn amd_cache_line_size(
97 cpuid: CpuId<CpuIdReaderNative>,
98 level: u8,
99 cache_type: CacheType,
100) -> Option<usize> {
101 match (level, cache_type) {
102 (1, CacheType::Instruction) => cpuid
103 .get_l1_cache_and_tlb_info()
104 .map(|i| i.icache_line_size() as usize),
105 (1, CacheType::Data) => cpuid
106 .get_l1_cache_and_tlb_info()
107 .map(|i| i.dcache_line_size() as usize),
108 (2, CacheType::Unified) => cpuid
109 .get_l2_l3_cache_and_tlb_info()
110 .map(|i| i.l2cache_line_size() as usize),
111 (3, CacheType::Unified) => cpuid
112 .get_l2_l3_cache_and_tlb_info()
113 .map(|i| i.l3cache_line_size() as usize),
114 _ => None,
115 }
116}
117
118/// Returns the line size in bytes of `level` cache with type `cache_type`.
119///
120/// The only possibilities for this returning `None` are if the system does not support cache
121/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
122/// fail, or if the selected cache level and/or type does not exist.
123///
124/// On an AMD Zen architecture this is computed using tlb info. Instruction and data cache line
125/// sizes are available separately for the L1 cache, but only unified is available for L2 and L3
126/// caches.
127///
128/// On other x86 architectures this is computed from
129/// [`coherency_line_size()`](raw_cpuid::CacheParameter::coherency_line_size),
130/// and if there are multiple caches available, it returns the size of the **smallest** cache.
131#[inline]
132pub fn cache_line_size(level: u8, cache_type: CacheType) -> Option<usize> {
133 let cpuid = CpuId::new();
134 match cpuid.get_vendor_info()?.as_str() {
135 "AuthenticAMD" if amd_is_zen(&cpuid).unwrap_or(false) => {
136 amd_cache_line_size(cpuid, level, cache_type)
137 }
138 _ => generic_cache_line_size(cpuid, level, cache_type),
139 }
140}
141
142/// Returns the total size in bytes of the L1 data cache.
143///
144/// The only possibilities for this returning `None` are if the system does not support cache
145/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
146/// fail, or if the system has no L1 data cache (if it's a unified L1 cache for example).
147///
148/// This is computed as `associativity * line_size * sets`, and if there are multiple caches
149/// available, it returns the size of the **smallest** cache.
150#[inline]
151pub fn l1_cache_size() -> Option<usize> {
152 cache_size(1, CacheType::Data)
153}
154
155/// Returns the line size in bytes of the L1 data cache.
156///
157/// The only possibilities for this returning `None` are if the system does not support cache
158/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
159/// fail, or if the system has no L1 data cache (if it's a unified L1 cache for example).
160///
161/// This is computed from [`coherency_line_size()`](raw_cpuid::CacheParameter::coherency_line_size),
162/// and if there are multiple caches available, it returns the size of the **smallest** cache.
163#[inline]
164pub fn l1_cache_line_size() -> Option<usize> {
165 cache_line_size(1, CacheType::Data)
166}
167
168/// Returns the total size in bytes of the unified L2 cache.
169///
170/// The only possibilities for this returning `None` are if the system does not support cache
171/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
172/// fail, or if the system has no unified L2 cache.
173///
174/// This is computed as `associativity * line_size * sets`, and if there are multiple caches
175/// available, it returns the size of the **smallest** cache.
176#[inline]
177pub fn l2_cache_size() -> Option<usize> {
178 cache_size(2, CacheType::Unified)
179}
180
181/// Returns the line size in bytes of the unified L2 cache.
182///
183/// The only possibilities for this returning `None` are if the system does not support cache
184/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
185/// fail, or if the system has no unified L2 cache.
186///
187/// This is computed from [`coherency_line_size()`](raw_cpuid::CacheParameter::coherency_line_size),
188/// and if there are multiple caches available, it returns the size of the **smallest** cache.
189#[inline]
190pub fn l2_cache_line_size() -> Option<usize> {
191 cache_line_size(2, CacheType::Unified)
192}
193
194/// Returns the total size in bytes of the unified L3 cache.
195///
196/// The only possibilities for this returning `None` are if the system does not support cache
197/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
198/// fail, or if the system has no unified L3 cache.
199///
200/// This is computed as `associativity * line_size * sets`, and if there are multiple caches
201/// available, it returns the size of the **smallest** cache.
202#[inline]
203pub fn l3_cache_size() -> Option<usize> {
204 cache_size(3, CacheType::Unified)
205}
206
207/// Returns the line size in bytes of the unified L3 cache.
208///
209/// The only possibilities for this returning `None` are if the system does not support cache
210/// parameters, in which case [`get_cache_parameters()`](raw_cpuid::CpuId::get_cache_parameters) will
211/// fail, or if the system has no unified L3 cache.
212///
213/// This is computed from [`coherency_line_size()`](raw_cpuid::CacheParameter::coherency_line_size),
214/// and if there are multiple caches available, it returns the size of the **smallest** cache.
215#[inline]
216pub fn l3_cache_line_size() -> Option<usize> {
217 cache_line_size(3, CacheType::Unified)
218}
219
220/// Tests
221///
222/// I'd like to figure out a way to test this crate, but it's behavior being entirely
223/// hardware-dependant that seems hard, if not impossible.
224#[cfg(test)]
225mod tests {}