基于CloudSim Plus的计算卸载仿真设计
1. 前提介绍
仿真框架的实现,主要依托于仿真实体、以及仿真事件,简单介绍如下
1.1 仿真实体
-
继承
CloudSimEntity
类(推荐
)或者实现SimEntity
接口(不建议
)java">public class ExampleEntity extends CloudSimEntity { public ExampleEntity(Simulation simulation) { super(simulation); } @Override protected void startInternal() { // 实体开始启动 } @Override public void processEvent(SimEvent evt) { // 接收仿真事件 } }
-
该实体将交由
CloudSim
进行管理 -
各个实体之间因此也可以通信(通过仿真事件)
1.2 仿真事件
-
发送仿真事件
可以使用
schedule()
和scheduleNow()
来发送仿真事件java">schedule(final SimEntity dest, final double delay, final CloudSimTag tag, final Object data) scheduleNow(final SimEntity dest, final CloudSimTag tag, final Object data)
例如:实体启动时给自身发送事件
java">@Override protected void startInternal() { schedule(this, 1, CloudSimTag.ENTITY_UPDATE); }
主要包括:事件的接收实体,事件的到达时间,事件的标签
例如:
this
表示事件发送给该实体自身,1
表示事件一秒钟后到达,ENTITY_UPDATE
表示事件的标签 -
接收仿真事件
java">@Override public void processEvent(SimEvent evt) { CloudSimTag tag = evt.getTag(); if (tag == CloudSimTag.ENTITY_UPDATE){ // Do Something(在这里可以执行更新状态的逻辑) // 继续发送仿真事件,从而使得仿真实体每秒钟更新自身状态 schedule(this, 1, CloudSimTag.ENTITY_UPDATE); } }
2. 基础设施层
2.1 边缘设备
-
实现方式:继承仿真实体类,然后给自身发送仿真事件,实现自身状态的更新(如:位置,能耗,利用率等)
java">public class DeviceEntity extends CloudSimEntity { public DeviceEntity(Simulation simulation) { super(simulation); } @Override protected void startInternal() { schedule(this, 1, CloudSimTag.ENTITY_UPDATE); } @Override public void processEvent(SimEvent evt) { CloudSimTag tag = evt.getTag(); if (tag == CloudSimTag.ENTITY_UPDATE){ updateStatus(); schedule(this, 1, CloudSimTag.ENTITY_UPDATE); } } private void updateStatus() { // 更新能耗、利用率、节点位置、无线接入点切换等(根据需求自定义) } }
-
自定义的其他组件同理
3. 其他组件
3.1 任务调度算法接口
- 为任务执行计算卸载决策
- 计算节点内进行任务与虚拟机匹配调度
- 任务结果返回事件(便于扩展强化学习算法)
java">public abstract class AbstractAlgorithm extends DatacenterBrokerSimple { protected SimManager simulationManager; public SimManager getSimulationManager() { return simulationManager; } public AbstractAlgorithm(SimManager simulation) { super(simulation.getSimulation()); this.simulationManager = simulation; } /** * 选择任务处理方式:本地处理、边缘处理、云端处理 * @param cloudlet 待处理的任务 * @return 为任务选择的计算节点 */ public abstract AbstractNode makeDecision(Cloudlet cloudlet); /** * 任务在计算节点内的调度策略 * * @param cloudlet 待调度的任务 * @return Vm 为任务选择的虚拟机 */ public abstract Vm scheduleTask(Cloudlet cloudlet); /** * 任务返回: 例如使用强化学习算法计算奖励值 * * @param cloudlet 执行完成的任务 */ public abstract void resultReturn(Cloudlet cloudlet); @Override protected Vm defaultVmMapper(Cloudlet cloudlet) { if (cloudlet.isBoundToVm()) { return cloudlet.getVm(); } Vm mappedVm = Vm.NULL; if (!getVmCreatedList().isEmpty()){ mappedVm = scheduleTask(cloudlet); } if (mappedVm == Vm.NULL) { LOGGER.warn("{}: {}: {} (PEs: {}) couldn't be mapped to any suitable VM.", getSimulation().clockStr(), getName(), cloudlet, cloudlet.getNumberOfPes()); } else { LOGGER.trace("{}: {}: {} (PEs: {}) mapped to {} (available PEs: {}, tot PEs: {})", getSimulation().clockStr(), getName(), cloudlet, cloudlet.getNumberOfPes(), mappedVm, mappedVm.getExpectedFreePesNumber(), mappedVm.getFreePesNumber()); } return mappedVm; } @Override public void processEvent(final SimEvent ev) { super.processEvent(ev); if (ev.getTag() == CloudSimTag.CLOUDLET_RETURN) { Cloudlet cloudlet = (Cloudlet) ev.getData(); resultReturn(cloudlet); } } }
3.2 能耗模型
-
1.主机功耗数据来源:SPEC官网
-
例如型号为
AcerR380F2
的主机功耗数据,其中11个数据分别对于主机利用率从0, 0.1, 0.2, ..., 1
的功耗java">double[] AcerR380F2 = { 63.7, 78.0, 87.0, 96.5, 106, 116, 135, 158, 188, 221, 252};
-
2.为了计算功耗,可以基于主机功耗数据,拟合利用率-功耗函数,从而根据利用率来计算能耗
java">new PowerModelHostFunc(x -> 100.7 + 103.6 * x - 8.7 * x * x);
PowerModelHostFunc
介绍如下:java">public class PowerModelHostFunc extends PowerModelHost { /** * 功率计算函数 */ Function<Double,Double> function; public PowerModelHostFunc(Function<Double, Double> function) { this.function = function; } @Override public double getPower(double utilization) throws IllegalArgumentException { if (utilization < 0 || utilization > 1) { throw new IllegalArgumentException("utilizationFraction has to be between [0 and 1]"); } return function.apply(utilization); } @Override public PowerMeasurement getPowerMeasurement() { final double utilization = getHost().getCpuMipsUtilization() / getHost().getTotalMipsCapacity(); Double dynamicPower = function.apply(utilization); return new PowerMeasurement(0, dynamicPower); } }
-
2.或者,直接基于主机利用率功耗数据,来计算能耗
java">new PowerModelHostSpec(Arrays.stream(AcerR380F2).boxed().collect(Collectors.toList()));
PowerModelHostSpec
介绍如下:java">/** * 原作者代码存在问题: 一般 SPEC 官网提供的功率数据为11个,即为 0,10,20,...,100%的利用率功耗数据; * 而原作者考虑不周到,假设有10个数据,从而造成数组越界. * 更正: * 修改获得当前功率的代码从 powerSpec.size() 改成 (powerSpec.size()-1) * 例如:当前功率为 98%, 则 Math.round(0.98 * 10) = 10, 即取功率为 100% 的利用率数据 */ public class PowerModelHostSpec extends PowerModelHost { public static final int MIN_POWER_CONSUMPTION_DATA_SIZE = 2; private final List<Double> powerSpec; public PowerModelHostSpec(final List<Double> powerSpec) { super(); Objects.requireNonNull(powerSpec, "powerSpec cannot be null"); if (powerSpec.size() >= MIN_POWER_CONSUMPTION_DATA_SIZE) { this.powerSpec = powerSpec; return; } final String msg = String.format("powerSpec has to contain at least %d elements (representing utilization at 0%% and 100%% load, respectively)", MIN_POWER_CONSUMPTION_DATA_SIZE); throw new IllegalArgumentException(msg); } @Override public PowerMeasurement getPowerMeasurement() { final double utilizationFraction = getHost().getCpuMipsUtilization() / getHost().getTotalMipsCapacity(); final int utilizationIndex = (int) Math.round(utilizationFraction * (powerSpec.size()-1)); final double powerUsage = powerSpec.get(utilizationIndex); return new PowerMeasurement(powerSpec.get(0), powerUsage - powerSpec.get(0)); } @Override public double getPower(final double utilizationFraction) throws IllegalArgumentException { final int utilizationIndex = (int) Math.round(utilizationFraction * (powerSpec.size()-1)); return powerSpec.get(utilizationIndex); } }
仿真实验测试
1. 本地处理算法
1.1 实验设定
- 任务只在本地处理,不进行卸载处理
- 假设任务执行时,计算节点的功耗保持不变。因此,
任务消耗的能源
=处理时间
*当前功耗
- 任务处理延迟包括:
排队延迟
+执行延迟
1.2 仿真实验结果
-
计算节点利用率
-
计算节点能源消耗
-
仿真结果数据
{ "algorithmName" : "LocalAlgorithm", "taskSuccessRate" : 0.770289898928766, "avgTaskTotalDelay" : 1.732609574535639, "avgTaskTotalEnergy" : 26.7213677578472, "avgCloudUtilization" : 0.0, "avgEdgeUtilization" : 0.0, "avgDeviceUtilization" : 29.972115384615385, "avgCloudEnergyConsume" : 22776.0, "avgEdgeEnergyConsume" : 35692.8, "avgTaskNetworkDelay" : 0.0, "cloudTaskExecuted" : 0.0, "cloudTaskSuccessExecuted" : 0.0, "edgeTaskExecuted" : 0.0, "edgeTaskSuccessExecuted" : 0.0, "deviceTaskExecuted" : 66092.0, "deviceTaskSuccessExecuted" : 50910.0 }
1.2 随机算法
-
计算节点平均利用率
-
计算节点能源消耗
-
仿真结果数据
{ "algorithmName" : "RandomAlgorithm", "taskSuccessRate" : 0.920377786173026, "avgTaskTotalDelay" : 1.2247280541003729, "avgTaskTotalEnergy" : 18.352311136392565, "avgCloudUtilization" : 38.35336538461539, "avgEdgeUtilization" : 35.13461538461538, "avgDeviceUtilization" : 9.517307692307694, "avgCloudEnergyConsume" : 95546.2, "avgEdgeEnergyConsume" : 67100.36, "avgDeviceEnergyConsume" : 12109.257999999996, "cloudTaskExecuted" : 22044.0, "cloudTaskSuccessExecuted" : 21781.0, "edgeTaskExecuted" : 21924.0, "edgeTaskSuccessExecuted" : 20995.0, "deviceTaskExecuted" : 22207.0, "deviceTaskSuccessExecuted" : 18130.0 }
1.3 贪心算法
-
计算节点平均利用率
-
计算节点能源消耗
-
仿真结果数据
{ "algorithmName" : "GreedyAlgorithm", "taskSuccessRate" : 0.9416770318235598, "avgTaskTotalDelay" : 1.2437790398350788, "avgTaskTotalEnergy" : 17.790207236463743, "avgCloudUtilization" : 28.44951923076923, "avgEdgeUtilization" : 30.355769230769226, "avgDeviceUtilization" : 16.62211538461539, "avgCloudEnergyConsume" : 72169.3, "avgEdgeEnergyConsume" : 63736.02, "avgDeviceEnergyConsume" : 15318.622999999994, "cloudTaskExecuted" : 2874.0, "cloudTaskSuccessExecuted" : 2397.0, "edgeTaskExecuted" : 6510.0, "edgeTaskSuccessExecuted" : 5667.0, "deviceTaskExecuted" : 56542.0, "deviceTaskSuccessExecuted" : 54017.0 }