Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/tsc/grouping.rs
5394 views
1
// Copyright 2022 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::cmp::Ordering;
6
use std::cmp::Reverse;
7
use std::ops::Sub;
8
use std::time::Duration;
9
10
use anyhow::anyhow;
11
use anyhow::bail;
12
use anyhow::Result;
13
use base::warn;
14
15
fn abs_diff<T: Sub<Output = T> + Ord>(x: T, y: T) -> T {
16
if x < y {
17
y - x
18
} else {
19
x - y
20
}
21
}
22
23
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
24
pub struct CoreOffset {
25
pub core: usize,
26
pub offset: i128,
27
}
28
29
impl Ord for CoreOffset {
30
// CoreOffsets are ordered by offset ascending, then by their core number ascending
31
fn cmp(&self, other: &Self) -> Ordering {
32
(self.offset, self.core).cmp(&(other.offset, other.core))
33
}
34
}
35
36
impl PartialOrd for CoreOffset {
37
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
38
Some(self.cmp(other))
39
}
40
}
41
42
#[derive(Default, Debug, Clone, Eq, PartialEq)]
43
pub struct CoreGroup {
44
pub cores: Vec<CoreOffset>,
45
}
46
47
impl CoreGroup {
48
fn size(&self) -> usize {
49
self.cores.len()
50
}
51
52
fn add(&self, core: CoreOffset, limit: u128) -> Result<Self> {
53
let diff_from_min = abs_diff(self.cores.iter().min().unwrap().offset, core.offset) as u128;
54
let diff_from_max = abs_diff(self.cores.iter().max().unwrap().offset, core.offset) as u128;
55
let can_add = diff_from_min < limit && diff_from_max < limit;
56
57
if can_add {
58
let mut new = self.clone();
59
new.cores.push(core);
60
Ok(new)
61
} else {
62
Err(anyhow!(
63
"offset {} not within {} of all members of core group",
64
core.offset,
65
limit
66
))
67
}
68
}
69
}
70
71
#[derive(Default, Debug, Clone, Eq, PartialEq)]
72
pub struct CoreGrouping {
73
groups: Vec<CoreGroup>,
74
}
75
76
impl Ord for CoreGrouping {
77
fn cmp(&self, other: &Self) -> Ordering {
78
// Ordered by largest group size descending, then by number of groups ascending
79
(Reverse(self.largest_group().size()), self.groups.len())
80
.cmp(&(Reverse(other.largest_group().size()), other.groups.len()))
81
}
82
}
83
84
impl PartialOrd for CoreGrouping {
85
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
86
Some(self.cmp(other))
87
}
88
}
89
90
impl CoreGrouping {
91
pub(super) fn new(groups: Vec<CoreGroup>) -> Result<Self> {
92
// Other functions in this type rely on the fact that there is at least one CoreGroup.
93
if groups.is_empty() {
94
return Err(anyhow!("Cannot create an empty CoreGrouping."));
95
}
96
Ok(CoreGrouping { groups })
97
}
98
99
pub fn size(&self) -> usize {
100
self.groups.len()
101
}
102
103
pub fn largest_group_index(&self) -> usize {
104
// Sort by Reverse(size) then group index, and take the min.
105
// We could sort by size then Reverse(index), but then getting the index out is hard.
106
self.groups
107
.iter()
108
.enumerate()
109
.map(|(i, g)| (Reverse(g.size()), i))
110
.min()
111
.unwrap_or((Reverse(0), 0))
112
.1
113
}
114
115
pub fn largest_group(&self) -> &CoreGroup {
116
&self.groups[self.largest_group_index()]
117
}
118
119
pub fn core_grouping_bitmask(&self) -> u64 {
120
// If there's only one group, then all cores are in sync
121
if self.size() == 0 {
122
return 0;
123
}
124
125
let mut bitmask = 0u64;
126
let largest_group_index = self.largest_group_index();
127
for (i, group) in self.groups.iter().enumerate() {
128
// The largest group is considered the in-sync group
129
if i == largest_group_index {
130
continue;
131
}
132
133
// Set the bitmask to 1 for all cores in all other groups
134
for core in &group.cores {
135
if core.core > 63 {
136
warn!("Core grouping bitmask cannot contain core {}", core.core);
137
continue;
138
}
139
bitmask |= 1 << core.core;
140
}
141
}
142
143
bitmask
144
}
145
146
fn add_group(&mut self, group: CoreGroup) {
147
self.groups.push(group)
148
}
149
150
fn add_core_to_last_group(&self, core: CoreOffset, in_sync_threshold: u128) -> Option<Self> {
151
let last_group = self.groups.len() - 1;
152
self.groups[last_group]
153
.add(core, in_sync_threshold)
154
.map(|new_group| {
155
let mut new_grouping = self.clone();
156
new_grouping.groups[last_group] = new_group;
157
new_grouping
158
})
159
.ok()
160
}
161
}
162
163
/// Group cores by their offsets that are within `in_sync_threshold` of each other.
164
///
165
/// This uses a generic integer grouping algorithm. Because we're grouping within a threshold,
166
/// there are potentially multiple ways we can group the integers, and we want to find the "best"
167
/// grouping. Our definition of best grouping is:
168
/// 1. The grouping who's largest group is the largest.
169
/// 2. If there are multiple groupings with the same size largest group, take the one with the
170
/// fewest groups.
171
///
172
/// We could naively generate all possible groupings by iterating through the sorted integers and,
173
/// for each grouping, either adding that integer as it's own group or adding it to the last group
174
/// of that grouping. This could generate 2^N groupings, where N is the number of integers. Instead,
175
/// we still iterate through the sorted integers and, for each grouping, we add it to the last
176
/// group of that grouping. But we only add the integer as it's own group to the current best
177
/// grouping. This optimization avoids creating groupings that we know will not be the "best"
178
/// grouping, because adding the integer as it's own group means that all subsequent integers
179
/// cannot be grouped with the existing groups, and thus we only care about the optimal existing
180
/// groups.
181
pub(super) fn group_core_offsets(
182
offsets: &[(usize, i128)],
183
in_sync_threshold: Duration,
184
tsc_frequency: u64,
185
) -> Result<CoreGrouping> {
186
if offsets.is_empty() {
187
bail!("Per-core offsets cannot be empty");
188
}
189
190
// Convert threshold to TSC ticks
191
let in_sync_threshold_ticks =
192
in_sync_threshold.as_nanos() * tsc_frequency as u128 / 1_000_000_000u128;
193
194
let mut cores: Vec<CoreOffset> = offsets
195
.iter()
196
.map(|(i, offset)| CoreOffset {
197
core: *i,
198
offset: *offset,
199
})
200
.collect();
201
202
// Cores are sorted by their ascending by their offset then ascending by their core #. See the
203
// Ord implementation for CoreOffset.
204
cores.sort();
205
206
let mut grouping_options: Vec<CoreGrouping> = vec![CoreGrouping::new(vec![CoreGroup {
207
cores: vec![cores[0]],
208
}])?];
209
for core in &cores[1..] {
210
let mut best = grouping_options[0].clone();
211
best.add_group(CoreGroup { cores: vec![*core] });
212
213
let mut next_grouping_options = vec![best];
214
215
for grouping_option in &grouping_options {
216
if let Some(new_grouping) =
217
grouping_option.add_core_to_last_group(*core, in_sync_threshold_ticks)
218
{
219
next_grouping_options.push(new_grouping);
220
}
221
}
222
223
next_grouping_options.sort();
224
grouping_options = next_grouping_options;
225
}
226
227
Ok(grouping_options[0].clone())
228
}
229
230
#[cfg(test)]
231
mod tests {
232
use super::super::TscState;
233
use super::*;
234
235
#[test]
236
fn test_simple_offset_grouping() {
237
let offsets = vec![(0, 10), (1, 10), (2, 10), (3, 1000)];
238
let state = TscState::new(1_000_000_000, offsets, Duration::from_nanos(1))
239
.expect("TscState::new should not fail for this test");
240
241
let group0 = CoreGroup {
242
cores: vec![
243
CoreOffset {
244
core: 0,
245
offset: 10,
246
},
247
CoreOffset {
248
core: 1,
249
offset: 10,
250
},
251
CoreOffset {
252
core: 2,
253
offset: 10,
254
},
255
],
256
};
257
let group1 = CoreGroup {
258
cores: vec![CoreOffset {
259
core: 3,
260
offset: 1000,
261
}],
262
};
263
assert_eq!(
264
state.core_grouping,
265
CoreGrouping::new(vec![group0.clone(), group1])
266
.expect("CoreGrouping::new should not fail here")
267
);
268
269
assert_eq!(state.core_grouping.largest_group().clone(), group0);
270
assert_eq!(state.core_grouping.core_grouping_bitmask(), 0b1000u64);
271
}
272
273
#[test]
274
fn test_ambiguous_offset_grouping() {
275
// Could be grouped in several ways:
276
// - [10, 20] [30, 40] [50] <--- we like to have core0 be in a larger group
277
// - [10] [20, 30] [40, 50]
278
// - [10, 20] [30] [40, 50]
279
let offsets = vec![(0, 10), (1, 20), (2, 30), (3, 40), (4, 50)];
280
let state = TscState::new(1_000_000_000, offsets, Duration::from_nanos(20))
281
.expect("TscState::new should not fail for this test");
282
283
let group0 = CoreGroup {
284
cores: vec![
285
CoreOffset {
286
core: 0,
287
offset: 10,
288
},
289
CoreOffset {
290
core: 1,
291
offset: 20,
292
},
293
],
294
};
295
let group1 = CoreGroup {
296
cores: vec![
297
CoreOffset {
298
core: 2,
299
offset: 30,
300
},
301
CoreOffset {
302
core: 3,
303
offset: 40,
304
},
305
],
306
};
307
let group2 = CoreGroup {
308
cores: vec![CoreOffset {
309
core: 4,
310
offset: 50,
311
}],
312
};
313
assert_eq!(
314
state.core_grouping,
315
CoreGrouping::new(vec![group0.clone(), group1, group2])
316
.expect("CoreGrouping::new should not fail here")
317
);
318
319
// largest_group should return the first group over other equally large groups
320
assert_eq!(state.core_grouping.largest_group().clone(), group0);
321
assert_eq!(state.core_grouping.core_grouping_bitmask(), 0b11100u64);
322
}
323
324
#[test]
325
fn test_worst_case_grouping() {
326
// Worst case for the grouping algorithm, where if your algorithm isn't smart you can
327
// generate 2^129 groupings, which would be too large for a Vec to hold. This test just
328
// verifies that we don't crash or run out of memory.
329
let offsets = (0..129).map(|i| (i, 0)).collect();
330
TscState::new(1_000_000_000, offsets, Duration::from_nanos(1))
331
.expect("TscState::new should not fail for this test");
332
}
333
}
334
335