前言

很开心能够参加这次2021华为软挑的比赛,喜欢这样充满挑战性,在任务驱动下不断学习,主动思考解决问题的方法的经历,以及与一群小伙伴讨论问题,一起学习成长进步的感觉,收获满满,真的很棒!
于是在此记录下一点心得,留下我曾经来过的痕迹,为自己这些年来参加的大大小小各式各样的比赛再添一笔,也留下这一段收获满满、值得回味的记忆吧~

赛题解读

今年的赛题是关于对云上资源的规划和调度问题。给了一系列有着不同CPU、内存资源量的服务器,根据用户请求的不同 CPU与内存量的虚拟机资源分配合适的服务器。

资料收集

首先到处查询各种资料,有讲到这个问题的任务可以看成一个变种的多维装箱问题,也就是一个np难问题,比较难找到一个精确的解,但是我们可以找出一个趋近于最优解的方案。

解读参考:
与赛题中服务器选型和请求调度这两个问题最为相似的运筹优化模型是Vector Bin Packing(多维装箱问题),该问题是传统Bin Packing问题的一个变种。该问题模型与赛题中的定义非常相似,如果不考虑虚拟机的删除和服务器的能耗成本,那么服务器选型 + 请求调度这两个问题与该模型是完全等价的。而如果加入虚拟机的删除,则该问题会变成动态的版本,对应于Dynamic Bin Packing的相关扩展模型。

以及参考里面还提到了阿姆达尔定律?不太懂,于是也去查了一下:

Bin packing(装箱问题)
Problem:给定n件物品和k个箱子,每一个箱子的容量为1,每一件物品的大小w为(0,1),要求使用最少数目的箱子来装上全部的物品。
这个问题是NPC问题,只有approximation(近似)算法。

B站上对背包问题的讲解:【动态规划】背包问题

思路分析

此问题最关键的步骤主要有两个部分:一方面是根据需以及请求的资源量合理的选择适合的服务器类型,另一方面是为各个虚拟机请求找到合适的服务器进行分配。

选择服务器的部分

各表建立

我们分析了官方所给出的training-1与training-2两个样本的虚拟机请求数据。首先服务器的属性有包括服务器类型、CPU资源、内存资源、机器售价、每日花费。虚拟机的属性包括虚拟机类型、CPU资源、内存资源、单双节点。我们按照提议设计建立了:

  1. 服务器类型查询表
  2. 虚拟机类型查询表
  3. 已有服务器资源表
  4. 已分配好服务器的虚拟机表
  5. 纯粹统计服务器购买数的表
  6. 虚拟机迁移表
  7. 创建请求表
  8. 虚拟机请求表
  9. 虚拟机-服务器对应分配表
  10. 服务器ID编号查询表
  11. 服务器原始表(其实只是一开始记录读入服务器信息的表而已,也可不使用)
  12. 虚拟机原始表(其实只是一开始记录读入虚拟机信息的表而已,也可不使用)

按顺序的各表定义如下:

//服务器信息
struct Server
{string type; //服务器型号int cpuA;    //A节点cpu核数int memoryA; //A节点内存大小int cpuB;    //B节点cpu核数int memoryB; //B节点内存大小int price;   //硬件成本int cost;    //每天能耗成本//   int id;                 //服务器编号
};
//虚拟机信息
struct VM
{string type; //虚拟机型号int cpu;     //cpu核数int memory;  //内存大小int node;    //单节点:0;双节点:1//   int id;                 //虚拟机编号
};
//请求信息
struct Request
{string order; //请求指令:add | delstring type;  //虚拟机型号int id;       //虚拟机编号
};
//迁移表、创建请求表
struct Migration
{int vmId;     //虚拟机编号int serverId; //目的服务器编号string ab;    //目的服务器节点
};unordered_map<string, Server> mapServer; //根据server.type查询server信息
unordered_map<string, VM> mapVm; //根据vm.type查询VM信息
set<pair<int, Server>, Comp> serverResource; // 记录每个服务器的剩余资源。其中pair.first是服务器id,pair.second记录剩余资源
unordered_map<int, VM> haveVm; //根据vm.id查询所放置在服务器上的该虚拟机信息
unordered_map<string, int> purchase[T_NUM]; //记录第t天购买的服务器型号与数量
queue<Migration> migration[T_NUM];          //虚拟机迁移队列(虚拟机ID,目的服务器ID)or(虚拟机ID,目的服务器ID,目的服务器节点)
unordered_map<int, Migration> create;       //创建请求表,根据vm.id查询虚拟机部署在服务器上的关联信息
queue<Request> request[T_NUM]; //第t天的请求队列
queue<Migration> assign[T_NUM];             //分配信息 (服务器ID)或(服务器ID,部署节点)队列
unordered_map<int, decltype(serverResource)::iterator> serverByID;
// 通过服务器ID找到其在serverResource中的iterator
Server server[SERVER_NUM];
VM vm[VM_NUM];

