LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cache_metrics.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_CACHE_METRICS_H
6#define LINE_SOLVERS_CACHE_METRICS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * What a solver observed about the Cache nodes of a model.
12 *
13 * THE AvgTable CANNOT CARRY THIS AND IS NOT MEANT TO. A cache's answer is a hit
14 * probability per (node, read class), a per-list breakdown of it, and a per-item
15 * occupancy -- three different index spaces, none of them (station, class). The
16 * reference keeps them in `getAvgCacheTable` and `getAvgItemTable` for exactly
17 * that reason, and this struct is what those two tables are built from.
18 *
19 * EVERY FIELD IS OPTIONAL AND ABSENT MEANS NOT COMPUTED, never zero. A hit
20 * probability of 0 is a cache that never hits; an empty vector is a solver that
21 * did not measure one, and the tables print NaN there. The distinction matters
22 * because the four cache branches of SolverNC compute different subsets: the
23 * integrated caching-queueing network gives hit and miss but no per-item law,
24 * the non-reentrant Source-Cache-Sink model gives the per-item law and the
25 * per-list breakdown, and only the retrieval branches give a delayed-hit
26 * fraction at all.
27 */
28
29#include <cmath>
30#include <cstddef>
31#include <limits>
32#include <map>
33#include <string>
34#include <vector>
35
37#include "line/util/matrix.h"
38
39namespace line {
40namespace solvers {
41
42/** One Cache node's measured behaviour. */
43template <class T>
45 std::size_t node = 0; ///< 1-based node index of the Cache
46 /**
47 * The Cache node's NAME, which is what a cross-language payload must key on.
48 *
49 * `node` is an index into THIS struct's node order, and that order is not
50 * the model.json declaration order: on retrieval_simple the JSON declares
51 * Source, Cache, Queue, Sink while the struct holds Source, Queue, Sink,
52 * Cache, so the Cache is 2 to MATLAB and 4 here. A host restoring results
53 * by index therefore wrote onto the Sink, found no Cache, and silently kept
54 * the previous solver's numbers -- see CPPLINE.restoreCacheResults.
55 */
56 std::string name;
57 std::vector<double> itemcap; ///< (h) capacity of each list
58 std::vector<double> itemsize; ///< (n) storage cost per item, EMPTY without setItemSizes
59 std::size_t nitems = 0;
60 std::vector<T> hitprob; ///< (K) TRUE hit fraction, EMPTY = not computed
61 std::vector<T> missprob; ///< (K)
62 std::vector<T> delayedprob; ///< (K) delayed-hit fraction, EMPTY off a retrieval system
63 std::vector<T> latency; ///< (K) expected retrieval latency, EMPTY = not computed
64 Matrix<T> hitproblist; ///< (K x h) per-list hit fraction, EMPTY = not computed
65 Matrix<T> itemprob; ///< (n x h+1), column 0 = miss; EMPTY = not computed
66 std::vector<T> listcost; ///< (h) mean storage cost held by each list
67 /**
68 * (n) mean secondary requests waiting on the in-flight fetch of each item,
69 * and the same including the request that triggered the fetch. EMPTY off a
70 * retrieval system and off every solver that does not form the per-item law
71 * of block B; only the exact chain does, which is why the reference computes
72 * them in SolverCTMC alone. They are the DelayedHitQLen columns of
73 * `getAvgItemTable`.
74 */
76};
77
78/** Every Cache node of the model, in node order; empty on a model with none. */
79template <class T>
81 std::vector<CacheNodeMetrics<T>> caches;
82 bool empty() const { return caches.empty(); }
83};
84
85/**
86 * Assemble `CacheMetrics` from what a cache analyzer returned.
87 *
88 * ONE PLACE, EVERY BRANCH OF EVERY SOLVER. Each cache analyzer computes a
89 * different subset -- the retrieval ones give a delayed-hit fraction, the
90 * non-reentrant one gives the per-item law, the integrated one gives neither --
91 * and each leaves the rest empty. Assembling the struct here rather than in each
92 * branch keeps "absent means not computed" a single rule, which is what lets
93 * `getAvgCacheTable` print NaN in exactly the right places.
94 *
95 * IT LIVES HERE, BESIDE THE STRUCT IT BUILDS, rather than in a solver's runner:
96 * SolverNC and SolverMVA both have cache branches and must assemble the answer
97 * the same way, and a runner including another runner to borrow the helper is
98 * how the two would drift apart.
99 *
100 * THE MODEL'S CACHES ARE READ FROM THE STRUCT, not from the analyzer: the caps,
101 * the item count and the item sizes are model parameters and are reported even
102 * where the solve measured nothing, so a caller can see the cache it described.
103 */
104template <class T>
105CacheMetrics<T> cache_metrics_of(const qn::NetworkStruct<T>& sn, const std::vector<T>& hitprob,
106 const std::vector<T>& missprob,
107 const std::vector<T>& delayedprob, const std::vector<T>& latency,
108 const Matrix<T>& hitproblist, const Matrix<T>& itemprob,
109 const std::vector<T>& listcost) {
110 CacheMetrics<T> out;
111 for (std::size_t ind = 1; ind <= sn.nodes.size(); ++ind) {
112 if (sn.nodes[ind - 1].nodetype != qn::NodeType::Cache) continue;
113 const typename std::map<std::size_t, qn::CacheParam<T>>::const_iterator it =
114 sn.nodeparam.find(ind);
115 if (it == sn.nodeparam.end()) continue;
117 m.node = ind;
118 m.name = sn.nodes[ind - 1].name;
119 m.nitems = it->second.nitems;
120 for (std::size_t l = 0; l < it->second.itemcap.size(); ++l)
121 m.itemcap.push_back(static_cast<double>(it->second.itemcap[l]));
122 for (std::size_t i = 0; i < it->second.itemsize.size(); ++i)
123 m.itemsize.push_back(static_cast<double>(it->second.itemsize[i]));
124 m.hitprob = hitprob;
125 m.missprob = missprob;
126 m.delayedprob = delayedprob;
127 m.latency = latency;
128 m.hitproblist = hitproblist;
129 m.itemprob = itemprob;
130 m.listcost = listcost;
131 // The scalar hit and miss fractions of the non-reentrant branch, derived
132 // from the per-list breakdown it does report. NOT invented: a read
133 // either finds the item in some list or misses, so the row sum IS the
134 // hit probability and 1 minus it IS the miss probability.
135 if (m.hitprob.empty() && hitproblist.rows() > 0) {
136 const T one = num_traits<T>::from_int(1);
137 for (std::size_t r = 0; r < hitproblist.rows(); ++r) {
138 T acc = num_traits<T>::from_int(0);
139 bool any = false;
140 for (std::size_t l = 0; l < hitproblist.cols(); ++l) {
141 const double v = num_traits<T>::to_double(hitproblist(r, l));
142 if (std::isnan(v)) continue;
143 acc = T(acc + hitproblist(r, l));
144 any = true;
145 }
146 m.hitprob.push_back(any ? acc : num_traits<T>::from_double(
147 std::numeric_limits<double>::quiet_NaN()));
148 m.missprob.push_back(any ? T(one - acc)
150 std::numeric_limits<double>::quiet_NaN()));
151 }
152 }
153 out.caches.push_back(m);
154 }
155 return out;
156}
157
158/**
159 * The same, for the integrated caching-queueing branch, whose hit and miss
160 * probabilities are (ncaches x nclasses) rather than one vector per model.
161 */
162template <class T>
164 const Matrix<T>& missprob) {
165 CacheMetrics<T> out =
166 cache_metrics_of(sn, std::vector<T>(), std::vector<T>(), std::vector<T>(), std::vector<T>(),
167 Matrix<T>(), Matrix<T>(), std::vector<T>());
168 for (std::size_t c = 0; c < out.caches.size(); ++c) {
169 if (c >= hitprob.rows()) break;
170 out.caches[c].hitprob.clear();
171 out.caches[c].missprob.clear();
172 for (std::size_t r = 0; r < hitprob.cols(); ++r) {
173 out.caches[c].hitprob.push_back(hitprob(c, r));
174 out.caches[c].missprob.push_back(missprob(c, r));
175 }
176 }
177 return out;
178}
179
180} // namespace solvers
181} // namespace line
182
183#endif // LINE_SOLVERS_CACHE_METRICS_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
Dense matrix and non-owning view.
CacheMetrics< T > cache_metrics_of(const qn::NetworkStruct< T > &sn, const std::vector< T > &hitprob, const std::vector< T > &missprob, const std::vector< T > &delayedprob, const std::vector< T > &latency, const Matrix< T > &hitproblist, const Matrix< T > &itemprob, const std::vector< T > &listcost)
Assemble CacheMetrics from what a cache analyzer returned.
CacheMetrics< T > cache_metrics_of_matrix(const qn::NetworkStruct< T > &sn, const Matrix< T > &hitprob, const Matrix< T > &missprob)
The same, for the integrated caching-queueing branch, whose hit and miss probabilities are (ncaches x...
A queueing network and its refreshed NetworkStruct.
Every Cache node of the model, in node order; empty on a model with none.
std::vector< CacheNodeMetrics< T > > caches
One Cache node's measured behaviour.
std::vector< T > delayedhitqlen
(n) mean secondary requests waiting on the in-flight fetch of each item, and the same including the r...
std::vector< double > itemcap
(h) capacity of each list
std::vector< double > itemsize
(n) storage cost per item, EMPTY without setItemSizes
std::vector< T > hitprob
(K) TRUE hit fraction, EMPTY = not computed
std::vector< T > delayedprob
(K) delayed-hit fraction, EMPTY off a retrieval system
Matrix< T > hitproblist
(K x h) per-list hit fraction, EMPTY = not computed
std::size_t node
1-based node index of the Cache
std::vector< T > latency
(K) expected retrieval latency, EMPTY = not computed
std::vector< T > listcost
(h) mean storage cost held by each list
Matrix< T > itemprob
(n x h+1), column 0 = miss; EMPTY = not computed
std::vector< T > delayedhitqlenfull
std::string name
The Cache node's NAME, which is what a cross-language payload must key on.