LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mva_cache.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_MVA_SOLVER_MVA_CACHE_H
6#define LINE_SOLVERS_MVA_SOLVER_MVA_CACHE_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * The non-reentrant cache analyzer: a Source-Cache-Sink model.
12 *
13 * Templated port of `matlab/src/solvers/MVA/solver_mva_cache_analyzer.m`.
14 *
15 * A read of class r reaches the cache at the Source rate weighted by the class
16 * read distribution, and completes as a HIT (switching to `hitclass(r)`) or a
17 * MISS (switching to `missclass(r)`). The analyzer computes the per-item
18 * occupancy of the cache and from it the miss rate of each read class, then
19 * splits the Source throughput between the hit and the miss class.
20 *
21 * The item occupancy comes from the cache algorithm family, keyed on the
22 * replacement policy:
23 *
24 * RR, FIFO `exact` -> cache_mva (the exact product-form recursion)
25 * default -> cache_prob_fpi (the fixed-point approximation)
26 * LRU -> cache_ttl_lrua (the LRU-A characteristic-time approximation)
27 * HLRU -> cache_ttl_hlru (the h-LRU / LRU(m) characteristic time)
28 *
29 * The RR/FIFO inputs -- the access factors `gamma` -- are built by
30 * `da_cache_isolate`, which spreads each class rate over the items through its
31 * read distribution and attaches the list-to-list access-cost matrices;
32 * `qn::CacheParam` carries `pread` and `accost` in exactly the shape it consumes.
33 *
34 * THE TTL FAMILY IS SINGLE-STREAM. `cache_ttl_lrua` and `cache_ttl_hlru` read
35 * only the FIRST user stream in the reference (`lambda(1,i,j)`, `R{1,i}`), so
36 * the port passes the first reading class's per-item rates and access graphs,
37 * which is what those two consume. A model with more than one reading class is
38 * therefore an LRU/h-LRU approximation over its first read law, matching the
39 * reference exactly rather than silently aggregating.
40 *
41 * WHAT IS REFUSED BY NAME. The exact solution of any policy other than RR/FIFO,
42 * SFIFO, CLIMB and QLRU, and the marked-MAP LRU sub-branch, follow the
43 * reference's own `line_error`s. The marked-MAP branch needs an MMAP Source
44 * with per-item marks, which the builder cannot yet construct.
45 *
46 * Arithmetic: RR/FIFO exact (cache_mva) is field arithmetic and runs at any T;
47 * the FPI approximation evaluates a fixed point on a tolerance and needs a
48 * transcendental T, so it refuses under Rational.
49 */
50
51#include <cmath>
52#include <vector>
53
61
62namespace line {
63namespace mva {
64
65/** What the cache analyzer reports beyond the [Q,U,R,T] block. */
66template <class T>
69 std::vector<T> hitprob; ///< per class, NaN where the class does not read
70 std::vector<T> missprob;
71 /**
72 * (R x h) per-list hit fractions, access-weighted over items. NaN unless the
73 * EXACT recursion ran: only it produces a genuine per-list occupancy, which
74 * is why the reference gates this on `pijlist` rather than on `pij`.
75 */
77 /** (n x h+1) per-item occupancy, column 0 = miss. EMPTY = not computed. */
79 std::string actualmethod;
80};
81
82/** Port of `solver_mva_cache_analyzer.m` for a Source-Cache-Sink model. */
83template <class T>
85 using qn::NodeType;
86 const T zero = num_traits<T>::from_int(0);
87 const std::size_t M = L.nstations, R = L.nclasses;
88
89 // the Cache node and its parameters
90 std::size_t cnode = 0;
91 for (std::size_t i = 0; i < L.nof_nodes(); ++i)
92 if (L.nodes[i].nodetype == NodeType::Cache) {
93 cnode = i + 1;
94 break;
95 }
96 if (cnode == 0) throw InputError("solver_mva_cache_analyzer: the model has no Cache node");
97 const auto it = L.nodeparam.find(cnode);
98 if (it == L.nodeparam.end())
99 throw InputError("solver_mva_cache_analyzer: the Cache node has no parameters");
100 const qn::CacheParam<T>& ch = it->second;
101
102 const std::size_t nan_flag = 0;
103 (void)nan_flag;
104 const std::size_t n = ch.nitems, h = ch.itemcap.size();
105
106 // the Source, and its per-class read rate
107 std::size_t src = 0;
108 for (std::size_t i = 0; i < M; ++i)
109 if (L.stations[i].nodetype == NodeType::Source) src = i + 1;
110 if (src == 0) throw InputError("solver_mva_cache_analyzer: a non-reentrant cache needs a Source");
111 std::vector<T> sourceRate(R, zero);
112 for (std::size_t r = 0; r < R; ++r) {
113 const double v = num_traits<T>::to_double(L.rates(src - 1, r));
114 if (std::isfinite(v)) sourceRate[r] = L.rates(src - 1, r);
115 }
116
117 // isolate the cache: gamma over the items, plus the per-class per-item
118 // arrival rates and access graphs the TTL family needs
120 dch.nitems = n;
121 dch.itemcap = ch.itemcap;
122 dch.pread = ch.pread;
123 dch.accost = ch.accost;
124 const da::CacheIsolateResult<T> iso = da::da_cache_isolate(dch, sourceRate);
125
126 // per-item occupancy pij: column 0 is the miss column
127 Matrix<T> pij;
128 // The genuine per-list occupancy (n x h), set ONLY by the exact branch. The
129 // reference gates both per-list and per-item reporting on this being set.
130 Matrix<T> pijlist;
131 std::string method;
134 if (opt.method == "exact") {
136 // prepend the miss column, |1 - sum_l pij|, as the reference does
137 pij = Matrix<T>(n, h + 1, zero);
138 for (std::size_t k = 0; k < n; ++k) {
139 T s = zero;
140 for (std::size_t l = 0; l < h; ++l) {
141 pij(k, l + 1) = cm.pij(k, l);
142 s += cm.pij(k, l);
143 }
144 pij(k, 0) = num_abs(T(num_traits<T>::from_int(1) - s));
145 }
146 pijlist = cm.pij;
147 method = "exact";
148 } else {
149 if constexpr (!num_traits<T>::has_transcendental) {
150 throw UnsupportedError(
151 "solver_mva_cache_analyzer: the RR/FIFO fixed-point approximation stops on a "
152 "tolerance and needs a transcendental T; use method 'exact' under Rational");
153 } else {
154 pij = cache::cache_prob_fpi(iso.gamma, ch.itemcap);
155 method = "fpi";
156 }
157 }
160 // The TTL characteristic-time approximations, over the FIRST reading
161 // class's stream (see the header note). A marked-MAP Source would take
162 // the reference's cache_ttl_lrum_map branch, which is not reachable
163 // here, so a plain rate stream is the only case.
164 if constexpr (!num_traits<T>::has_transcendental) {
165 throw UnsupportedError(
166 "solver_mva_cache_analyzer: the LRU / h-LRU characteristic-time approximation "
167 "solves a fixed point on a tolerance and needs a transcendental T");
168 } else {
169 std::size_t rd = ch.pread.size();
170 for (std::size_t v = 0; v < ch.pread.size(); ++v)
171 if (!ch.pread[v].empty()) {
172 rd = v;
173 break;
174 }
175 if (rd == ch.pread.size())
176 throw InputError("solver_mva_cache_analyzer: no class reads the cache");
178 // cache_ttl_hlru wants the (users x n) request-rate matrix and
179 // sums over the user rows: MATLAB passes the full lambda and
180 // does lam = sum_v lambda(v,:,min(2,h+1)). Build one row per
181 // class (empty-pread rows are zero and contribute nothing).
182 const std::size_t u = ch.pread.size();
183 Matrix<T> lam_h(u, n, zero);
184 for (std::size_t v = 0; v < u; ++v)
185 for (std::size_t k = 0; k < n; ++k)
186 lam_h(v, k) = iso.lambda_cache[v](k, 0);
187 pij = cache::cache_ttl_hlru(lam_h, ch.itemcap);
188 method = "ttl";
189 } else {
190 // cache_ttl_lrua reads user 1 literally (lambda(1,...), R{1,...});
191 // MATLAB passes the full array but only ever indexes the first
192 // user row, so the LRU stream is class index 0, not `rd`.
193 std::vector<T> mT(ch.itemcap.size());
194 for (std::size_t l = 0; l < ch.itemcap.size(); ++l)
195 mT[l] = num_traits<T>::from_int(ch.itemcap[l]);
197 iso.lambda_cache[0], iso.Rcost[0], mT,
199 method = "ttl";
200 }
201 }
202 } else {
203 throw UnsupportedError(
204 "solver_mva_cache_analyzer: replacement policy " +
205 std::to_string(static_cast<int>(ch.replacestrat)) +
206 " has no MVA cache analyzer; RR, FIFO, LRU and h-LRU are supported");
207 }
208
209 // per-class miss rate: the class request rate on each item times the item's
210 // miss probability, summed over items
211 std::vector<T> missRate(R, zero);
212 for (std::size_t v = 0; v < R; ++v) {
213 if (v >= ch.pread.size() || ch.pread[v].empty()) continue;
214 for (std::size_t k = 0; k < n; ++k)
215 missRate[v] += T(sourceRate[v] * ch.pread[v][k] * pij(k, 0));
216 }
217
218 // assemble outputs. A Cache is not a queue, so Q/U/R at it are zero; the
219 // Source carries its own throughput, and the hit and miss classes carry the
220 // split of it.
221 MvaSolution<T> out;
222 out.Q = Matrix<T>(M, R, zero);
223 out.U = Matrix<T>(M, R, zero);
224 out.R = Matrix<T>(M, R, zero);
225 out.Tp = Matrix<T>(M, R, zero);
226 out.C.assign(R, zero);
227 out.X.assign(R, zero);
228 for (std::size_t r = 0; r < R; ++r) out.Tp(src - 1, r) = sourceRate[r];
229 for (std::size_t r = 0; r < R; ++r) {
230 if (r >= ch.hitclass.size()) continue;
231 const std::size_t hc = ch.hitclass[r], mc = ch.missclass[r];
232 if (hc == 0 || mc == 0) continue;
233 out.X[mc - 1] = T(out.X[mc - 1] + missRate[r]);
234 out.X[hc - 1] = T(out.X[hc - 1] + T(sourceRate[r] - missRate[r]));
235 }
236
237 CacheResult<T> res;
238 res.sol = out;
239 res.sol.method = method;
240 res.sol.lG = std::numeric_limits<double>::quiet_NaN();
241 res.actualmethod = method;
242 const T nan = num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN());
243 res.hitprob.assign(R, nan);
244 res.missprob.assign(R, nan);
245 for (std::size_t r = 0; r < R; ++r) {
246 if (r >= ch.pread.size() || ch.pread[r].empty()) continue;
247 if (!(sourceRate[r] > zero)) continue;
248 res.missprob[r] = T(missRate[r] / sourceRate[r]);
249 res.hitprob[r] = T(num_traits<T>::from_int(1) - res.missprob[r]);
250 }
251
252 // per-list hit probabilities, access-weighted, ONLY where the exact
253 // algorithm produced a genuine per-list occupancy
254 res.hitproblist = Matrix<T>(R, h, nan);
255 if (pijlist.rows() > 0) {
256 for (std::size_t v = 0; v < R; ++v) {
257 if (v >= ch.pread.size() || ch.pread[v].empty()) continue;
258 for (std::size_t l = 0; l < h; ++l) {
259 T acc = zero;
260 for (std::size_t k = 0; k < n; ++k) acc = T(acc + ch.pread[v][k] * pijlist(k, l));
261 res.hitproblist(v, l) = acc;
262 }
263 }
264 }
265
266 // per-item occupancy. RR/FIFO under the FIXED POINT has no per-item law, so
267 // the reference re-runs the exact recursion for it -- and refuses past 10
268 // items, where that recursion is not affordable, reporting NaN rather than
269 // the fixed point's marginals, which are not a per-item distribution.
270 if (pijlist.rows() > 0) {
271 res.itemprob = pij;
272 } else if (pij.cols() == h + 1) {
275 if (n > 10) {
276 res.itemprob = Matrix<T>(n, h + 1, nan);
277 } else {
279 res.itemprob = Matrix<T>(n, h + 1, zero);
280 for (std::size_t k = 0; k < n; ++k) {
281 T s = zero;
282 for (std::size_t l = 0; l < h; ++l) {
283 res.itemprob(k, l + 1) = cm.pij(k, l);
284 s = T(s + cm.pij(k, l));
285 }
286 res.itemprob(k, 0) = num_abs(T(num_traits<T>::from_int(1) - s));
287 }
288 }
289 } else {
290 res.itemprob = pij;
291 }
292 }
293 return res;
294}
295
296} // namespace mva
297} // namespace line
298
299#endif // LINE_SOLVERS_MVA_SOLVER_MVA_CACHE_H
Exact mean value analysis of a multi-list cache.
Cache hit and miss probabilities from the fixed-point multipliers.
TTL (characteristic-time) approximation of an h-LRU / LRU(m) cache.
TTL (characteristic-time) approximation of an LRU cache whose lists form an arbitrary access graph.
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::size_t nof_nodes() const
std::map< std::size_t, CacheParam< T > > nodeparam
Cache parameters by 1-based NODE index; only Cache nodes have an entry.
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< NodeDef > nodes
every node, in creation order
Isolated-cache input construction for the decomposition methods.
The option and result types every MVA analyzer shares.
CacheMvaResult< T > cache_mva(const Matrix< T > &gamma, const std::vector< int > &m)
Exact mean value analysis of a multi-list cache.
Definition cache_mva.h:66
Matrix< T > cache_prob_fpi(const Matrix< T > &gamma, const std::vector< int > &m)
Cache hit and miss probabilities from the fixed-point multipliers.
Matrix< T > cache_ttl_hlru(const Matrix< T > &lambda, const std::vector< int > &m)
TTL (characteristic-time) approximation of an h-LRU / LRU(m) cache.
Matrix< T > cache_ttl_lrua(const Matrix< T > &lambda, const std::vector< Matrix< T > > &R, const std::vector< T > &m, const T &tol, unsigned maxswp=200)
TTL (characteristic-time) approximation of an LRU cache whose lists form an arbitrary access graph.
CacheIsolateResult< T > da_cache_isolate(const CacheParam< T > &ch, const std::vector< T > &lambda)
Isolated-cache input construction for the decomposition methods.
NodeType
Node kinds, with the values of MATLAB NodeType.
Definition lang_types.h:324
@ HLRU
h-LRU / LRU(m): h lists, promote i -> i+1 on a hit
Definition lang_types.h:383
@ LRU
least recently used
Definition lang_types.h:382
@ FIFO
first in, first out
Definition lang_types.h:380
CacheResult< T > solver_mva_cache_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_cache_analyzer.m for a Source-Cache-Sink model.
T num_abs(const T &v)
Definition number.h:172
A queueing network and its refreshed NetworkStruct.
Return value of cache_mva, mirroring [pi,pi0,pij,x,u,E].
Definition cache_mva.h:50
Matrix< T > pij
(n x h) probability that item k sits in list l
Definition cache_mva.h:53
Return value of da_cache_isolate, mirroring [gamma,lambda_cache,Rcost].
std::vector< Matrix< T > > lambda_cache
(u) matrices of size n x (h+1)
Matrix< T > gamma
(n x h) access factors
std::vector< std::vector< Matrix< T > > > Rcost
(u x n) of (h+1)x(h+1)
The fields of sn.nodeparam{cache} that da_cache_isolate reads.
std::vector< int > itemcap
(h) list capacities
std::vector< std::vector< Matrix< T > > > accost
(u) x (n) of (h+1)x(h+1), or empty
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN
static constexpr double FineTol
Definition lang_types.h:668
What the cache analyzer reports beyond the [Q,U,R,T] block.
std::vector< T > hitprob
per class, NaN where the class does not read
std::vector< T > missprob
Matrix< T > itemprob
(n x h+1) per-item occupancy, column 0 = miss.
Matrix< T > hitproblist
(R x h) per-list hit fractions, access-weighted over items.
The options SolverMVA reads.
Definition mva_types.h:31
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::vector< T > X
Definition mva_types.h:98
std::vector< T > C
Definition mva_types.h:98
std::vector< std::vector< Matrix< T > > > accost
(u) x (n) of (h+1)x(h+1), or empty
std::vector< int > itemcap
std::vector< std::size_t > missclass
std::vector< std::size_t > hitclass
lang::ReplacementStrategy replacestrat
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN