1classdef Cache < StatefulNode
2 % Multi-level cache node with hit/miss
class switching
4 % Models cache memory systems with multiple levels and replacement strategies.
6 % Copyright (c) 2012-2026, Imperial College London
20 totalCacheCapacity; % sum(itemLevelCap)
21 retrievalSystemCapacity; % 0 until setRetrievalSystem
is called; nitems-totalCacheCapacity otherwise
22 retrievalSystemQueueIndices; % containers.Map jobinClassIdx -> [queue node indices]
23 retrievalClassIndices; % set of indices of retrieval
classes (used in afterEventCache READ)
24 retrievalRoutingEntries; % cell
array of [fromCls,toCls,srcNode,dstNode,prob] routing tuples;
25 % link() injects these into the routing matrix
P (the auto-generated
26 % retrieval
classes are not part of the user-supplied
P).
31 function self = Cache(model, name, nitems, itemLevelCap, replStrat, graph)
32 % CACHE Create a Cache node instance
34 % @brief Creates a Cache node with configurable levels and replacement strategy
35 % @param model Network model to add the cache to
36 % @param name String identifier for the cache node
37 % @param nitems Total number of cacheable items
38 % @param itemLevelCap Vector specifying capacity of each cache level
39 % @param replStrat Replacement strategy (LRU, FIFO, Random, etc.)
40 % @param graph Optional graph structure for cache hierarchy
41 % @return self Cache instance configured for the given model
43 % The constructor creates a multi-level cache with the specified
44 % total items and per-level capacities. The replacement strategy
45 % determines how items are evicted when cache levels become full.
47 self@StatefulNode(name);
48 if model.isMatlabNative()
49 if ~exist('itemLevelCap','var')
54 self.output = Dispatcher(
classes);
55 self.schedPolicy = SchedStrategyType.NP;
56 self.schedStrategy = SchedStrategy.FCFS;
57 self.items = ItemSet(model, [name,'_','Items'], nitems, self);
58 self.nLevels = nnz(itemLevelCap);
59 self.cap = Inf; % job capacity
61 self.itemLevelCap = itemLevelCap; % item capacity
62 self.totalCacheCapacity = sum(itemLevelCap);
63 self.retrievalSystemCapacity = 0; % no retrieval system by
default
64 if self.totalCacheCapacity > nitems
65 line_error(mfilename,sprintf(
'The number of items is smaller than the capacity of %s.',name));
67 self.retrievalSystemQueueIndices = containers.Map(
'KeyType',
'int32',
'ValueType',
'any');
68 self.retrievalClassIndices = [];
69 self.retrievalRoutingEntries = {};
70 self.replacestrategy = replStrat;
71 %probHit = min(sum(itemLevelCap)/nitems,1.0); % initial estimate of hit probability
72 %self.setResultHitProb(probHit);
73 %self.setResultMissProb(1-probHit);
74 self.server = CacheClassSwitcher(
classes, nitems, itemLevelCap); % replace Server created by Queue
77 self.model.addNode(self);
83 elseif model.isJavaNative()
85 if nargin<6 || isempty(graph)
86 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat);
88 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat, graph);
90 self.index = model.obj.getNodeIndex(self.obj);
94 % function setMissTime(self, distribution)
95 % SETMISSTIME(DISTRIBUTION)
97 % itemclass = self.items;
98 % self.server.serviceProcess{1, itemclass.index} = {[], ServiceStrategy.SD, distribution};
101 % function setHitTime(self, distribution, level)
102 % SETHITTIME(DISTRIBUTION, LEVEL)
104 % itemclass = self.items;
105 %
if ~exist(
'level',
'var')
106 % levels = 2:self.nLevels;
111 % self.server.serviceProcess{1+level, itemclass.index} = {[], ServiceStrategy.SD, distribution};
115 function self = reset(self)
118 % Reset internal data structures when the network model
is
120 self.server.actualHitProb = sparse([]);
121 self.server.actualMissProb = sparse([]);
122 self.server.actualDelayedHitProb = sparse([]);
123 self.server.actualHitProbList = sparse([]);
124 self.server.actualItemProb = sparse([]);
125 self.server.actualResidT = sparse([]);
128 function self = setResultResidT(self, actualResidT)
129 self.server.actualResidT = actualResidT;
132 function p = getResidT(self)
133 p = full(self.server.actualResidT);
136 function tc = getTotalCacheCapacity(self)
137 tc = self.totalCacheCapacity;
140 function rc = getRetrievalSystemCapacity(self)
141 rc = self.retrievalSystemCapacity;
144 function m = getRetrievalClasses(self)
145 m = self.server.retrievalClasses;
148 function idx = getRetrievalClassIndices(self)
149 idx = self.retrievalClassIndices;
152 function q = getRetrievalSystemQueueIndicesFor(self, jobinClassIdx)
153 % q = getRetrievalSystemQueueIndicesFor(jobinClassIdx)
154 % Return node indices of the queues comprising the retrieval system
for the
155 % given (0-indexed) arrival
class, or []
if no retrieval system
is set.
156 if isKey(self.retrievalSystemQueueIndices, int32(jobinClassIdx))
157 q = self.retrievalSystemQueueIndices(int32(jobinClassIdx));
163 function setRetrievalClass(self, jobinClass, joboutClass, item)
164 % SETRETRIEVALCLASS(jobinClass, joboutClass, item)
165 % item
is 1-based (MATLAB convention).
166 self.server.retrievalClasses(item, jobinClass.index) = joboutClass.index;
169 function self = setResultHitProb(self, actualHitProb)
170 self.server.actualHitProb = actualHitProb;
173 function self = setResultMissProb(self, actualMissProb)
174 self.server.actualMissProb = actualMissProb;
177 function self = setResultDelayedHitProb(self, actualDelayedHitProb)
178 % SETRESULTDELAYEDHITPROB Per-
class delayed-hit fraction
179 % (requests arriving
for an item whose fetch
is already in
180 % progress in the retrieval system). Zero
for caches without a
182 self.server.actualDelayedHitProb = actualDelayedHitProb;
185 function p = getHitRatio(self)
186 % GETHITRATIO Actual (
true) hit fraction per class: the item
is
187 % resident in the cache. Delayed hits are reported separately by
188 % getDelayedHitRatio.
189 p = full(self.server.actualHitProb);
192 function p = getMissRatio(self)
193 p = full(self.server.actualMissProb);
196 function p = getDelayedHitRatio(self)
197 % GETDELAYEDHITRATIO Actual delayed-hit fraction per class
198 % (empty/zero when the cache has no retrieval system).
199 if isprop(self.server, 'actualDelayedHitProb')
200 p = full(self.server.actualDelayedHitProb);
206 function self = setResultHitProbList(self, actualHitProbList)
207 % SETRESULTHITPROBLIST Per-class, per-list (per-level) hit
208 % fraction matrix [
classes x lists]; rows sum to getHitRatio.
209 self.server.actualHitProbList = actualHitProbList;
212 function p = getHitRatioByList(self)
213 % GETHITRATIOBYLIST Per-class, per-list hit fraction matrix
214 % [
classes x lists]; empty when not computed by the solver.
215 if isprop(self.server, 'actualHitProbList')
216 p = full(self.server.actualHitProbList);
222 function self = setResultItemProb(self, actualItemProb)
223 % SETRESULTITEMPROB Per-item occupancy matrix [items x (lists+1)];
224 % column 1 = miss (item not cached), columns 2..end = per-list.
225 self.server.actualItemProb = actualItemProb;
228 function p = getItemProb(self)
229 % GETITEMPROB Per-item occupancy matrix [items x (lists+1)]: column 1
230 %
is the miss probability, columns 2..end the per-list probabilities;
231 % empty when not computed by the solver.
232 if isprop(self.server, 'actualItemProb')
233 p = full(self.server.actualItemProb);
239 function setHitClass(self, jobinclass, joboutclass)
240 % SETHITCLASS(JOBINCLASS, JOBOUTCLASS)
242 self.server.hitClass(jobinclass.index) = joboutclass.index;
245 function setMissClass(self, jobinclass, joboutclass)
246 % SETMISSCLASS(JOBINCLASS, JOBOUTCLASS)
248 self.server.missClass(jobinclass.index) = joboutclass.index;
252 function setRead(self,
jobclass, distribution)
253 % SETREAD(JOBCLASS, DISTRIBUTION)
255 itemclass = self.items;
256 if distribution.isDiscrete
257 self.popularity{itemclass.index,
jobclass.index} = distribution.copy;
258 if self.popularity{itemclass.index,
jobclass.index}.support(2) ~= itemclass.nitems
259 line_error(mfilename,sprintf(
'The reference model is defined on a number of items different from the ones used to instantiate %s.',self.name));
261 switch class(distribution)
263 self.popularity{itemclass.index,
jobclass.index}.setParam(2,
'n', itemclass.nitems);
265 % self.probselect(itemclass.index,
jobclass.index) = probselect;
267 line_error(mfilename,
'A discrete popularity distribution is required.');
271 function setReadItemEntry(self,
jobclass, popularity, cardinality)
272 % SETREAD(JOBCLASS, DISTRIBUTION)
274 if popularity.isDiscrete
276 self.popularity{
jobclass.index} = popularity.copy;
277 switch class(popularity)
279 self.popularity{
jobclass.index}.setParam(2,
'n', cardinality);
283 line_error(mfilename,
'A discrete popularity distribution is required.');
286 function setAccessProb(self, R)
293 function setProbRouting(self,
class, destination, probability)
294 % SETPROBROUTING(CLASS, DESTINATION, PROBABILITY)
296 setRouting(self,
class, RoutingStrategy.PROB, destination, probability);
299 function hitClass = getHitClass(self)
300 % HITCLASS = GETHITCLASS
302 % For an incoming job of
class r, HITCLASS(r)
is the new class
303 % of that job after a hit
305 hitClass = self.server.hitClass;
308 function missClass = getMissClass(self)
309 % MISSCLASS = GETMISSCLASS
311 % For an incoming job of
class r, MISSCLASS(r)
is the new class
312 % of that job after a miss
314 missClass = self.server.missClass;
317 function addRetrievalRoutingEntry(self, fromCls, toCls, srcNode, dstNode, prob)
318 % ADDRETRIEVALROUTINGENTRY(fromCls, toCls, srcNode, dstNode, prob)
319 % Register a routing edge
for an
auto-generated retrieval
class. link()
320 % injects all such entries into the routing matrix
P. Entries are 1-based
321 %
class indices and 1-based node indices; prob
is the routing probability.
322 % Later entries
override earlier ones
for the same (fromCls,toCls,src,dst).
326 self.retrievalRoutingEntries{end+1} = [fromCls, toCls, srcNode, dstNode, prob];
329 function setItemServiceRateAtQueue(self, jobinClass, item, queue, serviceRate)
330 % SETITEMSERVICERATEATQUEUE(jobinClass, item, queue, serviceRate)
331 % Set the service rate
for a single item at one of the queues that comprise
332 % the retrieval system
for read requests originating from jobinClass.
334 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
336 line_error(mfilename,
'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
338 rClass = self.model.classes{rClassIdx};
339 queue.setService(rClass, Exp(serviceRate));
342 function setItemQueueEntryProbability(self, jobinClass, item, queue, probability)
343 % SETITEMQUEUEENTRYPROBABILITY(jobinClass, item, queue, probability)
344 % Probability of routing the retrieval
class for `item` from the cache
345 % into `queue` upon entering the retrieval system.
346 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
348 line_error(mfilename,
'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
350 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, self.index, queue.index, probability);
353 function setItemQueueExitProbability(self, jobinClass, item, queue, probability)
354 % SETITEMQUEUEEXITPROBABILITY(jobinClass, item, queue, probability)
355 % Probability of routing the retrieval
class for `item` out of `queue`
356 % back to the cache (where the returning READ logs the miss).
357 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
359 line_error(mfilename,
'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
361 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, queue.index, self.index, probability);
364 function setItemRoutingProbability(self, jobinClass, item, sourceQueue, destQueue, probability)
365 % SETITEMROUTINGPROBABILITY(jobinClass, item, sourceQueue, destQueue, probability)
366 % Probability of routing the retrieval
class for `item` between two queues
367 % inside the retrieval system.
368 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
370 line_error(mfilename,
'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
372 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, sourceQueue.index, destQueue.index, probability);
375 function setRetrievalSystem(self, jobinClass, missClass, queues, serviceRates, routingMatrices)
376 % SETRETRIEVALSYSTEM(jobinClass, missClass, queues, serviceRates, routingMatrices)
378 % Initialise the retrieval system through which a request that misses the cache
379 %
is fetched. The request switches to a per-item retrieval
class that circulates
380 % the queues and returns to the cache, where the returning READ logs it as a
381 % miss (switching into `missClass`). getResidT reports the per-
class
382 % queueing time in the retrieval sub-network.
385 % jobinClass arrival JobClass that can route through the retrieval system
386 % missClass JobClass into which a completed retrieval transitions
387 % queues single Queue or
array/cell of Queue
nodes comprising the system
388 % serviceRates one of:
389 % - scalar: same rate at every queue
for every item
390 % - row vector [1 x nItems]: rate per item, duplicated across queues
391 % - matrix [nQueues x nItems]: rate per (queue, item)
392 % routingMatrices one of:
393 % - matrix [(nQueues+1) x (nQueues+1)] shared across all items
394 % - cell
array of nItems such matrices (one per item)
395 % Index nQueues+1 (last row/col) corresponds to entering/leaving
396 % the retrieval system to/from the cache node.
398 % --- normalise inputs ---
399 if isa(queues,
'Queue')
401 elseif iscell(queues)
403 elseif isnumeric(queues)
404 line_error(mfilename,
'queues must be a Queue or a cell/array of Queues.');
406 queueArr = num2cell(queues);
408 nQueues = numel(queueArr);
409 nItems = self.items.nitems;
410 self.retrievalSystemCapacity = nItems - self.totalCacheCapacity;
413 line_error(mfilename,
'Retrieval system cannot be initialised with no stations.');
416 % broadcast serviceRates
417 if isscalar(serviceRates)
418 serviceRateMat = serviceRates * ones(nQueues, nItems);
419 elseif isvector(serviceRates) && numel(serviceRates) == nItems
420 serviceRateMat = repmat(serviceRates(:).
', nQueues, 1);
421 elseif size(serviceRates,1) == nQueues && size(serviceRates,2) == nItems
422 serviceRateMat = serviceRates;
424 line_error(mfilename,'serviceRates must be a scalar, a [1 x nItems] row vector, or a [nQueues x nItems] matrix.
');
427 % broadcast routingMatrices
428 if nargin < 6 || isempty(routingMatrices)
429 empty = zeros(nQueues+1, nQueues+1);
430 routingMatrixCell = repmat({empty}, 1, nItems);
431 elseif iscell(routingMatrices)
432 if numel(routingMatrices) ~= nItems
433 line_error(mfilename,'There must exist a routing matrix for every item in the cache node.
');
435 routingMatrixCell = routingMatrices;
437 routingMatrixCell = repmat({routingMatrices}, 1, nItems);
440 if size(routingMatrixCell{i},1) ~= nQueues+1 || size(routingMatrixCell{i},2) ~= nQueues+1
441 line_error(mfilename,'Each routing matrix must be of shape [nQueues+1, nQueues+1].
');
445 % --- record queue node indices ---
446 queueIdxs = zeros(1, nQueues);
448 queueIdxs(q) = queueArr{q}.index;
450 self.retrievalSystemQueueIndices(int32(jobinClass.index-1)) = queueIdxs;
452 % --- create one retrieval class per item ---
453 retrievalList = cell(1, nItems);
455 if isa(jobinClass, 'ClosedClass
')
456 refStation = jobinClass.refstat;
457 retrievalList{i} = ClosedClass(self.model, [jobinClass.name '_retrievalClass_
' num2str(i)], 0, refStation, 0);
459 retrievalList{i} = OpenClass(self.model, [jobinClass.name '_retrievalClass_
' num2str(i)], 0);
461 self.retrievalClassIndices(end+1) = retrievalList{i}.index;
464 % --- register routing for each item's retrieval class ---
465 % The single retrieval class circulates through the queues and then
466 % routes back to the cache as itself; the returning READ reads item i
467 % (one-hot popularity) and switches it to the miss
class. These routes
468 % are recorded as deferred entries and injected into the routing matrix
469 %
P by link() (the
auto-generated retrieval
classes are not part of the
472 routingMatrix = routingMatrixCell{i};
473 rClass = retrievalList{i};
475 %
switch arrival jobinClass -> retrieval
class for item i
476 self.setRetrievalClass(jobinClass, rClass, i);
478 % the retrieval
class triggers a read of item i (one-hot popularity)
479 itemPopularity = zeros(1, nItems);
480 itemPopularity(i) = 1.0;
481 self.popularity{self.items.index, rClass.index} = DiscreteSampler(itemPopularity);
483 % on the returning READ the retrieval
is logged as a miss
484 self.setMissClass(rClass, missClass);
486 for sourceQueueIdx = 1:nQueues
487 sourceQueue = queueArr{sourceQueueIdx};
489 % Cache -> queue (begin retrieval): routingMatrix(nQueues+1, sourceQueueIdx)
490 self.addRetrievalRoutingEntry(rClass.index, rClass.index, ...
491 self.index, sourceQueue.index, routingMatrix(nQueues+1, sourceQueueIdx));
493 % service
for the retrieval
class at this queue
494 sourceQueue.setService(rClass, Exp(serviceRateMat(sourceQueueIdx, i)));
496 % at most one retrieval in flight at a time
for this class
497 if length(sourceQueue.classCap) < rClass.index
498 sourceQueue.classCap((length(sourceQueue.classCap)+1):rClass.index) = Inf;
500 sourceQueue.classCap(rClass.index) = 1;
502 % queue -> other queues (stays in the retrieval
class)
503 for destQueueIdx = 1:nQueues
504 self.addRetrievalRoutingEntry(rClass.index, rClass.index, ...
505 sourceQueue.index, queueArr{destQueueIdx}.index, ...
506 routingMatrix(sourceQueueIdx, destQueueIdx));
509 % queue -> cache (
return; the returning READ logs the miss)
510 self.addRetrievalRoutingEntry(rClass.index, rClass.index, ...
511 sourceQueue.index, self.index, routingMatrix(sourceQueueIdx, nQueues+1));