LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
updatePopulations.m
1function updatePopulations(self, it)
2% UPDATEPOPULATIONS Apply interlock correction to call residence times
3%
4% Uses LQNS V5-style interlock analysis: computes interlock probability
5% for each (client, server) pair and reduces the waiting time component
6% of call residence times proportionally.
7%
8% LQNS applies interlock as arrival-rate scaling inside MVA:
9% L_k = (1 - prIL_k) * X_k * R_k
10% affecting only waiting time, not utilization. LINE approximates this
11% by adjusting callresidt post-MVA to remove interlocked waiting:
12% R_adj = S + (1 - prIL) * W
13% where S = service time, W = waiting time = R - S.
14%
15% This function is called AFTER updateMetrics (which computes raw
16% callresidt from layer MVA results) and BEFORE updateThinkTimes.
17
18lqn = self.lqn;
19
20% Save originals for proportional entry_servt update
21callresidt_orig = self.callresidt(:);
22residt_orig = self.residt(:);
23adjusted = false;
24
25% For each sync call, check if destination server has interlock
26for cidx = 1:lqn.ncalls
27 if lqn.calltype(cidx) ~= CallType.SYNC
28 continue;
29 end
30
31 dst_eidx = lqn.callpair(cidx, 2);
32 server_tidx = lqn.parent(dst_eidx);
33
34 % Find the server entity with interlock data
35 server_for_il = [];
36 if server_tidx <= length(self.il_common_entries) && ~isempty(self.il_common_entries{server_tidx})
37 server_for_il = server_tidx;
38 else
39 % Check host server
40 if server_tidx > lqn.tshift
41 host_idx = lqn.parent(server_tidx);
42 if host_idx >= 1 && host_idx <= length(self.il_common_entries) && ~isempty(self.il_common_entries{host_idx})
43 server_for_il = host_idx;
44 end
45 end
46 end
47 if isempty(server_for_il)
48 continue;
49 end
50
51 % Get client task (activity -> task via parent)
52 src_aidx = lqn.callpair(cidx, 1);
53 client_tidx = lqn.parent(src_aidx);
54
55 % Compute prIL using interlockedFlow formula
56 prIL = computeInterlockProb(self, lqn, client_tidx, server_for_il);
57
58 if prIL <= GlobalConstants.FineTol
59 continue;
60 end
61
62 % Compute waiting time reduction
63 S = self.servt(dst_eidx); % service time at destination entry
64 call_mean = lqn.callproc_mean(cidx);
65
66 if call_mean <= 0 || self.callservt(cidx) <= 0
67 continue;
68 end
69
70 RN = self.callservt(cidx) / call_mean; % response time per visit
71 W = max(0, RN - S); % waiting time per visit
72
73 if W > GlobalConstants.FineTol
74 RN_adj = S + (1 - prIL) * W;
75 scale = RN_adj / RN;
76 self.callservt(cidx) = self.callservt(cidx) * scale;
77 self.callresidt(cidx) = self.callresidt(cidx) * scale;
78 if self.callservt(cidx) > 0
79 self.callservtproc{cidx} = Exp.fitMean(self.callservt(cidx));
80 end
81 adjusted = true;
82 end
83end
84
85% Pass 2: Host-level interlock — reduce processor queueing in residt
86for h = 1:lqn.nhosts
87 hidx = h;
88 if isempty(self.il_common_entries{hidx})
89 continue;
90 end
91
92 % Compute prIL and processor utilization for each task on this host
93 host_tasks = lqn.tasksof{hidx}(:)';
94 task_prIL = zeros(length(host_tasks), 1);
95 task_util = zeros(length(host_tasks), 1);
96 for ti = 1:length(host_tasks)
97 tidx = host_tasks(ti);
98 task_prIL(ti) = computeInterlockProb(self, lqn, tidx, hidx);
99 % Compute task's processor utilization
100 for eidx = lqn.entriesof{tidx}(:)'
101 for aidx = lqn.actsof{eidx}(:)'
102 task_util(ti) = task_util(ti) + self.tput(aidx) * lqn.hostdem_mean(aidx);
103 end
104 end
105 end
106
107 U_total = sum(task_util);
108 U_interlocked = sum(task_util(task_prIL > GlobalConstants.FineTol));
109 if U_total <= GlobalConstants.FineTol || U_interlocked <= GlobalConstants.FineTol
110 continue;
111 end
112 il_fraction = U_interlocked / U_total;
113
114 for ti = 1:length(host_tasks)
115 if task_prIL(ti) <= GlobalConstants.FineTol
116 continue;
117 end
118 tidx = host_tasks(ti);
119 % Scale prIL by fraction of utilization that is interlocked
120 effective_prIL = task_prIL(ti) * il_fraction;
121 for eidx = lqn.entriesof{tidx}(:)'
122 for aidx = lqn.actsof{eidx}(:)'
123 D = lqn.hostdem_mean(aidx);
124 if D > 0 && self.residt(aidx) > D + GlobalConstants.FineTol
125 W_proc = self.residt(aidx) - D;
126 self.residt(aidx) = D + (1 - effective_prIL) * W_proc;
127 adjusted = true;
128 end
129 end
130 end
131 end
132end
133
134if ~adjusted
135 return;
136end
137
138% Recompute entry service times from adjusted callresidt/residt
139% Use proportional scaling to preserve visit ratio adjustments
140% applied in updateMetricsDefault
141entry_servt_old = self.servtmatrix * [residt_orig; callresidt_orig];
142entry_servt_new = self.servtmatrix * [self.residt; self.callresidt(:)];
143
144for eidx = (lqn.eshift+1):(lqn.eshift+lqn.nentries)
145 if entry_servt_old(eidx) > GlobalConstants.FineTol
146 ratio = entry_servt_new(eidx) / entry_servt_old(eidx);
147 self.servt(eidx) = self.servt(eidx) * ratio;
148 self.residt(eidx) = self.residt(eidx) * ratio;
149 if self.servt(eidx) > 0
150 self.servtproc{eidx} = Exp.fitMean(self.servt(eidx));
151 end
152 end
153end
154end
155
156%% Compute interlock probability for a (client, server) pair
157function prIL = computeInterlockProb(self, lqn, client_tidx, server_idx)
158commonEntries = self.il_common_entries{server_idx};
159numSources = self.il_num_sources(server_idx);
160allSrcTasks = self.il_source_tasks_all{server_idx};
161ph2SrcTasks = self.il_source_tasks_ph2{server_idx};
162
163if numSources == 0 || isempty(commonEntries)
164 prIL = 0;
165 return;
166end
167
168% Get client entries
169client_entries = lqn.entriesof{client_tidx};
170
171% Compute interlocked flow (LQNS interlockedFlow formula)
172sum_flow = 0;
173for ce_eidx = commonEntries(:)'
174 srcTask = lqn.parent(ce_eidx);
175 ce_num = ce_eidx - lqn.eshift;
176
177 for dstA_eidx = client_entries(:)'
178 dstA_num = dstA_eidx - lqn.eshift;
179
180 if dstA_num < 1 || dstA_num > lqn.nentries
181 continue;
182 end
183 if self.il_table_all(ce_num, dstA_num) <= 0
184 continue;
185 end
186
187 % Get source entry throughput
188 ce_tput = getEntryTput(self, lqn, ce_eidx, srcTask);
189
190 if ce_tput <= GlobalConstants.FineTol
191 continue;
192 end
193
194 % Check maxPhase for the source entry
195 hasP2 = hasPhase2Check(lqn, ce_eidx);
196
197 if ~hasP2 && ismember(srcTask, allSrcTasks)
198 sum_flow = sum_flow + ce_tput * self.il_table_all(ce_num, dstA_num);
199 elseif hasP2 && ismember(srcTask, allSrcTasks)
200 sum_flow = sum_flow + ce_tput * self.il_table_ph1(ce_num, dstA_num);
201 end
202
203 ph2 = self.il_table_all(ce_num, dstA_num) - self.il_table_ph1(ce_num, dstA_num);
204 if ph2 > 0 && ismember(srcTask, ph2SrcTasks)
205 sum_flow = sum_flow + ce_tput * ph2;
206 end
207 end
208end
209
210% Get client throughput
211client_tput = getTaskTput(self, lqn, client_tidx);
212
213if client_tput <= GlobalConstants.FineTol
214 prIL = 0;
215 return;
216end
217
218client_threads = lqn.mult(client_tidx);
219IL = min(sum_flow, client_tput) / (client_tput * client_threads * numSources);
220prIL = IL / lqn.mult(server_idx);
221prIL = min(prIL, 1.0);
222end
223
224%% Helper: get entry throughput
225function tput = getEntryTput(self, lqn, eidx, taskIdx)
226tput = self.tput(eidx);
227if tput <= GlobalConstants.FineTol
228 % Try first activity
229 acts = lqn.actsof{eidx};
230 if ~isempty(acts)
231 tput = self.tput(acts(1));
232 end
233end
234if tput <= GlobalConstants.FineTol
235 tput = self.tput(taskIdx);
236end
237end
238
239%% Helper: get task throughput
240function tput = getTaskTput(self, lqn, tidx)
241tput = self.tput(tidx);
242if tput <= GlobalConstants.FineTol
243 for eidx = lqn.entriesof{tidx}(:)'
244 et = self.tput(eidx);
245 if et <= GlobalConstants.FineTol
246 acts = lqn.actsof{eidx};
247 if ~isempty(acts)
248 et = self.tput(acts(1));
249 end
250 end
251 tput = tput + et;
252 end
253end
254end
255
256%% Helper: check if entry has phase-2 activities
257function result = hasPhase2Check(lqn, eidx)
258result = false;
259if ~isfield(lqn, 'actphase')
260 return;
261end
262for aidx = lqn.actsof{eidx}(:)'
263 a = aidx - lqn.ashift;
264 if a > 0 && a <= lqn.nacts && lqn.actphase(a) > 1
265 result = true;
266 return;
267 end
268end
269end