2018华为软件精英挑战赛——放置篇

上篇写了预测的部分,这篇写写放置的算法。

前期的放置算法

贪心算法

初看放置问题,我是从背包问题的角度考虑,因此我的最初的想法是贪心。每个服务器看作是不同容量的背包,虚拟机是物品,虚拟机和物理机都有CPU和内存两个约束维度,问题是要在虚拟机全部装完的情况下,使得两个维度的平均资源利用率最高。

贪心的思路很简单:把需要放置的每个虚拟机根据其CPU和内存属性按照从大到小排序,然后依次填入到物理机中。如果第一台物理机可以填入,则填入;如果超出物理机容量无法填入,则填入下一台物理机中,若所有物理机都无法填入,则新增加一台物理机,再填入。

其中有几个细节需要考虑:
1、如何根据两个维度给不同的虚拟机排序?
2、复赛中出现多种物理机类型怎么选择?
排序的一个解决办法是给内存除以一个系数后,按照虚拟机CPU和内存之和的大小排序;
初赛阶段只有一种物理机,不存在物理机选择的问题,而复赛可以采用随机算法迭代的策略选择物理机种类,或者根据剩余虚拟机CPU之和与内存之和的比例选择较为接近的物理机种类,我自己在复赛中没有尝试,只提供一种思路。
贪心算法的效率比较高,复杂度远小于O(nN),N是虚拟机个数,n是物理机个数。

贪心思想的动态规划

首先贪心找到的解很难是最优解,我接着考虑的算法是动态规划。因为背包的数量不是普通背包问题里的1个,而是可能有多个背包,因此并不是完全的动态规划:

首先考虑第一个背包,那么根据动态规划,它是存在最优解的,算法复杂度为:O(NCM),N是虚拟机个数,C是物理机CPU数,M是物理机内存数。
采用回溯的方法,将此背包最优解的虚拟机依次取出,再将剩余虚拟机装入一个新的背包,直到全部虚拟机装完。

我在实现了这个算法之后,一直以为这就是最优解,后来我突然想明白,这本身还是一个贪心算法,因为第二个背包的最优解是在第一个背包剩余的虚拟机条件下产生的,这就背包的解产生了一个先后顺序,这样就会使后面背包的利用率必然低于前面背包的利用率,也就是说某个背包的最优解,只是在当前剩余虚拟机中的最优解,那它就未必是全局的最优解
因此我把它叫做具有贪心思想的动态规划,整体的复杂度为:O(nNCM),n为物理机个数。在虚拟机数量较大的情况下,复杂度过高,因此被我放弃。

交换合并法

这是我根据实际问题设计的一种算法,我把它称作交换合并算法。主要包含如下流程:产生初始解、交换、合并、填充。提出这个算法是基于如下观察:在贪心算法找到的解中,容易发现某些物理机的单个维度已经填满,而另一个维度却还有很大空间没有使用,这样就没法填入新的虚拟机,造成资源的浪费,那么能否在这个解的基础上,产生更优的解呢?具体步骤如下:

1、使用上面提到的贪心算法,产生一个初始解;
2、如果大于迭代次数,则执行5,否则迭代次数加1,在当前解中,除最后一个物理机之外,随机选择两个不同的物理机P1,P2,分别在这两个物理机中随机选择一个的虚拟机V1,V2,并且保证两个虚拟机种类不同;如果 P1 - V1 + V2 、P2 - V2 + V1两个维度都满足约束条件,那么就交换两个物理机中的虚拟机种类,即P1中V1变成V2,P2中V2变成V1;
3、将当前解最后一个物理机中的所有虚拟机,在满足约束条件的前提下依次填入其他物理机中,并在最后一个物理机中移除,如果最后一个物理机为空,这删除这个物理机,返回2,否则,执行4;
4、随机选择一个物理机(除最后一个物理机之外)P,依次选择最后一台物理机中的虚拟机V,如果P中存在v(v != V),并且 P - v + V 满足约束条件,且v至少存在一个维度小于V,则交换v和V,即P中的v变成V,最后一个物理机中的V变成v,返回2;
5、在满足约束条件下,随机在最后一台物理机中填入某种虚拟机(需要相应增加此种虚拟机的预测数量),直到无法填入,算法结束。

算法的目的就是为了使各个物理机的两个维度都尽量达到最满,使资源利用率尽可能的高。
一、为了提高算法的效率,我建立了各种虚拟机属性及物理机属性的索引表,从而能够使用数组直接访问。
二、算法在迭代几次之后,效率就会大幅下降,对于某些大的虚拟机很难产生交换,因此可以作如下改进:步骤2中,修改为每次在物理机中各取出两个虚拟机:V11,V12,V21,V22,然后判断是否满足约束条件,如果满足则同时交换。
三、在不断产生更优解的过程实际上是贪心,可以使用模拟退火法改进,增加寻找全局最优的可能性。
在实际数据中,算法的表现还是不错的,基本可以达到98%以上的平均利用率(使用了步骤5)。

