前言
这算是本人第一次参加正式参加华为的比赛,去年在外围观望各位大佬,参加了比华为难度低一些的中兴算法笔试,今年的题目相对于往年难度较低,对于数据结构有一定的高度,但最重要的还是多种多样的解题思路,可能同样的算法构建难度,较好的解题思路缺能起到很好的效果。本次的成绩是初赛:练习赛第10,正式赛第11;复赛:练习赛第13,正式赛第17。由此可以看出该赛区的竞争激烈,本队维护的代码基本框架是具有一定普适性,但要得到更高的分数需要在某些方面进行约束 后期更改的数据集的对于维护的代码不太优化,慢慢的变成了调参细节优化。最后的结果还是比较满意的,拿到了一个奖项和笔试的绿卡,既提升代码能力,也有所收获。
本届赛题:
简述:云计算场景中的资源的规划和调度优化问题。预设有不同类型的服务器与虚拟机种类,给定一段时间内用户的申请、删除虚拟机请求序列,要求优化虚拟机迁移、云服务器购买、虚拟机部署等步骤,使得在满足用户需求的情况下总成本最低。
详细题目可到华为官网寻找:链接
小技巧:对于这类比赛本地有判题器和可视化软件,可以大大的提高版本迭代和优化的效率;
1)、判题器的编写:需根据题意来进行逻辑上的编写,与规则的错误判读并输出相应的错误;
如以下的几段代码检测资源是否异常:
//检查合理性
void check_ret_exe() {
int len = Server_list.size();
Server_cpures = 0;
Server_ramres = 0;
for (int i = 0; i < len; i++) {
Server_cpures += Server_list[i].node_a.cpu_now + Server_list[i].node_b.cpu_now;
Server_ramres += Server_list[i].node_a.ram_now + Server_list[i].node_b.ram_now;
if (Server_list[i].node_a.cpu_now<0 || Server_list[i].node_a.cpu_now>Server_list[i].node_a.cpu_max) {
fout << "服务器" << Server_list[i].id << "A节点CPU资源不合理" << endl;
}
if (Server_list[i].node_b.cpu_now<0 || Server_list[i].node_b.cpu_now>Server_list[i].node_b.cpu_max) {
fout << "服务器" << Server_list[i].id << "B节点CPU资源不合理" << endl;
}
if (Server_list[i].node_a.ram_now<0 || Server_list[i].node_a.ram_now>Server_list[i].node_a.ram_max) {
fout << "服务器" << Server_list[i].id << "A节点RAM资源不合理" << endl;
}
if (Server_list[i].node_b.ram_now<0 || Server_list[i].node_b.ram_now>Server_list[i].node_b.ram_max) {
fout << "服务器" << Server_list[i].id << "B节点RAM资源不合理" << endl;
}
}
vm_cpures = 0; vm_ramres = 0;
for (auto i : VMware_list) {
vm_cpures += i.second.cpu;
vm_ramres += i.second.ram;
}
if (vm_cpures != Server_cpures) fout << "服务器与虚拟机cpu资源搭配不合理" << endl;
if (vm_ramres != Server_ramres) fout << "服务器与虚拟机RAM资源搭配不合理" << endl;
}
运行脚本:运用一些运行时间计算、输入输出重定向,预处理加速优化,python脚本进行代价计算等,来提供开发的效率。
运行时间计算及输入输出重定向:
function getTiming() {
start=$1
end=$2
start_s=$(echo $start | cut -d '.' -f 1)
start_ns=$(echo $start | cut -d '.' -f 2)
end_s=$(echo $end | cut -d '.' -f 1)
end_ns=$(echo $end | cut -d '.' -f 2)
time=$(( ( 10#$end_s - 10#$start_s ) ))
echo "$time s"
}
g++ -g -o 1 codev1.cpp
start=$(date +%s.%N)
./1.exe < training-1.txt > training-1_out.txt
end=$(date +%s.%N)
runtime=$(getTiming $start $end)
echo "runtime: "$runtime
预处理加速优化:
#pragma GCC optimize(3,"Ofast","inline") //详细的介绍可自行查阅
python脚本进行代价计算:
在shell脚本中进行一些复杂的计算与处理较为麻烦,一般使用python来调用高级库开发效率较高
本项目只需进行一些简单的计算:
#coding=utf-8
cost1 = 0
cost2 = 0
cost3 = 0
cost4 = 0
for i in range(2):
f = open('log_'+str(i + 1) + '.txt', 'r', encoding="utf-8")
s1 = f.readline().strip().split(':')
s2 = f.readline().strip().split(':')
s3 = f.readline().strip().split(':')
s4 = f.readline().strip().split(':')
cost1 += int(s1[1],base=10)
cost2 += int(s2[1],base=10)
cost3 += int(s3[1],base=10)
cost4 += int(s4[1],base=10)
print('数据集', i + 1, '总成本:', s1[1])
print('数据集', i + 1, '硬件成本:', s2[1])
print('数据集', i + 1, '能耗成本:', s3[1])
print('数据集', i + 1, '迁移次数:', s4[1])
print('总成本:', cost1)
print('硬件成本:', cost2)
print('能耗成本:', cost3)
print('总迁移次数:', cost4)
数据的可视化使用python调用一些画图库,图行库当然是最好的选择,这部分较为复杂也不是重点就不细说了。
关于比赛思路
本赛题可分为四个处理过程去考虑:1.建立数据结构 2.购买服务器 3.分配虚拟机 4.迁移虚拟机
总体思路:前期通过查看了装箱问题、背包、整数规划、还要华为往年真题(如18中就有装箱问题)、百度18年也举办过相关的调度算法比赛,在其中有很多可借鉴的东西。最终确定的基本思想:使得总成本低——>服务器的使用率高(需要购买的服务器少了,空闲的服务器也多了),从而问题的目标函数转化为提高服务器使用率。对于背包与贪心算法进行比较,背包的利用率会比贪心的高,但其算法复杂度高,对于大的数据集,是容易超时的。这时选用贪心的想法,贪心是一种策略,具体的代码实现要有贪心的目的(提高服务器使用率)与相应的评价函数(对于放入该服务器以后的资源利用率的评价函数)。经过试验与查找质料,确定了降序最佳适应法的贪心策略装箱实现代码架构。在迁移方面也是以提高服务器的利用率为目标,思路大致为将资源利用率较小的服务器里的虚拟机放入其他服务器。在购买服务器方面,由于采用的贪心算法,故服务器的ram与cpu的资源要较为均衡。
1.建立数据结构
建立如下的服务器的类,方便以后的维护和扩展;
//Server 服务器类 自己购买的服务器类
class Server
{
public:
int id = 0; //服务器编号
string Version = ""; //服务器型号
int cpu = 0; //CPU核数
int ram = 0; //内存大小
int HDcost = 0; //硬件成本
int Ecost = 0; //每天的能耗成本
Server_node node_a;
Server_node node_b;
vector<int> vmID;//该服务器上部署的虚拟机列表
//方法
void add_VMware(VMware a, int status_code);
void del_VMware(VMware b);
int check_VMware(VMware c);
int check_VMware1(VMware c);
int check_VMware2(VMware c);
void set_Server(Server_type d);
};
//使用集合进行储存
vector<Server> Server_list;
2.购买服务器
通过查看数据集中预设的服务器类型,可发现服务器的cpu与ram的资源配置大部分,购买服务器影响的是资源大小和成本,最理想的是按照单位ram与cpu最小成本,即高性价比。但实际情况却不是的,经过多次实验,以能耗进行比较是最佳方案。考虑到总体的能耗成本和开机天数有关联,后续可以进行考虑。同时也要注意购买时服务器的资源要均衡。
比较代码:
//挑选服务器排序规则
bool Server_types_cmp(const Server_type& a, const Server_type& b) {
return a.Ecost < b.Ecost;
}
//挑选服务器
能放下就放下,二种资源尽量要均衡
3.分配虚拟机
降序最佳适应法:对虚拟机进行从大到小排序,以该虚拟机放入现有资源的服务器以后的评价函数进行记录,循环可放入的全部服务器,选择评价最高的进行放入操作。
对于请求的处理:由于删除请求的存在,请求的整体排序是不符合规则的,因此在不打乱删除请求的前提下,对全部请求按照删除请求进行分层排序。
评价函数:
//返回评价数值
double check_add(VMware VMware_temp, int i, int flag) {
double c5 = ((Server_list[i].node_a.cpu_now + VMware_temp.cpu)*l1 + (Server_list[i].node_a.ram_now + VMware_temp.ram)*l2)*1.0 / (Server_list[i].node_a.cpu_max*l1 + Server_list[i].node_a.ram_max*l2);
double c6 = ((Server_list[i].node_b.cpu_now + VMware_temp.cpu)*l1 + (Server_list[i].node_b.ram_now + VMware_temp.ram)*l2)*1.0 / (Server_list[i].node_b.cpu_max*l1 + Server_list[i].node_b.ram_max*l2);
double c7 = max(((Server_list[i].node_a.cpu_now + VMware_temp.cpu / 2)*l1 + (Server_list[i].node_a.ram_now + VMware_temp.ram / 2)*l2)*1.0 / (Server_list[i].node_a.cpu_max*l1 + Server_list[i].node_a.ram_max*l2), ((Server_list[i].node_b.cpu_now + VMware_temp.cpu / 2)*l1 + (Server_list[i].node_b.ram_now + VMware_temp.ram / 2)*l2)*1.0 / (Server_list[i].node_b.cpu_max*l1 + Server_list[i].node_b.ram_max*l2));
double cost_temp;
if (flag == 1) cost_temp = c5;
else if (flag == 2) cost_temp = c6;
else {
cost_temp = c7;
}
return cost_temp;
}
4.分配虚拟机
思路:对现有的全部虚拟机进行资源利用率选择,选择利用率最低的服务器,将其服务器中的虚拟机拿出来安装插入的思路重新放入。从而达到整合资源,提供整体服务器资源利用率。
注意:迁移时间复杂度较高,需要设置好退出条件
主要代码:
for (int i = 0; i < Server_list.size(); i++) {
useRate[i] = double(Server_list[i].node_a.cpu_now + Server_list[i].node_a.ram_now + Server_list[i].node_b.cpu_now + Server_list[i].node_b.ram_now) / (Server_list[i].cpu + Server_list[i].ram);
if (Server_list[i].vmID.size() == 0) {
useRate[i] = DBL_MAX;//当前服务器的虚拟机都被迁走了,所以利用率为0
vis[i] = false; //已经进行过迁移的就不能被迁移
continue;
}
if (vis[i] == true && min_useRate > useRate[i]) { //找到资源利用率最小的服务器
min_useRate = useRate[i];
min_index = i;
}
}
总结
本人的主要工作:
1、建立可扩展的数据结构,构建baseline,方便后续更新迭代;
2、编写本地判题器与可视化程序用于代码调试;
3、对于虚拟机部署尝试了贪心、动态规划、模拟退火、二维装箱等优化策略,最终采用贪心的思想,以资源利用率为评价函数,借鉴内存分配中的降序最佳适应算法 ,同时剪枝避免不同维度约束情况;
4、综合考虑CPU和 RAM两个维度设计虚拟机迁移策略,设置相应的评价函数,为降低运行时间,将计算分组引入并行计算;
5、对服务器类型、虚拟机类型、请求序列进行数据分析,定义购买服务器的性价比函数;