待选服务器性价比排序方案

一开始想的是通过服务器CPU资源与内存资源跟机器售价与每日花费之比进行排序,找出性价比最高的一款或几款服务器进行循环购买。于是来整理数据,做图表进一步分析,却在其中发现其实机器售价,每日花费与资源值之间是存在一个线性比例关系的,资源越多,对应的售价与每日花费一样也会增多,且机器售价与每日花费之间的比例也是呈线性变化的。也就是说以这样的方式来排序服务器,对于降低成本结果很可能并没有非常大的帮助。
反倒是虚拟机与服务器的共有属性里面CPU资源与内存资源之间的比例存在很有趣的关系,不同类型的服务器这二者的比例差距大相径庭,于是我决定改变策略,通过分析虚拟机,请求队列里所有虚拟机的CPU资源与内存资源比例作为参考系数 对服务器进行排序,找出与此比例系数最相近的服务器依次排序。于是开始尝试编写算法,这个方法果然好用,一下子让整体成本下降了很多。

时间性能优化

其中对服务器进行排序的时候也多方考虑更加省时的算法。因为项目要求在90秒内需要运行完毕,最开始的时候,我们的程序其实一直是要运行超时的。一开始设计数据结构的时候,都只是简单地使用map来设计服务器表、虚拟机表等各项表。map一种根据〈key, value〉进行键值对映射存储数据的数据结构,默认时会以key的值进行排序,内部实现是红黑树的原理,可进行自动排序。他的有序性也是map的优势所在,但对于查询来说却没有unordered_map更省时间。 unordered_map内部是以哈希表的数据结构来进行存储,虽然建立的时候会有一些耗时,但查询的时候是相当快的,时间复杂度只需O(1)。map是有排序的好处,然后我们当时的键值是设置成了服务器类型名称来进行索引查询,我们还是得重写按我们算法的排序方式,根据CPU/内存这两大属性的value值得出比例进行排序,所以map的优势也就对于我们没有什么用处了。我们把map改为unordered_map之后,速度果然大幅度提升了,本地运行只需要20多秒即可完成,与原先超时90秒还迟迟不能完成的速度相比,完全是大大提高了。顺便把各种能改成unordered_map的map都一并换了,惊讶的发现调用排序函数前的顺序更加随机之后,成本居然也莫名降低了不少,也可能是偶然吧,真是双喜临门,可喜可贺,吼吼~

排序算法及相关数据结构优化

啊,对了,在这里要感谢队友进行的排序算法优化。让服务器表可以按unordered_map进行CPU比memory这两个value值属性的排序,同时又实现了可以按照服务器ID号对服务器进行查询的功能,这为后续当遇到删除虚拟机的请求指定时,对照服务器虚拟机分配表中,服务器ID进行对应进行的查找,提供了很大的便利。以及使用set集合的方式来记录每个服务器的剩余资源,还为此编写了添加已有服务器资源表、更新已有服务器资源表的方法,通过删除、更改再插入的方式更新,使得按照服务器ID号进行查询的已有服务器资源表依旧可用借助set默认的排序规则按ID进行排序,更适合于实际操作的要求。

