Skip to main content

sva_engine/
cache.rs

1// Concern: declares what a store holds under a content hash and how a key is built | Non-concern: any one store's medium and budget (disk.rs, memory.rs) | IO: (Hash) -> a payload + traces
2
3mod disk;
4mod entry_bytes;
5mod evict;
6mod label;
7mod memory;
8mod pack;
9mod stats;
10mod tiered;
11
12pub use disk::{DiskCache, IO_NANOS_PER_BYTE};
13pub use entry_bytes::{RawF64, SampleCodec};
14pub use memory::MemoryCache;
15pub use pack::{FORMAT as PACK_FORMAT, Medium, Pack, VecMedium};
16pub(crate) use stats::Recording;
17pub use stats::{CacheStats, Lookup, Outcome};
18pub use sva_formula::Hash;
19pub use tiered::Tiered;
20
21use std::path::Path;
22use std::time::Duration;
23
24use sva_formula::SpectralSum;
25use sva_samples::{Buffer, FilterTrace, Frames, Label};
26
27/// A spectral sum, one collapse of it, or one analysis of that collapse.
28#[derive(Clone, Debug, PartialEq)]
29pub enum Payload {
30    Samples(Box<Buffer>),
31    Frames(Box<Frames>),
32    Symbolic(Box<SpectralSum>),
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum PayloadKind {
37    Samples,
38    Frames,
39    Symbolic,
40}
41
42/// An entry not matching this is a miss, never a coercion.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Expected {
45    Samples {
46        rate: u32,
47        width: usize,
48        samples: usize,
49    },
50    Frames,
51    Symbolic,
52}
53
54/// Where a hit was answered from: this process's heap, or a store that outlives it.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Tier {
57    Memory,
58    Persistent,
59}
60
61#[derive(Clone, Debug, PartialEq)]
62pub struct Entry {
63    pub payload: Payload,
64    pub traces: Vec<FilterTrace>,
65    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
66    pub label: Option<Label>,
67    pub tier: Tier,
68}
69
70impl Expected {
71    pub fn kind(self) -> PayloadKind {
72        match self {
73            Expected::Samples { .. } => PayloadKind::Samples,
74            Expected::Frames => PayloadKind::Frames,
75            Expected::Symbolic => PayloadKind::Symbolic,
76        }
77    }
78}
79
80impl Payload {
81    pub fn kind(&self) -> PayloadKind {
82        match self {
83            Payload::Samples(_) => PayloadKind::Samples,
84            Payload::Frames(_) => PayloadKind::Frames,
85            Payload::Symbolic(_) => PayloadKind::Symbolic,
86        }
87    }
88
89    pub fn samples(&self) -> Option<&Buffer> {
90        match self {
91            Payload::Samples(buffer) => Some(buffer),
92            _ => None,
93        }
94    }
95
96    pub fn symbolic(&self) -> Option<&SpectralSum> {
97        match self {
98            Payload::Symbolic(sum) => Some(sum),
99            _ => None,
100        }
101    }
102
103    pub fn bytes(&self) -> usize {
104        match self {
105            Payload::Samples(b) => b.len() * b.width * size_of::<f64>(),
106            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
107            Payload::Symbolic(n) => {
108                size_of::<SpectralSum>()
109                    + n.lanes
110                        .iter()
111                        .map(|lane| {
112                            lane.atoms.len() * size_of::<sva_formula::SpectralAtom>()
113                                + (lane.series.len() + lane.modal.len()) * size_of::<SpectralSum>()
114                        })
115                        .sum::<usize>()
116            }
117        }
118    }
119
120    pub fn answers(&self, expected: Expected) -> bool {
121        match (self, expected) {
122            (
123                Payload::Samples(b),
124                Expected::Samples {
125                    rate,
126                    width,
127                    samples,
128                },
129            ) => b.rate == rate && b.width == width && b.len() == samples,
130            (Payload::Frames(_), Expected::Frames) => true,
131            (Payload::Symbolic(_), Expected::Symbolic) => true,
132            _ => false,
133        }
134    }
135}
136
137/// What a render spent on one value. `wasm32-unknown-unknown` has no clock at all, so a
138/// render there measures nothing.
139#[derive(Clone, Copy)]
140pub struct Cost {
141    #[cfg(not(target_arch = "wasm32"))]
142    began: std::time::Instant,
143}
144
145impl Cost {
146    pub fn begun() -> Cost {
147        Cost {
148            #[cfg(not(target_arch = "wasm32"))]
149            began: std::time::Instant::now(),
150        }
151    }
152
153    pub fn elapsed(self) -> Duration {
154        #[cfg(not(target_arch = "wasm32"))]
155        return self.began.elapsed();
156        #[cfg(target_arch = "wasm32")]
157        return Duration::ZERO;
158    }
159}
160
161/// A hash carries the table version, so a bump retires every symbolic entry.
162pub fn symbolic_key(src: Hash) -> Hash {
163    mixed(src, &[0x73_79_6d_62_6f_6c_69_63])
164}
165
166/// The buffer's own key and the window it was read through.
167pub fn frames_key(buffer: Hash, window: usize, hop: usize) -> Hash {
168    mixed(
169        buffer,
170        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
171    )
172}
173
174/// One closed form at one rate, origin and width, scored or not: the label is part of the value.
175pub fn buffer_key(
176    symbolic: Hash,
177    rate: u32,
178    origin_secs: f64,
179    width: usize,
180    score: sva_samples::AliasScore,
181) -> Hash {
182    mixed(
183        symbolic,
184        &[
185            u64::from(rate),
186            origin_secs.to_bits(),
187            width as u64,
188            u64::from(score == sva_samples::AliasScore::Asked),
189            0x62_75_66_66_65_72_00_01,
190        ],
191    )
192}
193
194const ADDRESS_ROTATE: u32 = 17;
195
196fn mixed(seed: Hash, parts: &[u64]) -> Hash {
197    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
198    for part in parts {
199        lanes.word(*part);
200    }
201    lanes.finish()
202}
203
204pub trait Cache: Sync {
205    fn load(&self, key: Hash, node: &str, expected: Expected) -> Option<Entry>;
206
207    fn store(&self, key: Hash, payload: &Payload, traces: &[FilterTrace], label: Option<&Label>);
208
209    fn holds(&self, key: Hash) -> bool;
210
211    fn worth_storing(&self, cost: Duration, bytes: usize, kind: PayloadKind) -> bool;
212
213    fn sweep(&self);
214
215    fn held_bytes(&self) -> u64;
216
217    fn evicted_bytes(&self) -> u64;
218
219    fn faults(&self) -> u64 {
220        0
221    }
222
223    fn max_bytes(&self) -> u64;
224
225    fn dir(&self) -> Option<&Path> {
226        None
227    }
228}