2020华为软件精英挑战赛历程总结——复赛篇

前言

最近在打腾讯广告算法大赛,所以一直在鸽0 0,再加上复赛印象不怎么深刻,所以就随便扯啦。

开源地址:https://github.com/Chadriy/CodeCraft2020


放题

一、规则改动
复赛增加了转账金额的限制,搜索得到的目标环中相邻边必须满足金额限制,即环内任意相邻边 x x x y y y需满足 x ∈ ( 0.2 y , 3 y ) x\in (0.2y,3y) x(0.2y,3y) y ∈ ( 0.2 x , 3 x ) y\in (0.2x,3x) y(0.2x,3x),大概就是代码加了个判断而已,然后是环数上限拉到2000w之类的增强。
二、数据集增强
由于初赛数据集被各路大佬扒的差不多了,初赛一个完全图就离谱,于是复赛线上塞了个随机图来做增强。
三、前期准备
一开始我摸了好几天,队友写好了base去陪女票了,线下民间出现了1963w环的数据集。在放榜前,这个数据集很快被大家刷到了20s以内的成绩,我们大概是43s的样子。

练习榜A

刚开始放题的时候,线上数据死性不改,还拿完全图凑数- -。于是大家线下成绩以s为量级,线上第一天又到了1s以内。这时候是正是赛第二次选手情绪集中爆发吧,第一次是初赛最后一天官方限制针对线上数据集不处理的trick。这次官方允诺会加强数据集,所以练习榜分了AB。
这个榜十分短暂,我甚至不记得我做过什么优化…大概第一发就1.0了,然后加了一个小trick到0.88。
trick:

Before:uint circle[THREAD_COUNT]
After:uint circle[(THREAD_COUNT / 4 + 1) * 4]

如上,当时我们用的10线程,然后每个线程单独记录环数,然后定义了一个全局数组来记录对应线程的环数。对齐之后,线上提了10%。
所以来讲讲对齐的各种奇妙结果:

  • 情景1:
    全局变量的定义过程中,有某个变量定义时,长度没有cacheline(鲲鹏128位)对齐,之后的变量首地址都会偏移。最终结果会产生很大影响,因此这里对齐是正优化。
  • 情景2:
    在一个结构体内,假设该结构体是16+32,应该填充一个ushort填充到64,同理32+64应填充到128。然而,16+16与32+32不应该填充。因为对齐会降低cachemiss,在连续访存时,16+16的结构体会一次被读进一个cacheline长度(4个),于是提高了cachehit。于是,压缩内存比地址对齐更重要。除了这种对齐,我们还可以通过设置位段的形式,同时完成二者。譬如结构体内有32+64的两个变量,通过确定其上届,若存储仍有冗余,可以将之拆解为24+40这样(前提是不会溢出)。然后通过设置位段的方法将32+64压缩到xx+xx=64的形式(该策略来自凌少)。
  • 情景3:
    memcpy函数在固定长度、可变长度等多种情况下会编译成不同的指令,通过阅读其汇编源码可知,当长度为16时会被O3编译成一条读内存和一条写内存指令,所以这个对齐很强,尝试了各种魔改,都不如开O3+16位固定长度的memcpy- -。

当然对齐也并不是很大的要素,事实上在之后海海的开源代码中可以看到,大部分情况下,内存紧密排布比cacheline对齐更加重要,因此需要以实验为准。
除此之外,我们代码中的存储也由多维数组改成了动态一维数组的形式,类似于开辟一个内存池,然后变量从中按需取。然后在初赛时,我们对反向搜索时长度为2、3的路径都进行了保存,然而事实上只需要存长度为3的路径。除此之外,金额判定用整数乘法代替除法也是基本操作了。
综上,练习赛A榜最终成绩0.67s,这时候io仍占了很大比重,再加上我们对对齐的理解已经超过了大部分选手,因此封榜时我们跑到了全榜第一(大佬们还没入场)。

练习榜B

B榜对数据集进行了增强,增加了随机图和小菊花图(出度很大的局部图),因此优化空间提高了很多。这次我们开局4s,第一天海子哥已经2.4s了qwq。经过两天的debug,我们发现线上跑10线程就会炸,换回4线程就好了(莫名其妙)。浪费两天时间,成绩来到3.2s。
对于菊花图,我们到最后也没有做有效的处理,短暂的一周我们主要做了如下优化:

  • if判断存在部分冗余,其位置也影响成绩。
  • 逻辑上的些许优化(忘了)
  • 多线程读,线下400+ms->160ms

最后几天我主要对读文件做改进,多线程mmap、多线程归并sort等等,将读+映射+构图的逻辑全部并行,然后将unordered map替换为robin map,线上同步提升0.3s的成绩。然后是未完成的部分,首先是3.5+3.5的搜索方案,即正反向搜索层数在3+4与4+3中切换,以达到理论最优。同时进行了大量的数据分析,比如幂律分布导致线程负载均衡更极端,需要微调等等。同时对于出度很大的菊花图,应该尽量用反向搜索代替正向,在代码里写了switch的策略。当然,都没有什么好的结果。
最终以全国第七收尾,空佬在最后一天通过一个magic提升了0.4s到了榜二。空佬给了我一点提示,一直到最后复赛放题我还在写- -,最后发现我们理解的都不是一个意思。
magic-空佬:通过实验可以得出结论,二维数组访问速度比动态生成的一维数组。于是对于菊花点(出度很大)用二维数组存,对于普通点用一维数组。
magic-蔡总:对于菊花点,其出度很大,因此在正向搜索中只要遇到就会炸。因此可以进行“拆点”,将一个菊花点的邻接表进行拆分。譬如点A其邻接表是平均度的100倍,那么可以将A点拆成A1-A10,各自入度连接不变,出度邻接表进行均分。通过分析我们得知这是一个很大的逻辑优化,但由于时间紧迫,再加上存储结构比较复杂,最终未能实现。
最终全榜第七。

正赛

成也magic,败也magic。
1.50分官方放题,环长度要求变更为3-8,同时金额变为两位浮点数,变动“很小”。
针对规则1,由于进行了循环展开,代码改动很大,一点小小的疏忽就可能崩盘。蔡总开始改4+4,反向用原来的三层+一层打标记实现,我改数据类型和读文件。对于数据类型,我坚决反对用double,因为太慢,于是改为统一乘100到long的形式。一个半小时,debug到手抖,对上了所有线下答案。第一发提交3.2s,后面几发就调了调参数,临场发挥为0。
最终抖到3.19s,全榜第六。
正赛结束,复赛于大多数选手而言,都不是一段愉快的经历,我们也遇到了很多莫名其妙的bug。这里说句题外话,命题组敷衍的态度与出题水准,直接在复赛刷掉了70%的选手,其中还有ddd、魔法少女这样的顶级选手,甚至还在赛后开贴回应到“选手太菜”、“魔鬼参数”等等。果子哥最后几天还在打点滴熬夜solo,结果最后被double精度问题卡掉,空佬也是同样问题被卡。于是出现了很扯淡的情况,练习赛我前面的团队全部没分,真应该把空佬的队名给我们qwq。

结语

复赛办的乱七八糟,不过在这过程中,认识了很多好朋友。练习赛最后几天,我们相互鼓励,交流idea,度过了一段美好的时光,这远比题目本身有趣。然而,最终我的祝福变成了毒奶,只有我一人进到了决赛qwq。
之后心情低落了好几天,爷青无(摔)。

PS:赛后送了空佬一张奶刃2黄金国的卡带,希望能让他高兴一点。

你可能感兴趣的:(2020华为软件精英挑战赛历程总结——复赛篇)