class Comp
{public:bool operator()(const pair<int, Server> &lhs, const pair<int, Server> &rhs) const{auto srcLhs = lhs.second.cpuA + lhs.second.cpuB + lhs.second.memoryA + lhs.second.memoryB;auto srcRhs = rhs.second.cpuA + rhs.second.cpuB + rhs.second.memoryA + rhs.second.memoryB;if (srcLhs == srcRhs)return lhs.first < rhs.first;elsereturn srcLhs < srcRhs;}
};
set<pair<int, Server>, Comp> serverResource; // 记录每个服务器的剩余资源。其中pair.first是服务器id,pair.second记录剩余资源unordered_map<int, decltype(serverResource)::iterator> serverByID;
// 通过服务器ID找到其在serverResource中的iteratorvoid addServer(int id, Server s)
{serverByID[id] = serverResource.insert({ id, s }).first;
}void update(int id, Server s) // 通过删除、更改再插入的方式更新
{auto toUpdate = *serverByID[id]; // 将要更新的服务器toUpdate.second = s;             // 在这里改变toUpdate的值serverResource.erase(serverByID[id]);serverByID[id] = serverResource.insert(toUpdate).first;
}

服务器预购方案优化

以及在开始所有的请求之前,我会先按照进行排序处理后的服务器列表默认预购一批服务器,一开始选的是预购前10%的服务器,这样数量的服务器当然是可以满足测试数据的要求的,后来又比较了购买前5%、4%、6%等比例的服务器,发现预购前5%的服务器可以使成本进一步优化降到更低。

服务器前两位预设方案的探索与优化

后来,在实验中发现,依次根据每次来的虚拟机请求进行服务器分配时,当最优比例服务器不符合时才往下寻找次优的服务器,这样的策略处理数据是从整体来看,主要就是排名在前两位的服务器被购买就已经可以符合整个的需求了,也就是说在大多数情况下最关键的就是服务器列表状态是在前两位的服务器。于是开始研究进一步的优化策略,将排名第2位的服务器进一步优化设置。大多数情况下,CPU比慢慢锐的比例在服务器表中寻找,未必能找到百分百匹配的,总是会比所得的标准比例略高或略低于是思考决定,在之前按照性价比排序后,取前7%的服务器进一步优化考虑,将排名第二的服务器设置为这前7%的服务器中,cpu/内存比尽可能与排名第一的服务器互补的服务器,即这两者服务器加权后的服务器最接近所得的虚拟机需求的CPU/内存比例。这样一来所得的成本果然又被优化降低了不少。(不过后来调程序的时候,不知哪里出了问题,上传上去总是报资源分配不足之类的问题也一直没调通,不过这是后话了,经过本地测试的确是性能提高了不少。若是上传能够调得通的话,排名应该又能上升不少吧,有点小遗憾,但是学到了不少知识,还是挺有收获的啦)

分配虚拟机对应服务器的部分

我们决定用装箱问题的思路,首先按照我们当前拥有的服务器,按照剩余资源量的大小从小到大进行排序。然后依次处理各条虚拟机请求,按照贪心算法的方式,按照排序好的服务器剩余资源表(即所拥有的箱子)依次进行尝试放入,每次将单个箱子所填充物虚拟机器请求资源价值量最大化,从而使全局资源分配也达到最优。这个思路也是让我们在初赛上传后,成本能够下降到大致在15亿多的水平的一个很关键的思路。后来发现其实这正是装箱算法里面的一个优化方案,误打误撞居然使用了理论上最优的的方法,哈哈。
装箱问题知识点参考资料

调试

C++指定输入格式

