diff -r 6e5f40cfa877 -r 7db599be70cc src/Pure/Build/build_schedule.scala --- a/src/Pure/Build/build_schedule.scala Tue Feb 13 12:23:12 2024 +0100 +++ b/src/Pure/Build/build_schedule.scala Tue Feb 13 11:34:28 2024 +0100 @@ -556,35 +556,49 @@ def is_finished: Boolean = build_state.pending.isEmpty && build_state.running.isEmpty } - trait Scheduler { def build_schedule(build_state: Build_Process.State): Schedule } + trait Scheduler { def schedule(build_state: Build_Process.State): Schedule } + + trait Priority_Rule { def select_next(state: Build_Process.State): List[Config] } - abstract class Heuristic(timing_data: Timing_Data, build_uuid: String) - extends Scheduler { - val host_infos = timing_data.host_infos - val ordered_hosts = host_infos.hosts.sorted(host_infos.host_speeds) - - def next(state: Build_Process.State): List[Config] - - def build_schedule(build_state: Build_Process.State): Schedule = { + case class Generation_Scheme( + priority_rule: Priority_Rule, + timing_data: Timing_Data, + build_uuid: String + ) extends Scheduler { + def schedule(build_state: Build_Process.State): Schedule = { @tailrec def simulate(state: State): State = if (state.is_finished) state else { - val state1 = next(state.build_state).foldLeft(state)(_.start(_)).step(timing_data) + val state1 = + priority_rule + .select_next(state.build_state) + .foldLeft(state)(_.start(_)) + .step(timing_data) simulate(state1) } val start = Date.now() + val name = "generation scheme (" + priority_rule + ")" val end_state = - simulate(State(build_state, start.time, Schedule(build_uuid, toString, start, Graph.empty))) + simulate(State(build_state, start.time, Schedule(build_uuid, name, start, Graph.empty))) end_state.finished } } - class Default_Heuristic(timing_data: Timing_Data, options: Options, build_uuid: String) - extends Heuristic(timing_data, build_uuid) { - override def toString: String = "default build heuristic" + case class Optimizer(schedulers: List[Scheduler]) extends Scheduler { + require(schedulers.nonEmpty) + + def schedule(state: Build_Process.State): Schedule = + schedulers.map(_.schedule(state)).minBy(_.duration.ms) + } + + + /* priority rules */ + + class Default_Heuristic(host_infos: Host_Infos, options: Options) extends Priority_Rule { + override def toString: String = "default heuristic" def host_threads(host: Host): Int = { val m = (options ++ host.build.options).int("threads") @@ -594,7 +608,7 @@ def next_jobs(resources: Resources, sorted_jobs: List[String], host: Host): List[Config] = sorted_jobs.zip(resources.unused_nodes(host, host_threads(host))).map(Config(_, _)) - def next(state: Build_Process.State): List[Config] = { + def select_next(state: Build_Process.State): List[Config] = { val sorted_jobs = state.next_ready.sortBy(_.name)(state.sessions.ordering).map(_.name) val resources = host_infos.available(state) @@ -607,28 +621,57 @@ } } - class Meta_Heuristic(heuristics: List[Heuristic]) extends Scheduler { - require(heuristics.nonEmpty) + object Path_Time_Heuristic { + sealed trait Critical_Criterion + case class Absolute_Time(time: Time) extends Critical_Criterion { + override def toString: String = "absolute time (" + time.message_hms + ")" + } + case class Relative_Time(factor: Double) extends Critical_Criterion { + override def toString: String = "relative time (" + factor + ")" + } - def best_result(state: Build_Process.State): (Heuristic, Schedule) = - heuristics.map(heuristic => - heuristic -> heuristic.build_schedule(state)).minBy(_._2.duration.ms) + sealed trait Parallel_Strategy + case class Fixed_Thread(threads: Int) extends Parallel_Strategy { + override def toString: String = "fixed threads (" + threads + ")" + } + case class Time_Based_Threads(f: Time => Int) extends Parallel_Strategy { + override def toString: String = "time based threads" + } - def next(state: Build_Process.State): List[Config] = best_result(state)._1.next(state) - - def build_schedule(state: Build_Process.State): Schedule = best_result(state)._2 + sealed trait Host_Criterion + case object Critical_Nodes extends Host_Criterion { + override def toString: String = "per critical node" + } + case class Fixed_Fraction(fraction: Double) extends Host_Criterion { + override def toString: String = "fixed fraction (" + fraction + ")" + } + case class Host_Speed(min_factor: Double) extends Host_Criterion { + override def toString: String = "host speed (" + min_factor + ")" + } } - - /* heuristics */ - - abstract class Path_Heuristic( + class Path_Time_Heuristic( + is_critical: Path_Time_Heuristic.Critical_Criterion, + parallel_threads: Path_Time_Heuristic.Parallel_Strategy, + host_criterion: Path_Time_Heuristic.Host_Criterion, timing_data: Timing_Data, sessions_structure: Sessions.Structure, - max_threads_limit: Int, - build_uuid: String - ) extends Heuristic(timing_data, build_uuid) { + max_threads_limit: Int = 8 + ) extends Priority_Rule { + import Path_Time_Heuristic.* + + override def toString: Node = { + val params = + List( + "critical: " + is_critical, + "parallel: " + parallel_threads, + "fast hosts: " + host_criterion) + "path time heuristic (" + params.mkString(", ") + ")" + } + /* pre-computed properties for efficient heuristic */ + val host_infos = timing_data.host_infos + val ordered_hosts = host_infos.hosts.sorted(host_infos.host_speeds) val max_threads = host_infos.hosts.map(_.info.num_cpus).max min max_threads_limit @@ -687,59 +730,8 @@ parallel_paths(running.toMap)._1 } - } - - object Path_Time_Heuristic { - sealed trait Critical_Criterion - case class Absolute_Time(time: Time) extends Critical_Criterion { - override def toString: String = "absolute time (" + time.message_hms + ")" - } - case class Relative_Time(factor: Double) extends Critical_Criterion { - override def toString: String = "relative time (" + factor + ")" - } - - sealed trait Parallel_Strategy - case class Fixed_Thread(threads: Int) extends Parallel_Strategy { - override def toString: String = "fixed threads (" + threads + ")" - } - case class Time_Based_Threads(f: Time => Int) extends Parallel_Strategy { - override def toString: String = "time based threads" - } - - sealed trait Host_Criterion - case object Critical_Nodes extends Host_Criterion { - override def toString: String = "per critical node" - } - case class Fixed_Fraction(fraction: Double) extends Host_Criterion { - override def toString: String = "fixed fraction (" + fraction + ")" - } - case class Host_Speed(min_factor: Double) extends Host_Criterion { - override def toString: String = "host speed (" + min_factor + ")" - } - } - - class Path_Time_Heuristic( - is_critical: Path_Time_Heuristic.Critical_Criterion, - parallel_threads: Path_Time_Heuristic.Parallel_Strategy, - host_criterion: Path_Time_Heuristic.Host_Criterion, - timing_data: Timing_Data, - sessions_structure: Sessions.Structure, - build_uuid: String, - max_threads_limit: Int = 8 - ) extends Path_Heuristic(timing_data, sessions_structure, max_threads_limit, build_uuid) { - import Path_Time_Heuristic.* - - override def toString: Node = { - val params = - List( - "critical: " + is_critical, - "parallel: " + parallel_threads, - "fast hosts: " + host_criterion) - "path time heuristic (" + params.mkString(", ") + ")" - } - - def next(state: Build_Process.State): List[Config] = { + def select_next(state: Build_Process.State): List[Config] = { val resources = host_infos.available(state) def best_threads(task: Build_Process.Task): Int = @@ -1002,7 +994,7 @@ else { val start = Time.now() - val new_schedule = scheduler.build_schedule(state).update(state) + val new_schedule = scheduler.schedule(state).update(state) val schedule = if (_schedule.is_empty) new_schedule else List(_schedule.update(state), new_schedule).minBy(_.end)(Date.Ordering) @@ -1230,11 +1222,10 @@ parallel <- parallel_threads machine_split <- machine_splits } yield - Path_Time_Heuristic(is_critical, parallel, machine_split, timing_data, sessions_structure, - context.build_uuid) - val default_heuristic = - Default_Heuristic(timing_data, context.build_options, context.build_uuid) - new Meta_Heuristic(default_heuristic :: path_time_heuristics) + Path_Time_Heuristic(is_critical, parallel, machine_split, timing_data, sessions_structure) + val default_heuristic = Default_Heuristic(timing_data.host_infos, context.build_options) + val heuristics = default_heuristic :: path_time_heuristics + Optimizer(heuristics.map(Generation_Scheme(_, timing_data, context.build_uuid))) } override def open_build_process( @@ -1318,7 +1309,7 @@ def schedule_msg(res: Exn.Result[Schedule]): String = res match { case Exn.Res(schedule) => schedule.message case _ => "" } - Timing.timeit(scheduler.build_schedule(build_state), schedule_msg, output = progress.echo(_)) + Timing.timeit(scheduler.schedule(build_state), schedule_msg, output = progress.echo(_)) } using(store.open_server()) { server =>