LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
retrieval_fpi_latency.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_API_RETRIEVAL_RETRIEVAL_FPI_LATENCY_H
6#define LINE_API_RETRIEVAL_RETRIEVAL_FPI_LATENCY_H
7
8/**
9 * @file
10 * @ingroup api_retrieval
11 * FPI-based approximation of the delayed-hit count and the expected latency of
12 * a list-based cache with a phase-type retrieval system.
13 *
14 * Templated port of matlab/src/api/retrieval/retrieval_fpi_latency.m,
15 * cross-checked against
16 * jar/src/main/java/jline/api/retrieval/Retrieval_fpi_latency.java.
17 *
18 * From the fetch-period duration F_i of item i,
19 * d_i = phi_i lambda_i E0[F_i^2] / (2 E0[F_i]),
20 * with the Palm moments of a reduced absorbing CTMC of item i's visits to the
21 * retrieval stations, E0[F_i^k] = k! pi_e (-D0)^{-k} e. The steps are
22 * 1. retrieval_fpi on the whole system -> phi_i, pi_{i,0}
23 * 2. the cavity occupancy phitilde_s = sum_{k!=i} phi_{s,k}, read off step 1
24 * 3. one PH block per station, shared stations slowed by 1/(1+phitilde_s),
25 * routed by R, absorbing on return to the cache
26 * 4. the two moments, hence d_i
27 * 5. Z = sum_i (phi_i + d_i) / sum_i lambda_i, the paper's eq. (latency tot): the mean
28 * latency over ALL read requests, a hit (pi_{i,j}) waiting zero
29 *
30 * Routing convention, as in MATLAB: index 0 is the outside (entry on a miss,
31 * return to the cache on completion) and indices 1..S are the retrieval
32 * stations, R[i](a,b) being the probability of a -> b for item i.
33 *
34 * Station types: IS fetches are independent; PS and LCFSPR are symmetric
35 * insensitive disciplines and admit general phase-type, class-dependent
36 * service; SIRO and FCFS reduce to the same single-exponential sojourn only
37 * with exponential service at a class-independent rate, and are rejected
38 * otherwise. Any other discipline is rejected outright, matching MATLAB.
39 *
40 * ARITHMETIC: it calls retrieval_fpi, a tolerance-stopped successive
41 * substitution, so it is gated on has_transcendental for the same reason. The
42 * linear algebra around it (the visit-ratio solve and the two moment solves)
43 * would itself be exact, but the phitilde it is fed is not.
44 */
45
46#include <cmath>
47#include <cstddef>
48#include <vector>
49
51#include "line/num/number.h"
52#include "line/util/error.h"
53#include "line/util/linalg.h"
54#include "line/util/lu.h"
55#include "line/util/matrix.h"
56
57namespace line {
58namespace retrieval {
59
60/** Scheduling of a retrieval station, mirroring the MATLAB station_type strings. */
61enum class RetrievalStationType { IS, PS, SIRO, FCFS, LCFSPR };
62
63/** True for the disciplines that carry the mean-field sharing slowdown. */
67
68/** Mirrors the [Z, d, phi, pi0] return list of the MATLAB function. */
69template <class T>
71 T Z; ///< expected latency per read request (hits wait zero) of the delayed-hit system
72 std::vector<T> d; ///< (n) mean delayed hits awaiting fetch, per item
73 std::vector<T> phi; ///< (n) delayed-hit ratio phi_i
74 std::vector<T> pi0; ///< (n) miss ratio pi_{i,0}
75};
76
77/**
78 * Phase-type service of every item at one retrieval station.
79 *
80 * MATLAB carries this as alpha{s}(1,:,i) and T{s}(:,:,i); here alpha is one
81 * (n x f) matrix whose row i is the entry vector of item i, and sub is one
82 * (f x f) subgenerator per item.
83 */
84template <class T>
86 Matrix<T> alpha; ///< (n x f) entry vectors, row per item
87 std::vector<Matrix<T>> sub; ///< (n) subgenerators, (f x f) each
89
90 std::size_t phases() const { return alpha.cols(); }
91};
92
93namespace detail {
94
95/** Mean phase-type service time -alpha inv(Tm) 1. */
96template <class T>
97T ph_mean(const Matrix<T>& alpha_row, const Matrix<T>& Tm) {
98 const std::size_t f = Tm.rows();
99 std::vector<T> e(f, num_traits<T>::from_int(1));
100 Matrix<T> LU = Tm;
101 const std::vector<std::size_t> piv = lu_factor(LU);
102 lu_solve(LU, piv, e); // e <- inv(Tm) 1
103 T tau = num_traits<T>::from_int(0);
104 for (std::size_t k = 0; k < f; ++k) tau += alpha_row(0, k) * e[k];
105 return -tau;
106}
107
108} // namespace detail
109
110/**
111 * @brief FPI-based approximation of the delayed-hit count and the expected
112 * latency of a list-based cache with a phase-type retrieval system.
113 *
114 * @param m (h) cache list capacities
115 * @param lambda (n) per-item arrival rates
116 * @param gamma (n x h) access factors
117 * @param station (S) phase-type service and discipline of each retrieval station
118 * @param R (n) routing matrices, each (S+1) x (S+1), index 0 = outside
119 * @param options fixed-point options (tolerance, iteration cap, damping)
120 */
121template <class T>
123 const std::vector<T>& lambda,
124 const Matrix<T>& gamma,
125 const std::vector<RetrievalStationPH<T>>& station,
126 const std::vector<Matrix<T>>& R,
127 const FpiOptions& options = FpiOptions()) {
129 "retrieval_fpi_latency requires transcendental arithmetic: it is driven by "
130 "retrieval_fpi, a successive substitution stopped on a relative tolerance");
131 const std::size_t n = lambda.size();
132 const std::size_t S = station.size();
133 if (S == 0) throw InputError("retrieval_fpi_latency: no retrieval station");
134 if (gamma.rows() != n)
135 throw InputError("retrieval_fpi_latency: gamma and lambda disagree on the item count");
136 if (R.size() != n)
137 throw InputError("retrieval_fpi_latency: one routing matrix per item is required");
138 for (std::size_t i = 0; i < n; ++i)
139 if (R[i].rows() != S + 1 || R[i].cols() != S + 1)
140 throw InputError("retrieval_fpi_latency: routing matrices must be (S+1) x (S+1)");
141
142 std::vector<std::size_t> fsz(S);
143 for (std::size_t s = 0; s < S; ++s) {
144 fsz[s] = station[s].phases();
145 if (station[s].sub.size() != n)
146 throw InputError("retrieval_fpi_latency: one subgenerator per item is required");
147 if (station[s].alpha.rows() != n)
148 throw InputError("retrieval_fpi_latency: alpha must have one row per item");
149 for (std::size_t i = 0; i < n; ++i)
150 if (station[s].sub[i].rows() != fsz[s] || station[s].sub[i].cols() != fsz[s])
151 throw InputError("retrieval_fpi_latency: subgenerator size disagrees with alpha");
152 // SIRO/FCFS PS-collapse rationale: see _kb/03-api-layer.md (cpp port notes: retrieval)
153 if ((station[s].type == RetrievalStationType::SIRO ||
154 station[s].type == RetrievalStationType::FCFS) &&
155 fsz[s] > 1)
156 throw UnsupportedError(ErrorKind{"CacheRetrieval"},
157 "retrieval_fpi_latency: SIRO/FCFS retrieval stations require exponential "
158 "(single-phase) service");
159 }
160
161 // per-item mean service times at each station
163 for (std::size_t s = 0; s < S; ++s)
164 for (std::size_t i = 0; i < n; ++i) {
165 Matrix<T> arow(1, fsz[s]);
166 for (std::size_t k = 0; k < fsz[s]; ++k) arow(0, k) = station[s].alpha(i, k);
167 tau(i, s) = detail::ph_mean(arow, station[s].sub[i]);
168 }
169
170 // ... and the class-independence requirement at SIRO/FCFS stations
171 for (std::size_t s = 0; s < S; ++s) {
172 if (station[s].type != RetrievalStationType::SIRO &&
173 station[s].type != RetrievalStationType::FCFS)
174 continue;
175 double lo = 0.0, hi = 0.0;
176 for (std::size_t i = 0; i < n; ++i) {
177 const double x = num_traits<T>::to_double(tau(i, s));
178 if (i == 0 || x < lo) lo = x;
179 if (i == 0 || x > hi) hi = x;
180 }
181 if (hi - lo > 1e-9 * hi)
182 throw UnsupportedError(ErrorKind{"CacheRetrieval"},
183 "retrieval_fpi_latency: SIRO/FCFS retrieval stations require class-independent "
184 "mean service rates");
185 }
186
187 // IS stations aggregate into column 0 of eta; each shared station gets its own column.
188 std::vector<std::size_t> is_idx, ps_idx;
189 for (std::size_t s = 0; s < S; ++s) {
190 if (station[s].type == RetrievalStationType::IS)
191 is_idx.push_back(s);
192 else
193 ps_idx.push_back(s);
194 }
195 const std::size_t r = ps_idx.size();
196
197 // eta_{s,i} = (visits per fetch) * (mean service time)
198 Matrix<T> eta(n, r + 1, num_traits<T>::from_int(0));
199 for (std::size_t i = 0; i < n; ++i) {
200 Matrix<T> ImP(S, S);
201 for (std::size_t a = 0; a < S; ++a)
202 for (std::size_t b = 0; b < S; ++b)
203 ImP(a, b) = (a == b ? num_traits<T>::from_int(1) : num_traits<T>::from_int(0)) -
204 R[i](a + 1, b + 1);
205 const Matrix<T> Vinv = inverse(ImP);
206 std::vector<T> visits(S, num_traits<T>::from_int(0));
207 for (std::size_t b = 0; b < S; ++b)
208 for (std::size_t a = 0; a < S; ++a) visits[b] += R[i](0, a + 1) * Vinv(a, b);
209
210 T is_sum = num_traits<T>::from_int(0);
211 for (std::size_t s : is_idx) is_sum += visits[s] * tau(i, s);
212 eta(i, 0) = is_sum;
213 for (std::size_t p = 0; p < r; ++p) eta(i, 1 + p) = visits[ps_idx[p]] * tau(i, ps_idx[p]);
214 }
215
216 // step 1: FPI on the full system
217 const RetrievalFpiResult<T> full = retrieval_fpi(m, lambda, eta, gamma, options);
219 out.pi0 = full.pmiss;
220 out.phi.assign(n, num_traits<T>::from_int(0));
221 for (std::size_t i = 0; i < n; ++i)
222 for (std::size_t s = 0; s <= r; ++s) out.phi[i] += full.pdh(s, i);
223
224 out.d.assign(n, num_traits<T>::from_int(0));
225 const std::size_t Phi = [&]() {
226 std::size_t t = 0;
227 for (std::size_t s = 0; s < S; ++s) t += fsz[s];
228 return t;
229 }();
230 std::vector<std::size_t> off(S, 0);
231 for (std::size_t s = 1; s < S; ++s) off[s] = off[s - 1] + fsz[s - 1];
232
233 for (std::size_t i = 0; i < n; ++i) {
234 // step 2: the cavity occupancy left by the others, phitilde_s = sum_{k!=i}
235 // phi_{s,k}, read off the step-1 fixed point with item i's own term removed.
236 // That is the cavity retrieval_fpi itself applies in its sharing factor
237 // F(s,i) = 1 + sum_{k!=i} phi_{s,k}, so E0[F_i] below is a moment of the very
238 // fetch time that produced phi_i and the renewal-reward identity
239 // phi_i = lambda_i pi_{i,0} E0[F_i] behind thm:di holds. Re-solving the FPI on
240 // the n-1 other items is a different cavity: item i's cache slot is freed, the
241 // survivors miss less and so occupy the stations less, which understates
242 // phitilde and speeds the fetch up.
243 std::vector<T> phitilde(S, num_traits<T>::from_int(0));
244 for (std::size_t p = 0; p < r; ++p) {
245 T acc = num_traits<T>::from_int(0);
246 for (std::size_t k = 0; k < n; ++k)
247 if (k != i) acc += full.pdh(1 + p, k);
248 phitilde[ps_idx[p]] = acc;
249 }
250
251 // step 3: the reduced absorbing CTMC of item i's fetch
252 Matrix<T> D0(Phi, Phi, num_traits<T>::from_int(0));
253 std::vector<T> pe(Phi, num_traits<T>::from_int(0));
254 for (std::size_t s = 0; s < S; ++s) {
255 const T scale = is_shared_station(station[s].type)
257 (num_traits<T>::from_int(1) + phitilde[s])
259 Matrix<T> blk(fsz[s], fsz[s]);
260 for (std::size_t a = 0; a < fsz[s]; ++a)
261 for (std::size_t b = 0; b < fsz[s]; ++b) blk(a, b) = scale * station[s].sub[i](a, b);
262 for (std::size_t a = 0; a < fsz[s]; ++a)
263 for (std::size_t b = 0; b < fsz[s]; ++b) D0(off[s] + a, off[s] + b) += blk(a, b);
264 // completion rates out of each phase of station s
265 std::vector<T> compl_(fsz[s], num_traits<T>::from_int(0));
266 for (std::size_t a = 0; a < fsz[s]; ++a) {
267 T acc = num_traits<T>::from_int(0);
268 for (std::size_t b = 0; b < fsz[s]; ++b) acc += blk(a, b);
269 compl_[a] = -acc;
270 }
271 for (std::size_t sp = 0; sp < S; ++sp)
272 for (std::size_t a = 0; a < fsz[s]; ++a)
273 for (std::size_t b = 0; b < fsz[sp]; ++b)
274 D0(off[s] + a, off[sp] + b) +=
275 compl_[a] * R[i](s + 1, sp + 1) * station[sp].alpha(i, b);
276 for (std::size_t a = 0; a < fsz[s]; ++a)
277 pe[off[s] + a] = R[i](0, s + 1) * station[s].alpha(i, a);
278 }
279
280 // step 4: the two Palm moments, hence d_i
281 Matrix<T> A(Phi, Phi);
282 for (std::size_t a = 0; a < Phi; ++a)
283 for (std::size_t b = 0; b < Phi; ++b) A(a, b) = -D0(a, b);
284 Matrix<T> LU = A;
285 const std::vector<std::size_t> piv = lu_factor(LU);
286 std::vector<T> y(Phi, num_traits<T>::from_int(1));
287 lu_solve(LU, piv, y); // y = inv(A) 1
288 std::vector<T> y2 = y;
289 lu_solve(LU, piv, y2); // y2 = inv(A) y
291 for (std::size_t a = 0; a < Phi; ++a) {
292 M1 += pe[a] * y[a];
293 M2 += pe[a] * y2[a];
294 }
296 if (M1 == num_traits<T>::from_int(0))
297 throw NumericError(ErrorKind{"Degenerate"}, "retrieval_fpi_latency: zero mean fetch period");
298 out.d[i] = out.phi[i] * lambda[i] * M2 / (num_traits<T>::from_int(2) * M1);
299 }
300
301 // step 5: the expected latency over all read requests (paper eq. latency tot)
303 for (std::size_t i = 0; i < n; ++i) {
304 num += out.phi[i] + out.d[i];
305 den += lambda[i];
306 }
307 if (den == num_traits<T>::from_int(0))
308 throw NumericError(ErrorKind{"Degenerate"}, "retrieval_fpi_latency: no read traffic, the latency is undefined");
309 out.Z = num / den;
310 return out;
311}
312
313} // namespace retrieval
314} // namespace line
315
316#endif // LINE_API_RETRIEVAL_RETRIEVAL_FPI_LATENCY_H
InputError(const std::string &what)
Definition error.h:105
std::size_t size() const
Definition matrix.h:91
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:112
UnsupportedError(const std::string &what)
Definition error.h:119
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
RetrievalStationType
Scheduling of a retrieval station, mirroring the MATLAB station_type strings.
bool is_shared_station(RetrievalStationType t)
True for the disciplines that carry the mean-field sharing slowdown.
RetrievalFpiLatencyResult< T > retrieval_fpi_latency(const std::vector< int > &m, const std::vector< T > &lambda, const Matrix< T > &gamma, const std::vector< RetrievalStationPH< T > > &station, const std::vector< Matrix< T > > &R, const FpiOptions &options=FpiOptions())
FPI-based approximation of the delayed-hit count and the expected latency of a list-based cache with ...
RetrievalFpiResult< T > retrieval_fpi(const std::vector< int > &m, const std::vector< T > &lambda, const Matrix< T > &eta, const Matrix< T > &gamma, const FpiOptions &options=FpiOptions())
Fixed-point heuristic for a delayed-hit (list-based) cache.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Number-type abstraction for the templated API port.
Fixed-point heuristic for a delayed-hit (list-based) cache.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function.
Mirrors the [Z, d, phi, pi0] return list of the MATLAB function.
std::vector< T > pi0
(n) miss ratio pi_{i,0}
std::vector< T > d
(n) mean delayed hits awaiting fetch, per item
T Z
expected latency per read request (hits wait zero) of the delayed-hit system
std::vector< T > phi
(n) delayed-hit ratio phi_i
Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics.
Matrix< T > pdh
((r+1) x n) delayed-hit probabilities phi_{s,i}, s = 0..r
std::vector< T > pmiss
(n) miss ratios pi_{i,0}
Phase-type service of every item at one retrieval station.
std::vector< Matrix< T > > sub
(n) subgenerators, (f x f) each
Matrix< T > alpha
(n x f) entry vectors, row per item