问题:需输入的数据过多,控制台窗口缓存不足,用复制粘贴的方式难以在控制台一次性读入全部测试数据(什么?要我每次测试都手打几千行测试数据?!!那是不可能哒┗|`O′|┛ 嗷~~),在控制台窗口属性页设置缓存大小最大也只能设置为999,还是不够用。
解决:后来决定使用重定向的方法,读取文件里的数据作为输入,虽然比赛提交格式是要求不额外读取外部文件的,但思维不能太拘泥教条墨守成规嘛,实验时还是可以用重定向读取进来大量数据方便测试,待准备就绪后再把对应那几句代码注释掉再上传即可。查询资料学习到,其实重定向代码也并不复杂,用来测试操作还是很方便的啦~
C++输入输出重定向(3种方法)

对后续优化的思考

对于此次服务器资源分配与调度问题的最优化方案求解,其实如果当时自己早点安排,把握好时间分配,不至于最后阶段时间那么紧的话,还可以进一步优化迁移调度算法。在每一天的请求处理完后,以及新一天请求到来之前,考虑虚拟机迁移调度的方案,当前我们的算法虚拟机迁移量为0,看到排行榜上在我们前排的队伍里迁移调度量为0的队伍是极其极其少的,要是加上这一部分的优化,效果应该会更好些。其实,中途也有稍微尝试写了点迁移调度算法,但也由于我们一开始整体大的思维设计的分配方案用的是贪心算法,所以每次处理到来的虚拟机请求时已经是让每次请求达到最优的效果,以每一次的局部最优从而达到整体全局最优的效果。所以当del类型虚拟机请求量较小,不足以让足够多的已使用服务器都出现较大合适的剩余空间,达到让其中一些所耗资源量较小的已用服务器上的虚拟机全部迁移到其他已用服务器,让自已空闲出来的话,迁移并不能达到很好的节约未使用服务器的每日花费的目的。实验测试也的确如此,甚至居然出现越迁移花费越多的情况?!!也可能跟我们处理虚拟机请求时总是一条条按队列顺序依次处理,而不是先从全局一口气考虑好所有情况进行分配有关,不过那样的话对为了请求都是默认已知的(初赛这样是可以完成任务的,不过后来我也持续关注了一下后期复赛要求的任务进化,需要对虚拟机请求进行一定的预测,也就是难以使用一开始就一口气分析完全部请求的方案了),后来也一直没想到更好的方案。

感悟

我觉得自己还是超级喜欢这样类型的比赛的,在这段时间里面围绕一个具体的问题,自主对题目进行整理分析,并在网上查找资料,和同学们讨论,对数据分析,然后设计出自己的算法 不断的调试优化代码,看着排行榜上的成绩,不断与大家比拼……短短的这段时间,感觉学到了很多的知识,而且一点点的绞尽脑汁想优化的办法,看着排名榜上的排名一丢丢的挪进一点就会很开心,以及还有看到排行榜上前几位的大佬们不断的卷来卷去,群英角逐,彼此不断优化改进算法降低成本,大家努力地打比赛的样子,也真的好可爱,真是很有意思的事情。很开心能够参加这次比赛,喜欢这样充满挑战性,在任务驱动下不断学习,主动思考解决问题的方法的经历,以及与一群小伙伴讨论问题,一起学习成长进步的感觉,收获满满,真的很棒!
啊对了,最终比赛结果是入围上合赛区初赛前64强,队名叫做“蜗牛之家”,排名好像是第46名,虽然中途对服务器性价比优化排序的算法在上传时遇到点问题没调通,没法让排名再前一些,但整个比赛真的学到了不少知识,不亏~ 起初也是比赛中途才临时决定半路参赛,本来都没想到能得什么奖,也就是冲着锻炼自己而去的,这个结果已经超乎预料了,我感觉已经很开心啦~虽然自己就像在蜗壳中的一只小小的蜗牛,能量很小,总是一点一点缓缓向前,但也要不忘初心,迎接挑战,一直一直努力向前呀,前方的路,虽远必至,我们的征途可是星辰大海呐!背上满载知识的蜗壳行囊继续出发,冲鸭( •̀ ω •́ )y

2021华为精英软件挑战赛总结相关推荐

  1. # 2021华为软件精英挑战赛C/C++——build.sh/build_and_run.sh/CodeCraft_zip.sh注释

    2021华为软件精英挑战赛C/C++--build.sh/build_and_run.sh/CodeCraft_zip.sh注释 1.build.sh #!/bin/bashSCRIPT=$(read ...

  2. 2021华为软件精英挑战赛(附赠线下判题器链接)——经历

    2021华为软件精英挑战赛(附赠线下判题器链接)--经历 1.题目解析 本次赛题源自现实的互联网企业面临的问题,怎样购买与部署服务器最便宜! 服务器:不相同型号的服务器有着不同的CPU与不同的内存,每 ...

  3. 2021华为软件精英挑战赛,思路框架,欢迎留言讨论

    2021华为软件精英挑战赛,思路框架. 1.对数据初始化封装 服务器 用集合来进行封装,集合由字符串和数组组成键值对 {"服务器型号":[cpu内核,内存,硬件价格,能耗]} 例如 ...

  4. 2021华为软件精英挑战赛总结(复赛第12名)

    2021华为软件精英挑战赛 github 地址:https://github.com/DougZheng/Huawei_software 前言 无意中看到赛题,觉得很有意思,就匆忙在报名截止前几天上了 ...

  5. 2021华为软件精英挑战赛总结分享

    2021华为软件精英挑战赛总结分享 随着大赛的结束,自己的2021软挑也落下了帷幕,很幸运在自己学业生涯的最后几个月能够再参加一次华为软挑,虽然成绩不是特别好,但已经满足了.这是自己第二次参加华为的比 ...

  6. 2021华为软件精英挑战赛初赛baseline

    2021华为软件精英挑战赛初赛baseline,由ddmm大佬提供的单文件baseline按照工程开发格式改写,改为以类为单位,多文件的格式.同样没有在里面添加任何算法,相当于一个脚手架,帮助大家更快 ...

  7. 2021华为软件精英挑战赛初赛代码及思路

    2021华为软件精英挑战赛训练赛.正式赛思路分享     有幸再次参加了华为软件精英挑战赛(去年由于不知道数据集有坑,导致没能进入复赛,今年决定再来一次弥补去年的遗憾)     今年的赛题相比去年个人 ...

  8. 2021华为软件精英挑战总结

    2021华为软件精英挑战赛总结 今年的软挑最终止步于粤港澳赛区第16名,总成本为16亿3979万6349,赛区第一名总成本为15亿3903万4817. 虽然没进入决赛,但是拿到了华为面试直通卡,也喜提 ...

  9. 华为2021校招【软件开发岗】笔+面试总结

    华为2021校招[软件开发岗]笔+面试总结 文章目录 华为2021校招[软件开发岗]笔+面试总结 个人情况 一.机试 二.综合测试 三.专业面试一 四.专业面试二 五.业务主管面试 六.总结 个人情况 ...

  10. 2022华为软件挑战赛流量管理

    1.题目描述 共有 M 个客户节点和 N 个边缘节点. 在每个时刻,要决策如何把每个客户节点的带宽需求分配到边缘节点. 为了确保调度质量,每个客户节点的需求只能分配到满足 QoS 约束的边缘节点 上. ...

最新文章

  1. android端使用http2.0,android Retrofit2+okHttp3使用总结
  2. ssy-publish
  3. OpenCV EM clustering集群的实例(附完整代码)
  4. 远程登录另一个mysql 数据库_Ubuntu中开启MySQL远程访问功能,并将另一个数据库服务器中的数据迁移到新的服务器中...
  5. svn迁移,备份,重装系统后恢复数据 收藏
  6. ubuntu 任务栏监视器_从系统任务栏监视Google服务
  7. KVO - 观察自定义属性值
  8. 设计模式之单例模式8种实现方式,其三:懒汉式(线程不安全)
  9. 计算机专业学生前端该怎么自学?
  10. php去除中文以外的特殊字符,php从文本中去除空格、特殊字符的4种情况
  11. 手机如何把PDF文件压缩的小一点?教你手机压缩文件方法
  12. 用C语言将四个数字排列顺序(不重复)
  13. 这10个免费学习网站,个个堪称神器,不收后悔!
  14. [经典之作]vml经典之作
  15. 开源项目——小Q聊天机器人V1.1
  16. vmware服务器文件备份,三种VMware数据备份和恢复方法
  17. 【工具】OmniGraffle 激活码、UML模板型版
  18. RIO——健壮的IO包
  19. 强化学习(十八) 基于模拟的搜索与蒙特卡罗树搜索(MCTS)
  20. 护眼灯到底有没有护眼的效果?2022护眼儿童台灯选哪个牌子好

热门文章

  1. ad怎么批量改元器件封装_在AD软件中的PCB界面如何批量修改封装?
  2. 电脑计算机配置应用程序兼容性,电脑怎么打开兼容模式怎么办
  3. 匹配滤波器为何使得输出SNR最大?
  4. hsqldb mysql_HSQLDB创建数据库和基本的数据库访问 | 学步园
  5. 暴风激活后浏览器被锁定首页
  6. java fop_java – Apache FOP使用SimSun显示###
  7. 【LED灯屏控制器】国产FPGA之 AG10KSDE176 初探(1)
  8. PHP如何实现二维码的生成以及识别(代码)
  9. 零基础java学习笔记
  10. strcmp函数的实现