决赛阶段放置算法

遗传算法GA

交换合并算法在决赛的表现一般,在新的约束下,寻找全局最优解的能力被减弱。在参考了很多资料后,我最后决定使用遗传算法重写放置问题,事实证明效果确实更好,利润率可以稳定在89%以上(利润率存在一个上限,不可能达到100%)。

编码方式

遗传算法的首要问题是个体基因的编码方式。
我使用物理机作为个体的基因,记录每个物理机的类型,每个物理机中所有虚拟机的类型,以及每个物理机的利润率。

初始解

考虑到解空间的大小,以及运算速度,种群大小设置在60左右。
为了保证初始解的全局性,使用以下方式产生初始种群:

对于每个个体来说:将所有需要放置的虚拟机随机打乱顺序,依次放入到当前物理机中,如果可以放下,则放入,如果不满足约束条件,则随机产生一个新的物理机,放入到此物理机中(另外,如果是特殊种类的虚拟机,则按照其对应的物理机类型放入即可),并将此物理机加入到个体中。

适应度

首先以每个个体的利润率作为其适应度,其值越大则个体越优秀。为了提高效率,可以先算出所有虚拟机的价格,这样避免了重复计算。然后计算个体中每个物理机的利润率,作为每个物理机的适应度,其值也是越大越优,并将个体中所有物理机按照其适应度从大到小排序(个体不需要排序)。

选择算子

选择算子对于种群的进化意义重大,是进化快慢的原因之一。
首先,轮盘赌选择算子并不适合这个情景。原因如下:个体与个体之间的适应度差异很小,大概范围在60%到90%之间,轮盘赌算子有较大概率选择较差的个体作为父代,这样会减慢进化的速度。
我使用的是锦标赛选择算子,大概思路是随机选择两个个体,选择较优的个体作为父代,舍弃较差的个体,并且保证种群规模不变。这样可以保证优秀个体有较大概率保留下来。

交叉算子

交叉算子是遗传算法寻找全局最优解能力的保证,好的交叉算子可以使算法搜索的解空间更大,更不容易过早陷入局部最优。
交叉算子的原则是以一定概率,选择父代中两个个体,使其部分基因发生交换,产生两个新的个体(新的子代)。我的做法是在父代中,按照概率选择两个个体,随机选择二者前半段(物理机已排序)的一个物理机互相插入,这样可以保证父代中基因的优秀特性保留到子代中。
需要注意一个细节:每个子代中新插入的物理机会使其总的虚拟机增多,保证总的虚拟机相同是可行解的必要条件。因此子代插入新的物理机后,还需要从其他物理机中减去新增加的虚拟机种类,具体可以从个体的末尾物理机开始减起(末尾物理机的适应度更小),保证了总的虚拟机不变后,可以使用上述的交换合并法优化末尾的物理机,当然要注意特殊的虚拟机必须放置在特定的物理机中。

变异算子

变异算子的作用是增加遗传算法寻找局部最优解的能力。原则是以一个较小的概率选择一个个体,使其少量基因发生变异,产生新的个体。我的做法是以一定概率选择一个个体,并随机选择其后半段的一个物理机,改变其类型,再使用交换合并法重新填装(注意特殊类型的虚拟机的处理)。

其他优化

· 精英主义
为了防止迭代过程中每一代的最优解被交叉、变异破坏,可以使用精英主义原则,将父代中的最优解保存下来,并直接传递给子代
· 灾变
灾变是为了解决算法过早地陷入局部最优解中,使其跳出当前的局部解,从而在全局范围内寻找其他更优解。我的个人理解是:灾变不是否定全部个体,如果否定了当前种群所有个体,其实就等价于重新开始一次遗传算法,效率是非常低下的(亲自尝试过)。
如果算法非常容易陷入局部最优,那么应该从交叉算子和选择算子去考虑,而不是依赖灾变
灾变开始的判断:可以根据连续迭代无法产生更优解的次数作为开始的标志,比如连续10代没有产生更好的解,则进行灾变。
至于灾变的方法,以下仅供参考:1、在当前解破坏一部分较好的个体,使用新的(随机)个体替代;2、将种群按照适应度排序,从中选择一个序列,使用新的个体替换这个序列。
注意:1、灾变的效果较难保证,把握好灾变的时机和策略较难;2、依然可以使用精英主义原则,将父代最优解保存下来。

算法具体流程如下:


遗传算法

你可能感兴趣的:(2018华为软件精英挑战赛——放置篇)