LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Cache.m
1classdef Cache < StatefulNode
2 % Multi-level cache node with hit/miss class switching
3 %
4 % Models cache memory systems with multiple levels and replacement strategies.
5 %
6 % Copyright (c) 2012-2026, Imperial College London
7 % All rights reserved.
8
9 properties
10 cap;
11 schedPolicy;
12 schedStrategy;
13 replacestrategy;
14 popularity;
15 nLevels;
16 itemLevelCap;
17 items;
18 accessProb;
19 graph;
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).
27 end
28
29 methods
30 %Constructor
31 function self = Cache(model, name, nitems, itemLevelCap, replStrat, graph)
32 % CACHE Create a Cache node instance
33 %
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
42 %
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.
46
47 self@StatefulNode(name);
48 if model.isMatlabNative()
49 if ~exist('itemLevelCap','var')
50 levels = 1;
51 end
52 classes = model.getClasses();
53 self.input = Buffer(classes);
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
60 self.accessProb = {};
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));
66 end
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
75 self.popularity = {};
76 self.setModel(model);
77 self.model.addNode(self);
78 if nargin<6
79 self.graph = [];
80 else
81 self.graph = graph;
82 end
83 elseif model.isJavaNative()
84 self.setModel(model);
85 if nargin<6 || isempty(graph)
86 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat);
87 else
88 self.obj = jline.lang.nodes.Cache(model.obj, name, nitems, itemLevelCap, replStrat, graph);
89 end
90 self.index = model.obj.getNodeIndex(self.obj);
91 end
92 end
93
94 % function setMissTime(self, distribution)
95 % SETMISSTIME(DISTRIBUTION)
96
97 % itemclass = self.items;
98 % self.server.serviceProcess{1, itemclass.index} = {[], ServiceStrategy.SD, distribution};
99 % end
100 %
101 % function setHitTime(self, distribution, level)
102 % SETHITTIME(DISTRIBUTION, LEVEL)
103
104 % itemclass = self.items;
105 % if ~exist('level','var')
106 % levels = 2:self.nLevels;
107 % else
108 % levels = level;
109 % end
110 % for level = levels
111 % self.server.serviceProcess{1+level, itemclass.index} = {[], ServiceStrategy.SD, distribution};
112 % end
113 % end
114
115 function self = reset(self)
116 % SELF = RESET()
117 %
118 % Reset internal data structures when the network model is
119 % reset
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([]);
126 end
127
128 function self = setResultResidT(self, actualResidT)
129 self.server.actualResidT = actualResidT;
130 end
131
132 function p = getResidT(self)
133 p = full(self.server.actualResidT);
134 end
135
136 function tc = getTotalCacheCapacity(self)
137 tc = self.totalCacheCapacity;
138 end
139
140 function rc = getRetrievalSystemCapacity(self)
141 rc = self.retrievalSystemCapacity;
142 end
143
144 function m = getRetrievalClasses(self)
145 m = self.server.retrievalClasses;
146 end
147
148 function idx = getRetrievalClassIndices(self)
149 idx = self.retrievalClassIndices;
150 end
151
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));
158 else
159 q = [];
160 end
161 end
162
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;
167 end
168
169 function self = setResultHitProb(self, actualHitProb)
170 self.server.actualHitProb = actualHitProb;
171 end
172
173 function self = setResultMissProb(self, actualMissProb)
174 self.server.actualMissProb = actualMissProb;
175 end
176
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
181 % retrieval system.
182 self.server.actualDelayedHitProb = actualDelayedHitProb;
183 end
184
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);
190 end
191
192 function p = getMissRatio(self)
193 p = full(self.server.actualMissProb);
194 end
195
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);
201 else
202 p = [];
203 end
204 end
205
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;
210 end
211
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);
217 else
218 p = [];
219 end
220 end
221
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;
226 end
227
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);
234 else
235 p = [];
236 end
237 end
238
239 function setHitClass(self, jobinclass, joboutclass)
240 % SETHITCLASS(JOBINCLASS, JOBOUTCLASS)
241
242 self.server.hitClass(jobinclass.index) = joboutclass.index;
243 end
244
245 function setMissClass(self, jobinclass, joboutclass)
246 % SETMISSCLASS(JOBINCLASS, JOBOUTCLASS)
247
248 self.server.missClass(jobinclass.index) = joboutclass.index;
249 end
250
251
252 function setRead(self, jobclass, distribution)
253 % SETREAD(JOBCLASS, DISTRIBUTION)
254
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));
260 end
261 switch class(distribution)
262 case 'Zipf'
263 self.popularity{itemclass.index, jobclass.index}.setParam(2, 'n', itemclass.nitems);
264 end
265 % self.probselect(itemclass.index, jobclass.index) = probselect;
266 else
267 line_error(mfilename,'A discrete popularity distribution is required.');
268 end
269 end
270
271 function setReadItemEntry(self, jobclass, popularity, cardinality)
272 % SETREAD(JOBCLASS, DISTRIBUTION)
273
274 if popularity.isDiscrete
275
276 self.popularity{jobclass.index} = popularity.copy;
277 switch class(popularity)
278 case 'Zipf'
279 self.popularity{jobclass.index}.setParam(2, 'n', cardinality);
280 end
281
282 else
283 line_error(mfilename,'A discrete popularity distribution is required.');
284 end
285 end
286 function setAccessProb(self, R)
287 % SETACCESSCOSTS(R)
288
289 self.accessProb = R;
290 end
291
292
293 function setProbRouting(self, class, destination, probability)
294 % SETPROBROUTING(CLASS, DESTINATION, PROBABILITY)
295
296 setRouting(self, class, RoutingStrategy.PROB, destination, probability);
297 end
298
299 function hitClass = getHitClass(self)
300 % HITCLASS = GETHITCLASS
301 %
302 % For an incoming job of class r, HITCLASS(r) is the new class
303 % of that job after a hit
304
305 hitClass = self.server.hitClass;
306 end
307
308 function missClass = getMissClass(self)
309 % MISSCLASS = GETMISSCLASS
310 %
311 % For an incoming job of class r, MISSCLASS(r) is the new class
312 % of that job after a miss
313
314 missClass = self.server.missClass;
315 end
316
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).
323 if prob <= 0
324 return
325 end
326 self.retrievalRoutingEntries{end+1} = [fromCls, toCls, srcNode, dstNode, prob];
327 end
328
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.
333 % item is 1-based.
334 rClassIdx = self.server.retrievalClasses(item, jobinClass.index);
335 if rClassIdx <= 0
336 line_error(mfilename,'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
337 end
338 rClass = self.model.classes{rClassIdx};
339 queue.setService(rClass, Exp(serviceRate));
340 end
341
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);
347 if rClassIdx <= 0
348 line_error(mfilename,'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
349 end
350 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, self.index, queue.index, probability);
351 end
352
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);
358 if rClassIdx <= 0
359 line_error(mfilename,'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
360 end
361 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, queue.index, self.index, probability);
362 end
363
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);
369 if rClassIdx <= 0
370 line_error(mfilename,'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
371 end
372 self.addRetrievalRoutingEntry(rClassIdx, rClassIdx, sourceQueue.index, destQueue.index, probability);
373 end
374
375 function setRetrievalSystem(self, jobinClass, missClass, queues, serviceRates, routingMatrices)
376 % SETRETRIEVALSYSTEM(jobinClass, missClass, queues, serviceRates, routingMatrices)
377 %
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.
383 %
384 % Arguments:
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.
397
398 % --- normalise inputs ---
399 if isa(queues, 'Queue')
400 queueArr = {queues};
401 elseif iscell(queues)
402 queueArr = queues;
403 elseif isnumeric(queues)
404 line_error(mfilename,'queues must be a Queue or a cell/array of Queues.');
405 else
406 queueArr = num2cell(queues);
407 end
408 nQueues = numel(queueArr);
409 nItems = self.items.nitems;
410 self.retrievalSystemCapacity = nItems - self.totalCacheCapacity;
411
412 if nQueues == 0
413 line_error(mfilename,'Retrieval system cannot be initialised with no stations.');
414 end
415
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;
423 else
424 line_error(mfilename,'serviceRates must be a scalar, a [1 x nItems] row vector, or a [nQueues x nItems] matrix.');
425 end
426
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.');
434 end
435 routingMatrixCell = routingMatrices;
436 else
437 routingMatrixCell = repmat({routingMatrices}, 1, nItems);
438 end
439 for i = 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].');
442 end
443 end
444
445 % --- record queue node indices ---
446 queueIdxs = zeros(1, nQueues);
447 for q = 1:nQueues
448 queueIdxs(q) = queueArr{q}.index;
449 end
450 self.retrievalSystemQueueIndices(int32(jobinClass.index-1)) = queueIdxs;
451
452 % --- create one retrieval class per item ---
453 retrievalList = cell(1, nItems);
454 for i = 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);
458 else
459 retrievalList{i} = OpenClass(self.model, [jobinClass.name '_retrievalClass_' num2str(i)], 0);
460 end
461 self.retrievalClassIndices(end+1) = retrievalList{i}.index;
462 end
463
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
470 % user-supplied P).
471 for i = 1:nItems
472 routingMatrix = routingMatrixCell{i};
473 rClass = retrievalList{i};
474
475 % switch arrival jobinClass -> retrieval class for item i
476 self.setRetrievalClass(jobinClass, rClass, i);
477
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);
482
483 % on the returning READ the retrieval is logged as a miss
484 self.setMissClass(rClass, missClass);
485
486 for sourceQueueIdx = 1:nQueues
487 sourceQueue = queueArr{sourceQueueIdx};
488
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));
492
493 % service for the retrieval class at this queue
494 sourceQueue.setService(rClass, Exp(serviceRateMat(sourceQueueIdx, i)));
495
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;
499 end
500 sourceQueue.classCap(rClass.index) = 1;
501
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));
507 end
508
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));
512 end
513 end
514 end
515 end
516end
Definition mmt.m:124