1use std::collections::HashMap;
32
33use crate::analysis::topo_analyse;
34use crate::error::{GraphError, GraphResult};
35use crate::graph::ComputeGraph;
36use crate::node::{NodeId, StreamId};
37
38#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct StreamAssignment {
45 pub node: NodeId,
46 pub stream: StreamId,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct SyncPoint {
56 pub from: NodeId,
58 pub stream_from: StreamId,
59 pub to: NodeId,
61 pub stream_to: StreamId,
62}
63
64#[derive(Debug, Clone)]
70pub struct StreamPlan {
71 pub assignments: Vec<StreamAssignment>,
73 pub sync_points: Vec<SyncPoint>,
75 pub num_streams: usize,
77 pub max_streams: usize,
79}
80
81impl StreamPlan {
82 pub fn stream_of(&self, node: NodeId) -> StreamId {
84 self.assignments
85 .iter()
86 .find(|a| a.node == node)
87 .map(|a| a.stream)
88 .unwrap_or(StreamId::DEFAULT)
89 }
90
91 pub fn nodes_on(&self, stream: StreamId) -> Vec<NodeId> {
93 self.assignments
94 .iter()
95 .filter(|a| a.stream == stream)
96 .map(|a| a.node)
97 .collect()
98 }
99
100 pub fn has_concurrency(&self) -> bool {
102 let first = self.assignments.first().map(|a| a.stream);
103 self.assignments.iter().any(|a| Some(a.stream) != first)
104 }
105
106 pub fn sync_count(&self) -> usize {
108 self.sync_points.len()
109 }
110}
111
112pub fn analyse(graph: &ComputeGraph, max_streams: usize) -> GraphResult<StreamPlan> {
127 if graph.is_empty() {
128 return Err(GraphError::EmptyGraph);
129 }
130
131 let max_streams = max_streams.max(1);
132 let topo = topo_analyse(graph)?;
133
134 let priority = topo.priority_order();
136
137 let mut stream_last: Vec<(u64, NodeId, StreamId)> = Vec::new();
140
141 let mut node_stream: HashMap<NodeId, StreamId> = HashMap::new();
143
144 for &node_id in &priority {
146 let preds = graph.predecessors(node_id)?;
147 let node_alap = topo.node_info(node_id).map(|i| i.alap).unwrap_or(0);
148
149 if preds.is_empty() {
150 let stream = if stream_last.len() < max_streams {
152 let sid = StreamId(stream_last.len() as u32);
153 stream_last.push((node_alap, node_id, sid));
154 sid
155 } else {
156 let best = stream_last
159 .iter()
160 .enumerate()
161 .min_by_key(|(_, (alap, _, _))| *alap)
162 .map(|(i, (_, _, sid))| (i, *sid))
163 .ok_or_else(|| {
164 GraphError::StreamPartitioningFailed(
165 "no streams available for assignment".into(),
166 )
167 })?;
168 stream_last[best.0] = (node_alap, node_id, best.1);
169 best.1
170 };
171 node_stream.insert(node_id, stream);
172 } else {
173 let pred_set: std::collections::HashSet<NodeId> = preds.iter().copied().collect();
186 let compatible_stream = stream_last
187 .iter()
188 .find(|(_, last_node, _)| pred_set.contains(last_node))
189 .map(|(_, _, sid)| *sid);
190
191 let stream = if let Some(sid) = compatible_stream {
192 sid
193 } else {
194 if stream_last.len() < max_streams {
197 let base_stream = preds
199 .iter()
200 .filter_map(|&p| node_stream.get(&p).copied())
201 .next()
202 .unwrap_or(StreamId::DEFAULT);
203 let base_last_is_pred = stream_last
206 .iter()
207 .find(|(_, _, sid)| *sid == base_stream)
208 .map(|(_, ln, _)| pred_set.contains(ln))
209 .unwrap_or(false);
210 if !base_last_is_pred {
211 let sid = StreamId(stream_last.len() as u32);
212 stream_last.push((node_alap, node_id, sid));
213 node_stream.insert(node_id, sid);
214 continue;
215 }
216 base_stream
217 } else {
218 preds
220 .iter()
221 .filter_map(|&p| node_stream.get(&p).copied())
222 .max_by_key(|&s| {
223 stream_last
224 .iter()
225 .find(|(_, _, sid)| *sid == s)
226 .map(|(alap, _, _)| *alap)
227 .unwrap_or(0)
228 })
229 .unwrap_or(StreamId::DEFAULT)
230 }
231 };
232
233 if let Some(entry) = stream_last.iter_mut().find(|(_, _, sid)| *sid == stream) {
235 if node_alap > entry.0 {
236 entry.0 = node_alap;
237 }
238 entry.1 = node_id;
239 }
240 node_stream.insert(node_id, stream);
241 }
242 }
243
244 let assignments: Vec<StreamAssignment> = topo
246 .order
247 .iter()
248 .map(|&nid| StreamAssignment {
249 node: nid,
250 stream: node_stream.get(&nid).copied().unwrap_or(StreamId::DEFAULT),
251 })
252 .collect();
253
254 let mut sync_points: Vec<SyncPoint> = Vec::new();
256 for edge in graph.edges() {
257 let (from, to) = edge;
258 let sf = node_stream.get(&from).copied().unwrap_or(StreamId::DEFAULT);
259 let st = node_stream.get(&to).copied().unwrap_or(StreamId::DEFAULT);
260 if sf != st {
261 sync_points.push(SyncPoint {
262 from,
263 stream_from: sf,
264 to,
265 stream_to: st,
266 });
267 }
268 }
269 sync_points.sort_by_key(|sp| (sp.from.0, sp.to.0));
270 sync_points.dedup_by_key(|sp| (sp.from, sp.to));
271
272 let num_streams = stream_last.len().max(1);
273
274 Ok(StreamPlan {
275 assignments,
276 sync_points,
277 num_streams,
278 max_streams,
279 })
280}
281
282#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::builder::GraphBuilder;
290
291 fn barrier(b: &mut GraphBuilder, name: &str) -> NodeId {
292 b.add_barrier(name)
293 }
294
295 #[test]
296 fn stream_empty_graph() {
297 let g = ComputeGraph::new();
298 assert!(matches!(analyse(&g, 4), Err(GraphError::EmptyGraph)));
299 }
300
301 #[test]
302 fn stream_single_node_default_stream() {
303 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
304 let n = barrier(&mut b, "n");
305 let g = b.build().expect("test graph builds successfully");
306 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
307 assert_eq!(plan.stream_of(n), StreamId(0));
308 }
309
310 #[test]
311 fn stream_linear_chain_single_stream() {
312 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
313 let a = barrier(&mut b, "a");
314 let bnode = barrier(&mut b, "b");
315 let c = barrier(&mut b, "c");
316 b.chain(&[a, bnode, c]);
317 let g = b.build().expect("test graph builds successfully");
318 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
319 assert!(!plan.has_concurrency());
321 assert_eq!(plan.sync_count(), 0);
322 }
323
324 #[test]
325 fn stream_fork_parallel_branches() {
326 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
328 let src = barrier(&mut b, "src");
329 let a = barrier(&mut b, "a");
330 let bnode = barrier(&mut b, "b");
331 b.fan_out(src, &[a, bnode]);
332 let g = b.build().expect("test graph builds successfully");
333 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
334 let sa = plan.stream_of(a);
336 let sb = plan.stream_of(bnode);
337 assert_ne!(
338 sa, sb,
339 "independent branches should be on different streams"
340 );
341 }
342
343 #[test]
344 fn stream_max_streams_one_disables_parallelism() {
345 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
346 let a = barrier(&mut b, "a");
347 let bnode = barrier(&mut b, "b");
348 let c = barrier(&mut b, "c");
349 let g = b.build().expect("test graph builds successfully");
351 let plan = analyse(&g, 1).expect("stream assignment analysis succeeds on valid graph");
352 assert_eq!(plan.stream_of(a), StreamId(0));
354 assert_eq!(plan.stream_of(bnode), StreamId(0));
355 assert_eq!(plan.stream_of(c), StreamId(0));
356 assert!(!plan.has_concurrency());
357 }
358
359 #[test]
360 fn stream_cross_stream_sync_detected() {
361 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
364 let a = barrier(&mut b, "a");
365 let c = barrier(&mut b, "c");
366 let bnode = barrier(&mut b, "b");
367 b.dep(a, bnode).dep(c, bnode);
368 let g = b.build().expect("test graph builds successfully");
369 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
370 let sa = plan.stream_of(a);
371 let sc = plan.stream_of(c);
372 if sa != sc {
373 assert!(plan.sync_count() > 0);
375 }
376 }
377
378 #[test]
379 fn stream_nodes_on_stream_zero() {
380 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
381 let a = barrier(&mut b, "a");
382 let bnode = barrier(&mut b, "b");
383 b.dep(a, bnode);
384 let g = b.build().expect("test graph builds successfully");
385 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
386 let on_s0 = plan.nodes_on(StreamId(0));
387 assert!(!on_s0.is_empty());
388 }
389
390 #[test]
391 fn stream_assignment_covers_all_nodes() {
392 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
393 let a = barrier(&mut b, "a");
394 let bnode = barrier(&mut b, "b");
395 let c = barrier(&mut b, "c");
396 b.chain(&[a, bnode, c]);
397 let g = b.build().expect("test graph builds successfully");
398 let plan = analyse(&g, 2).expect("stream assignment analysis succeeds on valid graph");
399 assert_eq!(plan.assignments.len(), 3);
400 }
401
402 #[test]
403 fn stream_plan_respects_max_streams_cap() {
404 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
406 for i in 0..5 {
407 barrier(&mut b, &format!("n{i}"));
408 }
409 let g = b.build().expect("test graph builds successfully");
410 let plan = analyse(&g, 3).expect("stream assignment analysis succeeds on valid graph");
411 assert!(plan.num_streams <= 3);
412 }
413
414 #[test]
415 fn stream_diamond_join_handled() {
416 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
418 let a = barrier(&mut b, "a");
419 let bnode = barrier(&mut b, "b");
420 let c = barrier(&mut b, "c");
421 let d = barrier(&mut b, "d");
422 b.dep(a, bnode).dep(a, c).dep(bnode, d).dep(c, d);
423 let g = b.build().expect("test graph builds successfully");
424 let plan = analyse(&g, 4).expect("stream assignment analysis succeeds on valid graph");
425 assert_eq!(plan.assignments.len(), 4);
428 }
429
430 #[test]
431 fn stream_zero_max_treated_as_one() {
432 let mut b = GraphBuilder::new().with_auto_infer_edges(false);
433 barrier(&mut b, "n");
434 let g = b.build().expect("test graph builds successfully");
435 let plan =
436 analyse(&g, 0).expect("stream assignment analysis succeeds even with 0 max_streams"); assert_eq!(plan.num_streams, 1);
438 }
439}