1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum LaneError {
31 Shape,
33 NotTopological { item: usize, predecessor: usize },
35 InvalidIdentifier,
37}
38
39#[derive(Debug, Clone, Copy)]
41pub struct LaneAssignmentInput<'a> {
42 pub costs: &'a [u64],
44 pub predecessors: &'a [Vec<usize>],
46 pub groups: &'a [usize],
48 pub group_lanes: &'a [Option<u32>],
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct LaneAssignmentOptions {
54 pub lanes: u32,
56 pub synchronization_cost: u64,
59 pub locality_slack: u64,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct LaneAssignment {
66 pub item_lanes: Vec<u32>,
68 pub estimated_makespan: u64,
70 pub total_cost: u64,
72}
73
74pub fn assign_lanes(
76 input: LaneAssignmentInput<'_>,
77 options: LaneAssignmentOptions,
78) -> Result<LaneAssignment, LaneError> {
79 let count = input.costs.len();
80 if input.predecessors.len() != count || input.groups.len() != count {
81 return Err(LaneError::Shape);
82 }
83 let lane_count = options.lanes.max(1) as usize;
84 for &lane in input.group_lanes.iter().flatten() {
85 if lane as usize >= lane_count {
86 return Err(LaneError::InvalidIdentifier);
87 }
88 }
89 let mut group_lanes = input.group_lanes.to_vec();
90 let mut item_lanes = vec![0u32; count];
91 let mut finish = vec![0u64; count];
92 let mut lane_available = vec![0u64; lane_count];
93 let mut producer_finish = vec![0u64; lane_count];
96 let mut touched = Vec::new();
97 let mut total_cost = 0u64;
98 let sync = options.synchronization_cost;
99
100 let mut previous_lane = None::<usize>;
101 for item in 0..count {
102 let group = input.groups[item];
103 let Some(fixed) = group_lanes.get(group).copied() else {
104 return Err(LaneError::InvalidIdentifier);
105 };
106 let mut latest_producer = None::<(u64, usize)>;
107 for &predecessor in &input.predecessors[item] {
108 if predecessor >= item {
109 return Err(LaneError::NotTopological { item, predecessor });
110 }
111 let lane = item_lanes[predecessor] as usize;
112 if producer_finish[lane] == 0 {
113 touched.push(lane);
114 }
115 producer_finish[lane] = producer_finish[lane].max(finish[predecessor].max(1));
116 if latest_producer.is_none_or(|(time, _)| finish[predecessor] > time) {
117 latest_producer = Some((finish[predecessor], lane));
118 }
119 }
120 let mut best = (0u64, usize::MAX);
123 let mut second = 0u64;
124 for &lane in &touched {
125 let time = producer_finish[lane];
126 if time > best.0 {
127 second = best.0;
128 best = (time, lane);
129 } else if time > second {
130 second = time;
131 }
132 }
133 let ready_in = |lane: usize| {
134 let remote = if best.1 == lane { second } else { best.0 };
135 let remote = if remote == 0 {
136 0
137 } else {
138 remote.saturating_add(sync)
139 };
140 producer_finish[lane].max(remote)
141 };
142 let lane = match fixed {
143 Some(lane) => lane as usize,
144 None => {
145 let mut selected = 0usize;
146 let mut selected_key = (u64::MAX, u64::MAX);
147 for (lane, &available) in lane_available.iter().enumerate() {
148 let start = available.max(ready_in(lane));
149 let key = (start, available);
152 if key < selected_key {
153 selected_key = key;
154 selected = lane;
155 }
156 }
157 let local = latest_producer.map(|(_, lane)| lane).or(previous_lane);
158 match local {
159 Some(lane)
160 if lane_available[lane].max(ready_in(lane))
161 <= selected_key.0.saturating_add(options.locality_slack) =>
162 {
163 lane
164 }
165 _ => selected,
166 }
167 }
168 };
169 let start = lane_available[lane].max(ready_in(lane));
170 let cost = input.costs[item];
171 finish[item] = start.saturating_add(cost);
172 lane_available[lane] = finish[item];
173 item_lanes[item] = lane as u32;
174 group_lanes[group] = Some(lane as u32);
175 total_cost = total_cost.saturating_add(cost);
176 previous_lane = Some(lane);
177 for lane in touched.drain(..) {
178 producer_finish[lane] = 0;
179 }
180 }
181
182 Ok(LaneAssignment {
183 item_lanes,
184 estimated_makespan: lane_available.into_iter().max().unwrap_or(0),
185 total_cost,
186 })
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct LaneSchedulePlan {
192 pub item_lanes: Vec<u32>,
194 pub order: Vec<usize>,
196 pub estimated_makespan: u64,
198}
199
200pub fn list_schedule_lanes(
209 input: LaneAssignmentInput<'_>,
210 options: LaneAssignmentOptions,
211) -> Result<LaneSchedulePlan, LaneError> {
212 use std::cmp::Reverse;
213 use std::collections::BinaryHeap;
214
215 let count = input.costs.len();
216 if input.predecessors.len() != count || input.groups.len() != count {
217 return Err(LaneError::Shape);
218 }
219 let lane_count = options.lanes.max(1) as usize;
220 for &lane in input.group_lanes.iter().flatten() {
221 if lane as usize >= lane_count {
222 return Err(LaneError::InvalidIdentifier);
223 }
224 }
225 let mut successors = vec![Vec::new(); count];
226 for (item, predecessors) in input.predecessors.iter().enumerate() {
227 for &predecessor in predecessors {
228 if predecessor >= item {
229 return Err(LaneError::NotTopological { item, predecessor });
230 }
231 successors[predecessor].push(item);
232 }
233 }
234 if input
235 .groups
236 .iter()
237 .any(|&group| group >= input.group_lanes.len())
238 {
239 return Err(LaneError::InvalidIdentifier);
240 }
241 let fixed_lane = |item: usize| input.group_lanes[input.groups[item]];
245 let mut bottom_level = vec![0u64; count];
246 for item in (0..count).rev() {
247 let tail = successors[item]
248 .iter()
249 .map(|&successor| {
250 let crossing = matches!(
251 (fixed_lane(item), fixed_lane(successor)),
252 (Some(from), Some(to)) if from != to
253 );
254 bottom_level[successor].saturating_add(if crossing {
255 options.synchronization_cost
256 } else {
257 0
258 })
259 })
260 .max()
261 .unwrap_or(0);
262 bottom_level[item] = tail.saturating_add(input.costs[item]);
263 }
264
265 let mut group_lanes = input.group_lanes.to_vec();
266 let mut item_lanes = vec![0u32; count];
267 let mut start = vec![0u64; count];
268 let mut finish = vec![0u64; count];
269 let mut rank = vec![0usize; count];
270 let mut lane_available = vec![0u64; lane_count];
271 let mut producer_finish = vec![0u64; lane_count];
272 let mut touched = Vec::new();
273 let mut unresolved = input.predecessors.iter().map(Vec::len).collect::<Vec<_>>();
274 let mut ready = (0..count)
275 .filter(|&item| unresolved[item] == 0)
276 .map(|item| (bottom_level[item], Reverse(item)))
277 .collect::<BinaryHeap<_>>();
278 let sync = options.synchronization_cost;
279 let mut previous_lane = None::<usize>;
280 let mut next_rank = 0;
281 while let Some((_, Reverse(item))) = ready.pop() {
282 let mut latest_producer = None::<(u64, usize)>;
283 for &predecessor in &input.predecessors[item] {
284 let lane = item_lanes[predecessor] as usize;
285 if producer_finish[lane] == 0 {
286 touched.push(lane);
287 }
288 producer_finish[lane] = producer_finish[lane].max(finish[predecessor].max(1));
289 if latest_producer.is_none_or(|(time, _)| finish[predecessor] > time) {
290 latest_producer = Some((finish[predecessor], lane));
291 }
292 }
293 let mut best = (0u64, usize::MAX);
294 let mut second = 0u64;
295 for &lane in &touched {
296 let time = producer_finish[lane];
297 if time > best.0 {
298 second = best.0;
299 best = (time, lane);
300 } else if time > second {
301 second = time;
302 }
303 }
304 let ready_in = |lane: usize| {
305 let remote = if best.1 == lane { second } else { best.0 };
306 let remote = if remote == 0 {
307 0
308 } else {
309 remote.saturating_add(sync)
310 };
311 producer_finish[lane].max(remote)
312 };
313 let group = input.groups[item];
314 let lane = match group_lanes[group] {
315 Some(lane) => lane as usize,
316 None => {
317 let mut selected = 0usize;
318 let mut selected_key = (u64::MAX, u64::MAX);
319 for (lane, &available) in lane_available.iter().enumerate() {
320 let key = (available.max(ready_in(lane)), available);
321 if key < selected_key {
322 selected_key = key;
323 selected = lane;
324 }
325 }
326 match latest_producer.map(|(_, lane)| lane).or(previous_lane) {
327 Some(lane)
328 if lane_available[lane].max(ready_in(lane))
329 <= selected_key.0.saturating_add(options.locality_slack) =>
330 {
331 lane
332 }
333 _ => selected,
334 }
335 }
336 };
337 start[item] = lane_available[lane].max(ready_in(lane));
338 finish[item] = start[item].saturating_add(input.costs[item]);
339 lane_available[lane] = finish[item];
340 item_lanes[item] = lane as u32;
341 group_lanes[group] = Some(lane as u32);
342 rank[item] = next_rank;
343 next_rank += 1;
344 previous_lane = Some(lane);
345 for lane in touched.drain(..) {
346 producer_finish[lane] = 0;
347 }
348 for &successor in &successors[item] {
349 unresolved[successor] -= 1;
350 if unresolved[successor] == 0 {
351 ready.push((bottom_level[successor], Reverse(successor)));
352 }
353 }
354 }
355 let mut order = (0..count).collect::<Vec<_>>();
358 order.sort_unstable_by_key(|&item| (start[item], rank[item]));
359 Ok(LaneSchedulePlan {
360 item_lanes,
361 order,
362 estimated_makespan: lane_available.into_iter().max().unwrap_or(0),
363 })
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
369pub struct LaneWait {
370 pub lane: u32,
371 pub completed_tasks: u32,
372}
373
374#[derive(Debug, Clone, PartialEq, Eq)]
376pub struct LaneTask {
377 pub lane: u32,
378 pub sequence: u32,
380 pub items: Vec<usize>,
382 pub waits: Vec<LaneWait>,
384}
385
386pub fn form_lane_tasks(
391 lane_count: u32,
392 item_lanes: &[u32],
393 predecessors: &[Vec<usize>],
394) -> Result<Vec<LaneTask>, LaneError> {
395 form_lane_tasks_separated(lane_count, item_lanes, predecessors, &[])
396}
397
398pub fn form_lane_tasks_separated(
406 lane_count: u32,
407 item_lanes: &[u32],
408 predecessors: &[Vec<usize>],
409 classes: &[u8],
410) -> Result<Vec<LaneTask>, LaneError> {
411 form_lane_tasks_with_options(
412 lane_count,
413 item_lanes,
414 predecessors,
415 classes,
416 TaskFormationOptions::default(),
417 )
418}
419
420#[derive(Debug, Clone, Copy, Default)]
422pub struct TaskFormationOptions<'a> {
423 pub costs: &'a [u64],
425 pub max_cost: u64,
428 pub publish_after: &'a [bool],
430}
431
432pub fn form_lane_tasks_with_options(
434 lane_count: u32,
435 item_lanes: &[u32],
436 predecessors: &[Vec<usize>],
437 classes: &[u8],
438 options: TaskFormationOptions<'_>,
439) -> Result<Vec<LaneTask>, LaneError> {
440 let count = item_lanes.len();
441 if predecessors.len() != count
442 || (!classes.is_empty() && classes.len() != count)
443 || (!options.costs.is_empty() && options.costs.len() != count)
444 || (options.max_cost != 0 && options.costs.len() != count)
445 || (!options.publish_after.is_empty() && options.publish_after.len() != count)
446 {
447 return Err(LaneError::Shape);
448 }
449 let class_of = |item: usize| classes.get(item).copied().unwrap_or(0) & 3;
450 let mut task_classes: Vec<u8> = Vec::new();
451 let mut task_costs: Vec<u64> = Vec::new();
452 let lane_count = lane_count.max(1) as usize;
453 let mut tasks: Vec<LaneTask> = Vec::new();
454 let mut task_clocks: Vec<Vec<u32>> = Vec::new();
457 let mut lane_tasks: Vec<Vec<usize>> = vec![Vec::new(); lane_count];
458 let mut open = vec![None::<usize>; lane_count];
459 let mut lane_clock = vec![vec![0u32; lane_count]; lane_count];
460 let mut task_of_item = vec![usize::MAX; count];
461 let mut required = vec![0u32; lane_count];
462 let mut touched = Vec::new();
463
464 for item in 0..count {
465 let cost = options.costs.get(item).copied().unwrap_or(0);
466 let publish = options.publish_after.get(item).copied().unwrap_or(false);
467 let lane = item_lanes[item] as usize;
468 if lane >= lane_count {
469 return Err(LaneError::InvalidIdentifier);
470 }
471 for &predecessor in &predecessors[item] {
472 if predecessor >= item {
473 return Err(LaneError::NotTopological { item, predecessor });
474 }
475 let producer_lane = item_lanes[predecessor] as usize;
476 if producer_lane == lane {
477 continue;
478 }
479 let producer_task = task_of_item[predecessor];
480 let completed = tasks[producer_task].sequence + 1;
481 if completed > lane_clock[lane][producer_lane] {
482 if required[producer_lane] == 0 {
483 touched.push(producer_lane);
484 }
485 required[producer_lane] = required[producer_lane].max(completed);
486 }
487 }
488
489 if touched.is_empty()
490 && let Some(task) = open[lane]
491 && (task_classes[task] | class_of(item)) != 3
492 && (options.max_cost == 0 || task_costs[task].saturating_add(cost) <= options.max_cost)
493 {
494 tasks[task].items.push(item);
495 task_classes[task] |= class_of(item);
496 task_costs[task] = task_costs[task].saturating_add(cost);
497 if publish {
498 open[lane] = None;
499 }
500 task_of_item[item] = task;
501 continue;
502 }
503
504 touched.sort_unstable();
505 let mut clock = lane_clock[lane].clone();
506 let mut waits = Vec::with_capacity(touched.len());
507 for &producer_lane in &touched {
508 let completed = required[producer_lane];
509 let producer_task = lane_tasks[producer_lane][completed as usize - 1];
510 if open[producer_lane] == Some(producer_task) {
513 open[producer_lane] = None;
514 }
515 for (known, &implied) in clock.iter_mut().zip(&task_clocks[producer_task]) {
516 *known = (*known).max(implied);
517 }
518 waits.push(LaneWait {
519 lane: producer_lane as u32,
520 completed_tasks: completed,
521 });
522 }
523 for &producer_lane in &touched {
524 clock[producer_lane] = clock[producer_lane].max(required[producer_lane]);
525 }
526 let direct = waits
528 .iter()
529 .copied()
530 .filter(|wait| {
531 !waits.iter().any(|other| {
532 other.lane != wait.lane && {
533 let other_task =
534 lane_tasks[other.lane as usize][other.completed_tasks as usize - 1];
535 task_clocks[other_task][wait.lane as usize] >= wait.completed_tasks
536 }
537 })
538 })
539 .collect::<Vec<_>>();
540 for producer_lane in touched.drain(..) {
541 required[producer_lane] = 0;
542 }
543
544 let task = tasks.len();
545 let sequence = lane_tasks[lane].len() as u32;
546 tasks.push(LaneTask {
547 lane: lane as u32,
548 sequence,
549 items: vec![item],
550 waits: direct,
551 });
552 task_classes.push(class_of(item));
553 task_costs.push(cost);
554 lane_clock[lane] = clock.clone();
555 task_clocks.push(clock);
556 lane_tasks[lane].push(task);
557 open[lane] = (!publish).then_some(task);
558 task_of_item[item] = task;
559 }
560 Ok(tasks)
561}
562
563pub fn estimate_task_makespan(
570 lane_count: u32,
571 tasks: &[LaneTask],
572 item_costs: &[u64],
573 synchronization_cost: u64,
574 task_overhead: u64,
575) -> u64 {
576 let lane_count = lane_count.max(1) as usize;
577 let mut lane_time = vec![0u64; lane_count];
578 let mut finished = vec![Vec::<u64>::new(); lane_count];
579 for task in tasks {
580 let lane = (task.lane as usize).min(lane_count - 1);
581 let mut start = lane_time[lane];
582 for wait in &task.waits {
583 let producer = finished
584 .get(wait.lane as usize)
585 .and_then(|times| times.get((wait.completed_tasks as usize).checked_sub(1)?))
586 .copied()
587 .unwrap_or(0);
588 start = start.max(producer.saturating_add(synchronization_cost));
589 }
590 let cost = task
591 .items
592 .iter()
593 .filter_map(|&item| item_costs.get(item))
594 .fold(task_overhead, |total, cost| total.saturating_add(*cost));
595 let finish = start.saturating_add(cost);
596 lane_time[lane] = finish;
597 finished[lane].push(finish);
598 }
599 lane_time.into_iter().max().unwrap_or(0)
600}
601
602pub fn verify_lane_tasks(
608 lane_count: u32,
609 tasks: &[LaneTask],
610 predecessors: &[Vec<usize>],
611) -> Result<(), (usize, usize)> {
612 let lane_count = lane_count.max(1) as usize;
613 let count = predecessors.len();
614 let mut position = vec![(usize::MAX, usize::MAX); count];
615 let mut lane_sequences: Vec<Vec<usize>> = vec![Vec::new(); lane_count];
616 for (index, task) in tasks.iter().enumerate() {
617 let lane = task.lane as usize;
618 if lane >= lane_count || task.sequence as usize != lane_sequences[lane].len() {
619 return Err((usize::MAX, usize::MAX));
620 }
621 lane_sequences[lane].push(index);
622 for (offset, &item) in task.items.iter().enumerate() {
623 if item >= count || position[item].0 != usize::MAX {
624 return Err((usize::MAX, item.min(count)));
625 }
626 position[item] = (index, offset);
627 }
628 }
629 if position.iter().any(|(task, _)| *task == usize::MAX) {
630 return Err((usize::MAX, usize::MAX));
631 }
632 let mut clocks: Vec<Vec<u32>> = Vec::with_capacity(tasks.len());
635 let mut lane_previous: Vec<Option<usize>> = vec![None; lane_count];
636 for (index, task) in tasks.iter().enumerate() {
637 let lane = task.lane as usize;
638 let mut clock = lane_previous[lane]
639 .map(|previous| {
640 let mut clock: Vec<u32> = clocks[previous].clone();
641 clock[lane] = clock[lane].max(tasks[previous].sequence + 1);
642 clock
643 })
644 .unwrap_or_else(|| vec![0; lane_count]);
645 for wait in &task.waits {
646 let waited_lane = wait.lane as usize;
647 if waited_lane >= lane_count || waited_lane == lane || wait.completed_tasks == 0 {
648 return Err((usize::MAX, usize::MAX));
649 }
650 let Some(&waited) = lane_sequences[waited_lane].get(wait.completed_tasks as usize - 1)
651 else {
652 return Err((usize::MAX, usize::MAX));
653 };
654 if waited >= index {
655 return Err((usize::MAX, usize::MAX));
656 }
657 for (known, &implied) in clock.iter_mut().zip(&clocks[waited]) {
658 *known = (*known).max(implied);
659 }
660 clock[waited_lane] = clock[waited_lane].max(wait.completed_tasks);
661 }
662 lane_previous[lane] = Some(index);
663 clocks.push(clock);
664 }
665 for (item, item_predecessors) in predecessors.iter().enumerate() {
666 let (task, offset) = position[item];
667 for &predecessor in item_predecessors {
668 let (producer_task, producer_offset) = position[predecessor];
669 let ordered = if tasks[producer_task].lane == tasks[task].lane {
670 (producer_task, producer_offset) < (task, offset)
671 } else {
672 clocks[task][tasks[producer_task].lane as usize] > tasks[producer_task].sequence
673 };
674 if !ordered {
675 return Err((predecessor, item));
676 }
677 }
678 }
679 Ok(())
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 fn assign(costs: &[u64], predecessors: &[Vec<usize>], lanes: u32, sync: u64) -> LaneAssignment {
687 let groups = (0..costs.len()).collect::<Vec<_>>();
688 let group_lanes = vec![None; costs.len()];
689 assign_lanes(
690 LaneAssignmentInput {
691 costs,
692 predecessors,
693 groups: &groups,
694 group_lanes: &group_lanes,
695 },
696 LaneAssignmentOptions {
697 lanes,
698 synchronization_cost: sync,
699 locality_slack: 0,
700 },
701 )
702 .unwrap()
703 }
704
705 #[test]
706 fn publication_does_not_hold_a_consumer_behind_unrelated_work() {
707 let lanes = [0, 0, 1];
708 let predecessors = vec![vec![], vec![], vec![0]];
709 let costs = [2, 100, 100];
710 let original = form_lane_tasks(2, &lanes, &predecessors).unwrap();
711 assert_eq!(estimate_task_makespan(2, &original, &costs, 0, 0), 202);
712 for options in [
713 TaskFormationOptions {
714 costs: &costs,
715 max_cost: 50,
716 ..TaskFormationOptions::default()
717 },
718 TaskFormationOptions {
719 publish_after: &[true, false, false],
720 ..TaskFormationOptions::default()
721 },
722 ] {
723 let tasks =
724 form_lane_tasks_with_options(2, &lanes, &predecessors, &[], options).unwrap();
725 assert_eq!(estimate_task_makespan(2, &tasks, &costs, 0, 0), 102);
726 verify_lane_tasks(2, &tasks, &predecessors).unwrap();
727 assert_eq!(
728 tasks[2].waits,
729 [LaneWait {
730 lane: 0,
731 completed_tasks: 1
732 }]
733 );
734 }
735 }
736
737 #[test]
738 fn publication_and_budget_keep_sparse_classes_separate() {
739 let predecessors = vec![vec![], vec![0], vec![1], vec![2]];
740 let tasks = form_lane_tasks_with_options(
741 1,
742 &[0; 4],
743 &predecessors,
744 &[1, 0, 2, 2],
745 TaskFormationOptions {
746 costs: &[1; 4],
747 max_cost: 100,
748 publish_after: &[false, true, false, false],
749 },
750 )
751 .unwrap();
752 assert_eq!(
753 tasks.iter().map(|t| t.items.clone()).collect::<Vec<_>>(),
754 [vec![0, 1], vec![2, 3]]
755 );
756 verify_lane_tasks(1, &tasks, &predecessors).unwrap();
757 assert_eq!(
758 form_lane_tasks_with_options(
759 1,
760 &[0],
761 &[vec![]],
762 &[],
763 TaskFormationOptions {
764 max_cost: 1,
765 ..TaskFormationOptions::default()
766 }
767 ),
768 Err(LaneError::Shape)
769 );
770 }
771
772 #[test]
773 fn independent_items_spread_across_lanes() {
774 let costs = vec![10; 8];
775 let predecessors = vec![Vec::new(); 8];
776 let result = assign(&costs, &predecessors, 4, 100);
777 let mut per_lane = [0; 4];
778 for lane in &result.item_lanes {
779 per_lane[*lane as usize] += 1;
780 }
781 assert_eq!(per_lane, [2, 2, 2, 2]);
782 assert_eq!(result.estimated_makespan, 20);
783 assert_eq!(result.total_cost, 80);
784 }
785
786 #[test]
787 fn expensive_synchronization_keeps_a_chain_in_one_lane() {
788 let costs = vec![10; 4];
789 let predecessors = vec![vec![], vec![0], vec![1], vec![2]];
790 let result = assign(&costs, &predecessors, 4, 1000);
791 assert!(
792 result
793 .item_lanes
794 .iter()
795 .all(|lane| *lane == result.item_lanes[0])
796 );
797 assert_eq!(result.estimated_makespan, 40);
798 }
799
800 #[test]
801 fn locality_slack_keeps_runs_of_independent_items_together() {
802 let costs = vec![10; 8];
803 let predecessors = vec![Vec::new(); 8];
804 let groups = (0..8).collect::<Vec<_>>();
805 let group_lanes = vec![None; 8];
806 let result = assign_lanes(
807 LaneAssignmentInput {
808 costs: &costs,
809 predecessors: &predecessors,
810 groups: &groups,
811 group_lanes: &group_lanes,
812 },
813 LaneAssignmentOptions {
814 lanes: 2,
815 synchronization_cost: 100,
816 locality_slack: 30,
817 },
818 )
819 .unwrap();
820 assert_eq!(result.item_lanes, vec![0, 0, 0, 0, 1, 1, 1, 1]);
822 assert_eq!(result.estimated_makespan, 40);
823 }
824
825 #[test]
826 fn ownership_groups_share_a_lane_and_pins_are_respected() {
827 let costs = vec![10, 10, 10, 10];
828 let predecessors = vec![Vec::new(); 4];
829 let groups = vec![0, 1, 0, 2];
830 let group_lanes = vec![None, Some(3), None];
831 let result = assign_lanes(
832 LaneAssignmentInput {
833 costs: &costs,
834 predecessors: &predecessors,
835 groups: &groups,
836 group_lanes: &group_lanes,
837 },
838 LaneAssignmentOptions {
839 lanes: 4,
840 synchronization_cost: 1,
841 locality_slack: 0,
842 },
843 )
844 .unwrap();
845 assert_eq!(result.item_lanes[0], result.item_lanes[2]);
846 assert_eq!(result.item_lanes[1], 3);
847 }
848
849 #[test]
850 fn non_topological_input_is_rejected() {
851 let costs = vec![1, 1];
852 let predecessors = vec![vec![1], vec![]];
853 let groups = vec![0, 1];
854 let group_lanes = vec![None, None];
855 assert_eq!(
856 assign_lanes(
857 LaneAssignmentInput {
858 costs: &costs,
859 predecessors: &predecessors,
860 groups: &groups,
861 group_lanes: &group_lanes,
862 },
863 LaneAssignmentOptions {
864 lanes: 2,
865 synchronization_cost: 0,
866 locality_slack: 0,
867 },
868 ),
869 Err(LaneError::NotTopological {
870 item: 0,
871 predecessor: 1
872 })
873 );
874 }
875
876 #[test]
877 fn tasks_split_only_at_new_cross_lane_dependencies() {
878 let lanes = [0, 1, 0, 1, 0, 1, 0, 1];
881 let predecessors = vec![
882 vec![],
883 vec![],
884 vec![0],
885 vec![1, 0],
886 vec![2],
887 vec![3, 2],
888 vec![4],
889 vec![5, 6],
890 ];
891 let tasks = form_lane_tasks(2, &lanes, &predecessors).unwrap();
892 verify_lane_tasks(2, &tasks, &predecessors).unwrap();
893 let lane0 = tasks
894 .iter()
895 .filter(|task| task.lane == 0)
896 .collect::<Vec<_>>();
897 assert_eq!(lane0[0].items, vec![0, 2]);
899 assert_eq!(lane0[1].items, vec![4, 6]);
900 let lane1 = tasks
901 .iter()
902 .filter(|task| task.lane == 1)
903 .collect::<Vec<_>>();
904 assert_eq!(lane1.len(), 3);
905 assert_eq!(lane1[0].items, vec![1]);
906 assert!(lane1[0].waits.is_empty());
907 assert_eq!(lane1[1].items, vec![3, 5]);
908 assert_eq!(
909 lane1[1].waits,
910 vec![LaneWait {
911 lane: 0,
912 completed_tasks: 1
913 }]
914 );
915 assert_eq!(lane1[2].items, vec![7]);
916 assert_eq!(
917 lane1[2].waits,
918 vec![LaneWait {
919 lane: 0,
920 completed_tasks: 2
921 }]
922 );
923 }
924
925 #[test]
926 fn list_scheduling_runs_critical_work_first() {
927 let costs = vec![6, 1, 1, 1];
931 let predecessors = vec![vec![], vec![], vec![1], vec![2]];
932 let groups = vec![0, 0, 1, 1];
933 let group_lanes = vec![Some(0), Some(1)];
934 let plan = list_schedule_lanes(
935 LaneAssignmentInput {
936 costs: &costs,
937 predecessors: &predecessors,
938 groups: &groups,
939 group_lanes: &group_lanes,
940 },
941 LaneAssignmentOptions {
942 lanes: 2,
943 synchronization_cost: 5,
944 locality_slack: 0,
945 },
946 )
947 .unwrap();
948 assert_eq!(plan.item_lanes, vec![0, 0, 1, 1]);
949 let position = |item| plan.order.iter().position(|&x| x == item).unwrap();
950 assert!(position(1) < position(0));
951 assert_eq!(plan.estimated_makespan, 8);
954 for (item, predecessors) in predecessors.iter().enumerate() {
955 for &predecessor in predecessors {
956 assert!(position(predecessor) < position(item));
957 }
958 }
959 }
960
961 #[test]
962 fn task_makespan_charges_task_boundaries_and_waits() {
963 let tasks = vec![
964 LaneTask {
965 lane: 0,
966 sequence: 0,
967 items: vec![0],
968 waits: Vec::new(),
969 },
970 LaneTask {
971 lane: 1,
972 sequence: 0,
973 items: vec![1, 2],
974 waits: vec![LaneWait {
975 lane: 0,
976 completed_tasks: 1,
977 }],
978 },
979 ];
980 assert_eq!(estimate_task_makespan(2, &tasks, &[9, 2, 3], 5, 1), 21);
982 }
983
984 #[test]
985 fn transitive_waits_are_omitted() {
986 let lanes = [0, 1, 2];
988 let predecessors = vec![vec![], vec![0], vec![1, 0]];
989 let tasks = form_lane_tasks(3, &lanes, &predecessors).unwrap();
990 verify_lane_tasks(3, &tasks, &predecessors).unwrap();
991 let last = tasks.iter().find(|task| task.lane == 2).unwrap();
992 assert_eq!(
993 last.waits,
994 vec![LaneWait {
995 lane: 1,
996 completed_tasks: 1
997 }]
998 );
999 }
1000
1001 #[test]
1002 fn separated_classes_never_share_a_task() {
1003 let lanes = [0, 0, 0, 0];
1004 let predecessors = vec![vec![], vec![], vec![], vec![]];
1005 let tasks = form_lane_tasks_separated(1, &lanes, &predecessors, &[1, 0, 2, 2]).unwrap();
1006 verify_lane_tasks(1, &tasks, &predecessors).unwrap();
1007 assert_eq!(
1008 tasks
1009 .iter()
1010 .map(|task| task.items.clone())
1011 .collect::<Vec<_>>(),
1012 vec![vec![0, 1], vec![2, 3]]
1013 );
1014 }
1015
1016 #[test]
1017 fn verification_rejects_a_missing_wait() {
1018 let lanes = [0, 1];
1019 let predecessors = vec![vec![], vec![0]];
1020 let mut tasks = form_lane_tasks(2, &lanes, &predecessors).unwrap();
1021 for task in &mut tasks {
1022 task.waits.clear();
1023 }
1024 assert_eq!(verify_lane_tasks(2, &tasks, &predecessors), Err((0, 1)));
1025 }
1026
1027 #[test]
1028 fn randomized_graphs_form_valid_tasks() {
1029 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1031 let mut next = || {
1032 state ^= state << 13;
1033 state ^= state >> 7;
1034 state ^= state << 17;
1035 state
1036 };
1037 for _ in 0..200 {
1038 let count = (next() % 40) as usize + 1;
1039 let lane_count = (next() % 5) as u32 + 1;
1040 let mut predecessors = vec![Vec::new(); count];
1041 for (item, row) in predecessors.iter_mut().enumerate() {
1042 for predecessor in 0..item {
1043 if next() % 5 == 0 {
1044 row.push(predecessor);
1045 }
1046 }
1047 }
1048 let costs = (0..count).map(|_| next() % 50 + 1).collect::<Vec<_>>();
1049 let assignment = assign(&costs, &predecessors, lane_count, next() % 40);
1050 let tasks = form_lane_tasks(lane_count, &assignment.item_lanes, &predecessors).unwrap();
1051 verify_lane_tasks(lane_count, &tasks, &predecessors).unwrap();
1052 let random_lanes = (0..count)
1053 .map(|_| (next() % u64::from(lane_count)) as u32)
1054 .collect::<Vec<_>>();
1055 let tasks = form_lane_tasks(lane_count, &random_lanes, &predecessors).unwrap();
1056 verify_lane_tasks(lane_count, &tasks, &predecessors).unwrap();
1057 let publish = (0..count).map(|_| next() % 3 == 0).collect::<Vec<_>>();
1058 let classes = (0..count).map(|_| (next() % 4) as u8).collect::<Vec<_>>();
1059 let tasks = form_lane_tasks_with_options(
1060 lane_count,
1061 &random_lanes,
1062 &predecessors,
1063 &classes,
1064 TaskFormationOptions {
1065 costs: &costs,
1066 max_cost: next() % 100 + 1,
1067 publish_after: &publish,
1068 },
1069 )
1070 .unwrap();
1071 verify_lane_tasks(lane_count, &tasks, &predecessors).unwrap();
1072 let mut covered = tasks
1073 .iter()
1074 .flat_map(|t| t.items.iter().copied())
1075 .collect::<Vec<_>>();
1076 covered.sort_unstable();
1077 assert_eq!(covered, (0..count).collect::<Vec<_>>());
1078 for task in &tasks {
1079 assert!(
1080 task.items.len() == 1
1081 || task
1082 .items
1083 .iter()
1084 .fold(0, |class, &item| class | classes[item])
1085 != 3
1086 );
1087 }
1088
1089 let groups = (0..count)
1092 .map(|_| (next() % count as u64) as usize)
1093 .collect::<Vec<_>>();
1094 let group_lanes = (0..count)
1095 .map(|_| (next() % 3 == 0).then(|| (next() % u64::from(lane_count)) as u32))
1096 .collect::<Vec<_>>();
1097 let plan = list_schedule_lanes(
1098 LaneAssignmentInput {
1099 costs: &costs,
1100 predecessors: &predecessors,
1101 groups: &groups,
1102 group_lanes: &group_lanes,
1103 },
1104 LaneAssignmentOptions {
1105 lanes: lane_count,
1106 synchronization_cost: next() % 40,
1107 locality_slack: next() % 20,
1108 },
1109 )
1110 .unwrap();
1111 let mut position = vec![usize::MAX; count];
1112 for (index, &item) in plan.order.iter().enumerate() {
1113 assert_eq!(position[item], usize::MAX, "item listed twice");
1114 position[item] = index;
1115 }
1116 for (item, row) in predecessors.iter().enumerate() {
1117 assert!(
1118 row.iter()
1119 .all(|&predecessor| position[predecessor] < position[item])
1120 );
1121 if let Some(lane) = group_lanes[groups[item]] {
1122 assert_eq!(plan.item_lanes[item], lane);
1123 }
1124 let first = groups
1125 .iter()
1126 .position(|&group| group == groups[item])
1127 .unwrap();
1128 assert_eq!(plan.item_lanes[item], plan.item_lanes[first]);
1129 }
1130 }
1131 }
1132}