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
14 admissionProb; % q-LRU admission probability on a miss (1.0 = always admit)
21 totalCacheCapacity; % sum(itemLevelCap)
22 retrievalSystemCapacity; % 0 until setRetrievalSystem
is called; nitems-totalCacheCapacity otherwise
23 retrievalSystemQueueIndices; % containers.Map jobinClassIdx -> [queue node indices]
24 retrievalClassIndices; % set of indices of retrieval classes (used in afterEventCache READ)
25 retrievalRoutingEntries; % cell array of [fromCls,toCls,srcNode,dstNode,prob] routing tuples;
26 % link() injects these into
the routing matrix
P (
the auto-generated
27 % retrieval classes are not part of
the user-supplied
P).
32 function self = Cache(model, name, nitems, itemLevelCap, replStrat, graph)
33 % CACHE Create a Cache node instance
35 % @brief Creates a Cache node with configurable levels and replacement strategy
36 % @param model Network model to add
the cache to
37 % @param name String identifier for
the cache node
38 % @param nitems Total number of cacheable items
39 % @param itemLevelCap Vector specifying capacity of each cache level
40 % @param replStrat Replacement strategy (LRU, FIFO, Random, etc.)
41 % @param graph Optional graph structure for cache hierarchy
42 % @return self Cache instance configured for
the given model
44 % The constructor creates a multi-level cache with
the specified
45 % total items and per-level capacities. The replacement strategy
46 % determines how items are evicted when cache levels become full.
48 self@StatefulNode(name);
49 if model.isMatlabNative()
50 if ~exist('itemLevelCap','var')
53 classes = model.getClasses();
54 self.input = Buffer(classes);
55 self.output = Dispatcher(classes);
56 self.schedPolicy = SchedStrategyType.NP;
57 self.schedStrategy = SchedStrategy.FCFS;
58 self.items = ItemSet(model, [name,'_','Items'], nitems, self);
59 self.nLevels = nnz(itemLevelCap);
60 self.cap = Inf; % job capacity
62 self.itemLevelCap = itemLevelCap; % item capacity
63 self.totalCacheCapacity = sum(itemLevelCap);
64 self.retrievalSystemCapacity = 0; % no retrieval system by
default
65 if self.totalCacheCapacity > nitems
66 line_error(mfilename,sprintf(
'The number of items is smaller than the capacity of %s.',name));
68 self.retrievalSystemQueueIndices = containers.Map(
'KeyType',
'int32',
'ValueType',
'any');
69 self.retrievalClassIndices = [];
70 self.retrievalRoutingEntries = {};
71 self.replacestrategy = replStrat;
72 self.admissionProb = 1.0; %
default: always admit on a miss (overridden
for q-LRU)
73 %probHit = min(sum(itemLevelCap)/nitems,1.0); % initial estimate of hit probability
74 %self.setResultHitProb(probHit);
75 %self.setResultMissProb(1-probHit);
76 self.server = CacheClassSwitcher(classes, nitems, itemLevelCap); % replace Server created by Queue
79 self.model.addNode(self);
85 elseif model.isJavaNative()
87 if nargin<6 || isempty(graph)
88 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat);
90 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat, graph);
92 self.index = model.obj.getNodeIndex(self.obj);
96 % function setMissTime(self, distribution)
97 % SETMISSTIME(DISTRIBUTION)
99 % itemclass = self.items;
100 % self.server.serviceProcess{1, itemclass.index} = {[], ServiceStrategy.SD, distribution};
103 % function setHitTime(self, distribution, level)
104 % SETHITTIME(DISTRIBUTION, LEVEL)
106 % itemclass = self.items;
107 %
if ~exist(
'level',
'var')
108 % levels = 2:self.nLevels;
113 % self.server.serviceProcess{1+level, itemclass.index} = {[], ServiceStrategy.SD, distribution};
117 function self = reset(self)
120 % Reset internal data structures when
the network model
is
122 self.server.actualHitProb = sparse([]);
123 self.server.actualMissProb = sparse([]);
124 self.server.actualDelayedHitProb = sparse([]);
125 self.server.actualHitProbList = sparse([]);
126 self.server.actualItemProb = sparse([]);
127 self.server.actualResidT = sparse([]);
130 function self = setResultResidT(self, actualResidT)
131 self.server.actualResidT = actualResidT;
134 function p = getResidT(self)
135 p = full(self.server.actualResidT);
138 function tc = getTotalCacheCapacity(self)
139 tc = self.totalCacheCapacity;
142 function rc = getRetrievalSystemCapacity(self)
143 rc = self.retrievalSystemCapacity;
146 function m = getRetrievalClasses(self)
147 m = self.server.retrievalClasses;
150 function idx = getRetrievalClassIndices(self)
151 idx = self.retrievalClassIndices;
154 function q = getRetrievalSystemQueueIndicesFor(self, jobinClassIdx)
155 % q = getRetrievalSystemQueueIndicesFor(jobinClassIdx)
156 % Return node indices of
the queues comprising
the retrieval system
for the
157 % given (0-indexed) arrival
class, or []
if no retrieval system
is set.
158 if isKey(self.retrievalSystemQueueIndices, int32(jobinClassIdx))
159 q = self.retrievalSystemQueueIndices(int32(jobinClassIdx));
165 function setRetrievalClass(self, jobinClass, joboutClass, item)
166 % SETRETRIEVALCLASS(jobinClass, joboutClass, item)
167 % item
is 1-based (MATLAB convention).
168 self.server.retrievalClasses(item, jobinClass.index) = joboutClass.index;
171 function self = setResultHitProb(self, actualHitProb)
172 self.server.actualHitProb = actualHitProb;
175 function self = setResultMissProb(self, actualMissProb)
176 self.server.actualMissProb = actualMissProb;
179 function self = setResultDelayedHitProb(self, actualDelayedHitProb)
180 % SETRESULTDELAYEDHITPROB Per-
class delayed-hit fraction
181 % (requests arriving
for an item whose fetch
is already in
182 % progress in
the retrieval system). Zero
for caches without a
184 self.server.actualDelayedHitProb = actualDelayedHitProb;
187 function p = getHitRatio(self)
188 % GETHITRATIO Actual (
true) hit fraction per class:
the item
is
189 % resident in
the cache. Delayed hits are reported separately by
190 % getDelayedHitRatio.
191 p = full(self.server.actualHitProb);
194 function p = getMissRatio(self)
195 p = full(self.server.actualMissProb);
198 function p = getDelayedHitRatio(self)
199 % GETDELAYEDHITRATIO Actual delayed-hit fraction per class
200 % (empty/zero when
the cache has no retrieval system).
201 if isprop(self.server, 'actualDelayedHitProb')
202 p = full(self.server.actualDelayedHitProb);
208 function self = setResultHitProbList(self, actualHitProbList)
209 % SETRESULTHITPROBLIST Per-class, per-list (per-level) hit
210 % fraction matrix [classes x lists]; rows sum to getHitRatio.
211 self.server.actualHitProbList = actualHitProbList;
214 function p = getHitRatioByList(self)
215 % GETHITRATIOBYLIST Per-class, per-list hit fraction matrix
216 % [classes x lists]; empty when not computed by
the solver.
217 if isprop(self.server, 'actualHitProbList')
218 p = full(self.server.actualHitProbList);
224 function self = setResultItemProb(self, actualItemProb)
225 % SETRESULTITEMPROB Per-item occupancy matrix [items x (lists+1)];
226 %
column 1 = miss (item not cached), columns 2..end = per-list.
227 self.server.actualItemProb = actualItemProb;
230 function p = getItemProb(self)
231 % GETITEMPROB Per-item occupancy matrix [items x (lists+1)]:
column 1
232 %
is the miss probability, columns 2..end
the per-list probabilities;
233 % empty when not computed by
the solver.
234 if isprop(self.server, 'actualItemProb')
235 p = full(self.server.actualItemProb);
241 function setHitClass(self, jobinclass, joboutclass)
242 % SETHITCLASS(JOBINCLASS, JOBOUTCLASS)
244 self.server.hitClass(jobinclass.index) = joboutclass.index;
247 function setMissClass(self, jobinclass, joboutclass)
248 % SETMISSCLASS(JOBINCLASS, JOBOUTCLASS)
250 self.server.missClass(jobinclass.index) = joboutclass.index;
254 function setRead(self,
jobclass, distribution)
255 % SETREAD(JOBCLASS, DISTRIBUTION)
257 itemclass = self.items;
258 if distribution.isDiscrete
259 self.popularity{itemclass.index,
jobclass.index} = distribution.copy;
260 if self.popularity{itemclass.index,
jobclass.index}.support(2) ~= itemclass.nitems
261 line_error(mfilename,sprintf(
'The reference model is defined on a number of items different from the ones used to instantiate %s.',self.name));
263 switch class(distribution)
265 self.popularity{itemclass.index,
jobclass.index}.setParam(2,
'n', itemclass.nitems);
267 % self.probselect(itemclass.index,
jobclass.index) = probselect;
269 line_error(mfilename,
'A discrete popularity distribution is required.');
273 function setReadItemEntry(self,
jobclass, popularity, cardinality)
274 % SETREAD(JOBCLASS, DISTRIBUTION)
276 if popularity.isDiscrete
278 self.popularity{
jobclass.index} = popularity.copy;
279 switch class(popularity)
281 self.popularity{
jobclass.index}.setParam(2,
'n', cardinality);
285 line_error(mfilename,
'A discrete popularity distribution is required.');
288 function setAccessProb(self, R)
294 function setAdmissionProb(self, q)
295 % SETADMISSIONPROB(Q)
296 % Probability q in [0,1] of admitting a missed item into
the cache
297 % (q-LRU). Only used when
the replacement strategy
is QLRU.
299 line_error(mfilename,
'The admission probability q must lie in [0,1].');
301 self.admissionProb = q;
305 function setProbRouting(self,
class, destination, probability)
306 % SETPROBROUTING(CLASS, DESTINATION, PROBABILITY)
308 setRouting(self,
class, RoutingStrategy.PROB, destination, probability);
311 function hitClass = getHitClass(self)
312 % HITCLASS = GETHITCLASS
314 % For an incoming job of
class r, HITCLASS(r)
is the new class
315 % of that job after a hit
317 hitClass = self.server.hitClass;
320 function missClass = getMissClass(self)
321 % MISSCLASS = GETMISSCLASS
323 % For an incoming job of
class r, MISSCLASS(r)
is the new class
324 % of that job after a miss
326 missClass = self.server.missClass;
329 function addRetrievalRoutingEntry(self, fromCls, toCls, srcNode, dstNode, prob, allowZero)
330 % ADDRETRIEVALROUTINGENTRY(fromCls, toCls, srcNode, dstNode, prob, allowZero)
331 % Register a routing edge
for an
auto-generated retrieval
class. link()
332 % injects all such entries into
the routing matrix
P. Entries are 1-based
333 %
class indices and 1-based node indices; prob
is the routing probability.
334 % Later entries
override earlier ones
for the same (fromCls,toCls,src,dst).
335 % With allowZero=
true an
explicit prob==0 entry
is recorded so it can
336 %
override (
delete) a
default edge inherited from
the read
class;
the
337 % internal broadcast path keeps allowZero=
false and drops zero edges.
341 if prob < 0 || (prob == 0 && ~allowZero)
344 self.retrievalRoutingEntries{end+1} = [fromCls, toCls, srcNode, dstNode, prob];
347 function setItemRoutingProbability(self, jobinClass, item, source, dest, probability)
348 % SETITEMROUTINGPROBABILITY(jobinClass, item, source, dest, probability)
349 % Probability of routing
the retrieval
class for `item` between two
nodes of
350 %
the retrieval system. `source`/`dest` are either a retrieval queue or
the
351 % cache itself: pass
the cache as `source`
for a cache->queue entry, or as
352 % `dest`
for a queue->cache exit.
353 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
355 line_error(mfilename,
'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
357 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, source.index, dest.index, probability,
true);
360 function setItemRoutingProb(self, jobinClass, item, source, dest, probability)
361 % Short alias
for setItemRoutingProbability.
362 self.setItemRoutingProbability(jobinClass, item, source, dest, probability);
365 function setRetrievalSystem(self, jobinClass, missClass, queues)
366 % SETRETRIEVALSYSTEM(jobinClass, missClass, queues)
368 % Initialise
the retrieval system through which a request that misses
the cache
369 %
is fetched. The request switches to a per-item retrieval
class that circulates
370 %
the queues and returns to
the cache, where
the returning READ logs it as a
371 % miss (switching into `missClass`). getResidT reports
the per-
class
372 % queueing time in
the retrieval sub-network.
375 % jobinClass arrival JobClass that can route through
the retrieval system
376 % missClass JobClass into which a completed retrieval transitions
377 % queues single Queue or array/cell of Queue
nodes comprising
the system
379 % Routing and service are NOT passed here; they are taken from
the read
class:
380 % - service:
the read
class's service distribution at each queue. Call
381 % queue.setService(jobinClass, ...) beforehand; override per item with
382 % queue.setItemServiceRate(cache, jobinClass, item, rate).
383 % - routing: the read class's routing among
the retrieval queues drawn in
the
384 % top-level routing matrix
P;
override per item with
385 % setItemQueueEntryProbability / setItemRoutingProbability / setItemQueueExitProbability.
387 % --- normalise inputs ---
388 if isa(queues,
'Queue')
390 elseif iscell(queues)
392 elseif isnumeric(queues)
393 line_error(mfilename,
'queues must be a Queue or a cell/array of Queues.');
395 queueArr = num2cell(queues);
397 nQueues = numel(queueArr);
398 nItems = self.items.nitems;
399 self.retrievalSystemCapacity = nItems - self.totalCacheCapacity;
402 line_error(mfilename,
'Retrieval system cannot be initialised with no stations.');
405 % inherit
the read
class's service distribution at each queue as the per-item default
406 serviceDistByQueue = cell(1, nQueues);
408 sp = queueArr{q}.serviceProcess;
409 if numel(sp) >= jobinClass.index && ~isempty(sp{jobinClass.index})
410 serviceDistByQueue{q} = sp{jobinClass.index};
412 line_error(mfilename, sprintf(['No service distribution
for the read
class at queue
"%s";
' ...
413 'call queue.setService(readClass, ...) before setRetrievalSystem.
'], queueArr{q}.name));
417 % --- record queue node indices ---
418 queueIdxs = zeros(1, nQueues);
420 queueIdxs(q) = queueArr{q}.index;
422 self.retrievalSystemQueueIndices(int32(jobinClass.index-1)) = queueIdxs;
424 % --- create one retrieval class per item ---
425 retrievalList = cell(1, nItems);
427 if isa(jobinClass, 'ClosedClass
')
428 refStation = jobinClass.refstat;
429 retrievalList{i} = ClosedClass(self.model, [jobinClass.name '_retrievalClass_
' num2str(i)], 0, refStation, 0);
431 retrievalList{i} = OpenClass(self.model, [jobinClass.name '_retrievalClass_
' num2str(i)], 0);
433 self.retrievalClassIndices(end+1) = retrievalList{i}.index;
436 % --- per-item retrieval class setup ---
437 % Each item's retrieval
class reads item i (one-hot popularity) and, on
the
438 % returning READ,
is logged as a miss. Its routing through
the queues
is NOT set
439 % here: it
is inherited at link() from
the read class's routing in
P, overridable
440 % per item via
the setItem* methods.
442 rClass = retrievalList{i};
444 %
switch arrival jobinClass -> retrieval
class for item i
445 self.setRetrievalClass(jobinClass, rClass, i);
447 %
the retrieval
class triggers a read of item i (one-hot popularity)
448 itemPopularity = zeros(1, nItems);
449 itemPopularity(i) = 1.0;
450 self.popularity{self.items.index, rClass.index} = DiscreteSampler(itemPopularity);
452 % on
the returning READ
the retrieval
is logged as a miss
453 self.setMissClass(rClass, missClass);
455 for sourceQueueIdx = 1:nQueues
456 sourceQueue = queueArr{sourceQueueIdx};
458 % service
for the retrieval
class at this queue (inherited read-class service)
459 sourceQueue.setService(rClass, serviceDistByQueue{sourceQueueIdx}.copy());
461 % at most one retrieval in flight at a time
for this class
462 if length(sourceQueue.classCap) < rClass.index
463 sourceQueue.classCap((length(sourceQueue.classCap)+1):rClass.index) = Inf;
465 sourceQueue.classCap(rClass.index) = 1;