/**
* Copyright 2012-2013 University Of Southern California
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.workflowsim.planning;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cloudbus.cloudsim.Consts;
import org.cloudbus.cloudsim.Log;
import org.workflowsim.CondorVM;
import org.workflowsim.FileItem;
import org.workflowsim.Task;
import org.workflowsim.utils.Parameters;
/**
* The HEFT planning algorithm.
*
* @author Pedro Paulo Vezz谩 Campos
* @date Oct 12, 2013
*/
public class HEFTPlanningAlgorithm extends BasePlanningAlgorithm {
private Map<Task, Map<CondorVM, Double>> computationCosts;
// 任务Task在某vm上的计算代价时间
private Map<Task, Map<Task, Double>> transferCosts;
// 任务与任务间的传输代价时间
private Map<Task, Double> rank;
// 任务的“rank”
private Map<CondorVM, List<Event>> schedules;
// vm上面的调度事件(一个一个的时间片)
private Map<Task, Double> earliestFinishTimes;
// 任务的最早完成时间EFT
private double averageBandwidth;
// 平均带宽,此值可用于计算任务到任务的传输代价时间
// vm上的一个调度事件(一个时间片)
private class Event {
public double start;
public double finish;
public Event(double start, double finish) {
this.start = start;
this.finish = finish;
}
}
private class TaskRank implements Comparable<TaskRank> {
public Task task;
public Double rank;
public TaskRank(Task task, Double rank) {
this.task = task;
this.rank = rank;
}
@Override
public int compareTo(TaskRank o) {
return o.rank.compareTo(rank);
// o.rank比rank大时,返回值为1;等:0;小:-1.
}
}
// 五大初始化
public HEFTPlanningAlgorithm() {
computationCosts = new HashMap<>();
transferCosts = new HashMap<>();
rank = new HashMap<>();
earliestFinishTimes = new HashMap<>();
schedules = new HashMap<>();
}
/**
* The main function
*/
@Override
public void run() {
Log.printLine("HEFT planner running with " + getTaskList().size()
+ " tasks.");
averageBandwidth = calculateAverageBandwidth();
// 平均带宽,此值可用于计算任务到任务的传输成本
// 初始化schedule
for (Object vmObject : getVmList()) {
CondorVM vm = (CondorVM) vmObject;
schedules.put(vm, new ArrayList<>());
}
// Prioritization phase
calculateComputationCosts();
// 计算每个task在每台vm上面的运行时间
calculateTransferCosts();
// 计算有前后关系的任务i到j的传输成本
calculateRanks();
// Selection phase
allocateTasks();
}
/**
* Calculates the average available bandwidth among all VMs in Mbit/s
*
* @return Average available bandwidth in Mbit/s
*/
// 计算所有vm的平均带宽,即vm.getBw()
private double calculateAverageBandwidth() {
double avg = 0.0;
for (Object vmObject : getVmList()) {
CondorVM vm = (CondorVM) vmObject;
avg += vm.getBw();
}
return avg / getVmList().size();
}
/**
* Populates the computationCosts field with the time in seconds to compute
* a task in a vm.
*/
// 计算每个task在每台vm上面的运行时间(即,计算成本)
private void calculateComputationCosts() {
for (Task task : getTaskList()) {
Map<CondorVM, Double> costsVm = new HashMap<>();
for (Object vmObject : getVmList()) {
CondorVM vm = (CondorVM) vmObject;
if (vm.getNumberOfPes() < task.getNumberOfPes()) { //这台vm处理不了
costsVm.put(vm, Double.MAX_VALUE);// 赋值最大
} else { //处理得了
costsVm.put(vm,
task.getCloudletTotalLength() / vm.getMips());
// 时间 = 总量/速度
}
}
computationCosts.put(task, costsVm);
}
// 最终可得每个task在每台vm上面的运行时间(如不能完成,则为Double最大值)
// 结果保存于computationCosts中
}
/**
* Populates the transferCosts map with the time in seconds to transfer all
* files from each parent to each child
*/
// 计算有前后关系的任务i到j的传输成本
private void calculateTransferCosts() {
// Initializing the matrix
// 初始化
// 对每一个任务i,transferCosts.put(i, <j,0.0>(j=1..n));
for (Task task1 : getTaskList()) {
Map<Task, Double> taskTransferCosts = new HashMap<>();
for (Task task2 : getTaskList()) {
taskTransferCosts.put(task2, 0.0);
}
transferCosts.put(task1, taskTransferCosts);
}
// Calculating the actual values
// 实际求值过程
for (Task parent : getTaskList()) {
for (Task child : parent.getChildList()) {
// parent.getChildList() 可取parent任务的子任务列表
transferCosts.get(parent).put(child,
calculateTransferCost(parent, child));
// 注意后面的这个calculateTransferCost(parent, child)函数是不加s的Cost,在后文有定义。
}
}
}
/**
* Accounts the time in seconds necessary to transfer all files described
* between parent and child
*
* @param parent
* @param child
* @return Transfer cost in seconds
*/
//计算传输成本,由parent到child仅一道直系
private double calculateTransferCost(Task parent, Task child) {
List<FileItem> parentFiles = parent.getFileList();
List<FileItem> childFiles = child.getFileList();
double acc = 0.0;
for (FileItem parentFile : parentFiles) {
// 如果当前parentFile.getType()不符要求Parameters.FileType.OUTPUT,则不算,跳过
if (parentFile.getType() != Parameters.FileType.OUTPUT) {
continue;
}
for (FileItem childFile : childFiles) {
// 如果childFile.getType()符合要求Parameters.FileType.INPUT 且父文件与子文件getName()相等
if (childFile.getType() == Parameters.FileType.INPUT
&& childFile.getName().equals(parentFile.getName())) {
acc += childFile.getSize();
break;
}
}
}
// file Size is in Bytes, acc in MB
acc = acc / Consts.MILLION;
// acc in MB, averageBandwidth in Mb/s
return acc * 8 / averageBandwidth;
}
/**
* Invokes calculateRank for each task to be scheduled
*/
private void calculateRanks() {
for (Task task : getTaskList()) {
calculateRank(task);
}
}
/**
* Populates rank.get(task) with the rank of task as defined in the HEFT
* paper.
*
* @param task The task have the rank calculates
* @return The rank
*/
private double calculateRank(Task task) {
if (rank.containsKey(task)) {
// 递归结束条件之一:若已经计算,即rank已经存有task的值,则直接返回rank.get(task)
return rank.get(task);
}
double averageComputationCost = 0.0;
// 对此task在所有可被执行的vm上的各项时间(共computationCosts.get(task).size()项)求累加和
for (Double cost : computationCosts.get(task).values()) {
averageComputationCost += cost;
}
// 总和sum/总数computationCosts.get(task).size(),得到平均值
averageComputationCost /= computationCosts.get(task).size();
double max = 0.0;
// 对当前的任务task,dfs处理其所有子任务child_i,求时间代价总和
for (Task child : task.getChildList()) {
double childCost = transferCosts.get(task).get(child) + calculateRank(child);
// 此子任务child的时间代价 = 父子传输的cost + 子与子孙的cost(递归求和)
max = Math.max(max, childCost);
// makespan是取各个子分支里最长的那一支的时间代价
}
rank.put(task, averageComputationCost + max);
// rank_task = 此任务在各台可行vm上的平均时间代价+各个子分支里最长的那一支的时间代价
return rank.get(task);
}
/**
* Allocates all tasks to be scheduled in non-ascending order of schedule.
*/
private void allocateTasks() {
List<TaskRank> taskRank = new ArrayList<>();
// 为便于排序,将上述rank里存储的结果“捯腾”进一个新的列表taskRank中。
// 其中,第一键值是task,第二键值是时间代价。
for (Task task : rank.keySet()) {
taskRank.add(new TaskRank(task, rank.get(task)));
}
// Sorting in non-ascending order of rank
// taskRank里的键值对,降序排序
Collections.sort(taskRank);
for (TaskRank rank : taskRank) {
allocateTask(rank.task);
}
}
/**
* Schedules the task given in one of the VMs minimizing the earliest finish
* time
*
* @param task The task to be scheduled
* @pre All parent tasks are already scheduled
*/
private void allocateTask(Task task) {
CondorVM chosenVM = null; //待选vm,初始为空
double earliestFinishTime = Double.MAX_VALUE; //最早完成时间EFT,初始为最大值
double bestReadyTime = 0.0;
double finishTime;
for (Object vmObject : getVmList()) { // 遍历各个vmObject
CondorVM vm = (CondorVM) vmObject; // 强制类型转换:vmObject->CondorVM,便于后续处理
double minReadyTime = 0.0;
for (Task parent : task.getParentList()) {
// 就绪时间 初始化,等于 父任务的最早完成时间
double readyTime = earliestFinishTimes.get(parent);
// 如果当前任务task与其父任务parent不在同一台vm上面执行(vm.getId不相等)
if (parent.getVmId() != vm.getId()) {
// 则当前任务task的就绪时间还要加上与父任务之间的传输时间
readyTime += transferCosts.get(parent).get(task);
}
minReadyTime = Math.max(minReadyTime, readyTime);
// 此task最终的最小等待时间,等于其各个前驱节点最小等待时间的最大值,因为要把前面的任务全部完成嘛。
}
// 计算任务task在虚拟机vm上的完成时间
finishTime = findFinishTime(task, vm, minReadyTime, false);
// 更新EFT,以及对应EFT的最好开始时间、待选vm
if (finishTime < earliestFinishTime) {
bestReadyTime = minReadyTime;
earliestFinishTime = finishTime;
chosenVM = vm;
}
}
findFinishTime(task, chosenVM, bestReadyTime, true);
earliestFinishTimes.put(task, earliestFinishTime);
task.setVmId(chosenVM.getId());
}
/**
* Finds the best time slot available to minimize the finish time of the
* given task in the vm with the constraint of not scheduling it before
* readyTime. If occupySlot is true, reserves the time slot in the schedule.
*
* @param task The task to have the time slot reserved
* @param vm The vm that will execute the task
* @param readyTime The first moment that the task is available to be
* scheduled
* @param occupySlot If true, reserves the time slot in the schedule.
* @return The minimal finish time of the task in the vmn
*/
private double findFinishTime(Task task, CondorVM vm, double readyTime, boolean occupySlot) {
// occupySlot……是否抢占
List<Event> sched = schedules.get(vm);
// 获取这台vm对应的列表,是为List<Event> sched
double computationCost = computationCosts.get(task).get(vm);
// 此task在vm上的计算代价时间
double start, finish;
int pos;
// 如果vm对应的列表空着(最简单的情况)
if (sched.isEmpty()) {
//如果是“抢占”式任务
if (occupySlot) {
// 那么直接“占”,起始时间是就绪时间,结束时间再加上计算代价时间
sched.add(new Event(readyTime, readyTime + computationCost));
}
// 完成时间 = 就绪时间 + 计算代价开销
return readyTime + computationCost;
}
// 如果vm对应的列表已经有了一个时间片段,或者说事件(比较简单的情况)
if (sched.size() == 1) {
// 当前任务的就绪时间,比vm上那个时间片的结束时间还要晚
if (readyTime >= sched.get(0).finish) {
pos = 1;
start = readyTime; // 起始时间 就等于 就绪时间
} else if (readyTime + computationCost <= sched.get(0).start) {
// 当前任务的就绪时间 + 计算代价时间,比vm上那个时间片的起始时间还要早
pos = 0;
start = readyTime; // 起始时间 仍等于 就绪时间
} else {
// 当前任务从就绪时间开始,与vm上已有的时间片存在重叠
pos = 1; // 执行“后来者靠后”的策略
start = sched.get(0).finish; // 起始时间 等于 vm上那个原有时间片的结束时间
}
if (occupySlot) {
// 若“抢占”
sched.add(pos, new Event(start, start + computationCost));
}
return start + computationCost;
}
// Trivial case: Start after the latest task scheduled
// 一般情况:在计划的最新任务之后启动
// 起始时间 等于 max(就绪时间,vm上最末时间片的结束时间)
start = Math.max(readyTime, sched.get(sched.size() - 1).finish);
// 结束时间 等于 起始时间 + 计算代价时间
finish = start + computationCost;
// vm上原有的最末
int i = sched.size() - 1;
// vm上原有的倒数第二
int j = sched.size() - 2;
// 当前任务即将放入的位置
pos = i + 1;
// 循环,挨个往前尝试,看能否插入vm上原有的两个任务之间
while (j >= 0) {
Event current = sched.get(i);
Event previous = sched.get(j);
// 就绪时间比前一任务的完成时间还要晚
if (readyTime > previous.finish) {
// 就绪时间+计算代价时间比后一任务的起始时间还不晚
if (readyTime + computationCost <= current.start) {
// 那么,该任务恰好可以放置于这两个任务之间
start = readyTime;
finish = readyTime + computationCost;
}
break;
// 跳出while循环
// 前一任务完成时,当前任务还未就绪,则不可能将当前任务放到前一任务之前,不必再继续循环了
}
// 前一任务的完成时间 + 当前任务的计算代价时间 不晚于 后一任务的起始时间
// 在完成前一任务之后执行当前任务,可行
// 注意这里没有比较previous.finish与readyTime的先与后
// 但因为前面有break,所以previous.finish一定不小于readyTime
// 即当前任务就绪时,前一任务还没有完成
// 亦即,前一任务完成时,当前任务一定已经就绪了
if (previous.finish + computationCost <= current.start) {
// 如果前一任务的完成时间 + 计算代价开销 不晚于 后一任务的起始时间
// 即,当前任务可以“插队”在前后两任务之间直接执行
start = previous.finish;
// 当前任务的起始时间 = 前一任务的完成时间;
finish = previous.finish + computationCost;
// 当前任务的完成时间 = 起始时间 + 计算代价开销 = 前一任务的完成时间 + 计算代价开销
pos = i;
}
i--;
j--;
}
if (readyTime + computationCost <= sched.get(0).start) {
// 如果当前任务可以放在vm的最开始
pos = 0;
// 最开始的位置的标号是0
start = readyTime;
// 最开始的起始时间,就是当前任务的就绪时间
// 如果“抢占”
if (occupySlot) {
sched.add(pos, new Event(start, start + computationCost));
}
return start + computationCost;
}
// 如果“抢占”
if (occupySlot) {
sched.add(pos, new Event(start, finish));
}
return finish;
}
}
WorkflowSim HEFT 代码注解
最新推荐文章于 2024-06-16 09:46:44 发布