第一次作业总结
-
Alpha版本(基于请求)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 这个版本功能比较废物,性能比较垃圾,没有前途已经被淘汰了,就不谈可扩展性了
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 设置了一个线程安全类RequestsQueue,其余部分均没有也不需要同步
-
锁的选择
- 采用理论课着重涉及的synchronized加锁
-
锁与同步块中处理语句的直接关系
- 线程安全类RequestsQueue中维护了Request的LinkedList,同步块中均涉及对此LinkedList的读或写操作因此必须同步
-
-
调度器设计与交互
-
调度器设计
- 本实现中没有单独的调度器
- 将公共托盘RequestsQueue作为调度器
-
调度器与线程的交互
- RequestReader线程可向调度器发送请求或结束信号
- ElevatorController线程可从调度器获取请求或询问上/下方是否存在请求
-
-
调度策略分析
-
调度逻辑
- 以ALS调度为参考,从请求出发进行调度,期间可捎带则捎带
- 不局限于ALS调度,在电梯内部有/无人从内/外部选择主请求时,均支持选择第一个请求/最远距离请求/最近距离请求
-
参数设置
- 枚举上述九种组合进行测试,结果显示在我的实现下从内/外部选择时均选择最远距离请求时,电梯拥有最佳的性能
- 本地测试结果显示,在我的实现下,相比于Beta版本,Alpha版本的性能落后且差距十分明显,因此从第二次作业开始实现全部基于扫描
-
-
-
Beta版本(基于扫描)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 功能方面,从本程序到第二次作业的集中式实现和分布式实现均可以快速完成修改,设置了线程安全类从而进一步加强可扩展性
- 性能方面,个人认为主要在于对控制参数的调整,这些参数和开关被集中定义在了Const类中,添加和修改较为方便
- 此外程序中设置了楼层映射机制,可以方便的处理可能增加的负楼层需求,设置了电梯类型映射机制,可以方便的扩展不同类型的电梯
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 设置了一个线程安全类Building,其余部分均没有也不需要同步
-
锁的选择
- 采用ReentrantReadWriteLock显式加锁,并根据方法性质的不同,在使用过程中选择性的采用读或写锁
- 之所以选择Lock而不是synchronized,主要是看了MY学长的博客,认为显式的Lock更清晰更易于理解,功能也相对丰富一些
- 之所以选择ReentrantReadWriteLock而不是普通的ReentrantLock是因为我认为逻辑上这样更合理,且在我的实现下读操作确实比较多
- 根据个人本地测试的结果,synchronized,ReentrantLock和ReentrantReadWriteLock三者性能差别不大,差异都在波动误差范围内
-
锁与同步块中处理语句的直接关系
- 线程安全类Building中维护了以下内容,涉及操作count/building/lastRequests的方法都进行了同步保护
private int count; private final Condition normalEmpty; private final Condition morningNotFull; private final ArrayList<Floor> building; private final LinkedList<Request> lastRequests; private final ReentrantReadWriteLock readWriteLock; - 对于size/hasUpper/hasLower等只读的方法,加readWriteLock.readLock
- 对于put/getUpper/getLower等涉及写的方法,加readWriteLock.writeLock
- 线程安全类Building中维护了以下内容,涉及操作count/building/lastRequests的方法都进行了同步保护
-
-
调度器设计与交互
-
调度器设计
- 与Alpha版本中的相应处理完全一致,只是RequestsQueue换成了Building
-
调度器与线程的交互
- 与Alpha版本中的相应处理完全一致,只是RequestsQueue换成了Building
-
-
调度策略分析
-
调度逻辑
- 以扫描为基础,电梯从底层到顶层再从顶层到底层往复运行,期间可捎带则捎带
- 支持缓冲式Look优化,如果电梯内无人且运行方向没有请求而运行反方向有请求,这样的情况连续若干次,电梯掉头运行
- 支持楼层聚集检测,如果电梯内无人且上下均没有请求,考虑此前MEMORY_VAL条请求,如果上方的请求超过下方的请求PREDICT_RANGE条,向上运行,如果下方的请求超过上方的请求PREDICT_RANGE条,向下运行
- 支持归中等待,如果电梯内无人且上下均没有请求,也不满足楼层聚集检测条件,则电梯会向中间楼层移动
-
参数设置
- 对上述优化的参数进行了枚举,在我的实现下的最优参数如下
- 缓冲式Look优化缓冲一级,即退化为普通Look优化,如果电梯内无人且运行方向没有请求而运行反方向有请求,电梯直接掉头运行
- 开启楼层聚集检测,MEMORY_VAL取值为8,PREDICT_RANGE取值为5,同时关闭归中等待优化
- 此版本为本次作业最终提交版本,强测总分98.3604,后分析发现,问题在于我的实现中选择请求时优先取短距离请求,但写出了如下方法计算距离,否则速度可以更快,
想穿越回去抽自己private int distance() { return changeToFloor - changedFromFloor; // abs ??? }
-
-
-
测试数据构造
-
Morning部分
-
分为如下三个主题
inline void getMorning() { singleTheme_M(12); //12 tot //start at zero/wait before start //only one request, random //with min-max //-------------------------------------- doubleTheme_M(18); //18 tot //start at zero/wait before start //only two request, random //with min-max //-------------------------------------- normalTheme_M(4); //24 tot //start at zero/wait before start //o-low/o-mid/o-high/o-random return; } -
单请求主题
- 仅一个请求
- 包括从零开始和等一段时间开始
- 包括从底层到顶层的数据,和若干随机数据
- 主要测试启动部分
-
双请求主题
- 仅两个请求
- 包括从零开始和等一段时间开始
- 包括从底层到顶层的数据,和若干随机数据
- 主要测试启动部分
-
标准主题
- 请求数在最大请求数附近
- 包括从零开始和等一段时间开始
- 包括全部从底层到顶层的数据,和若干随机数据
- 主要测试正确性和性能
-
-
Random部分
-
分为如下六个主题
inline void getRandom() { singleTheme_R(12); //12 tot //start at zero/wait before start //only one request, random //with max-min and min-max //-------------------------------------- doubleTheme_R(20); //20 tot //start at zero/wait before start //only two request, random //with max-min and min-max //-------------------------------------- normalTheme_R(3); //144 tot ///sudden/dense/scatter //i-low/i-mid/i-high/i-random //o-low/o-mid/o-high/o-random //-------------------------------------- limitTheme_R(2); //40 tot //start at zero/wait before start //low-high/high-low/random //with max-min and min-max //-------------------------------------- crossTheme_R(5); //20 tot //small amount up with small amount down //large range/random range //single cross/double cross //-------------------------------------- splitTheme_R(5); //20 tot //big amount up with big amount down //large range/random range //single split/double split return; } -
单请求主题
- 仅一个请求
- 包括从零开始和等一段时间开始
- 包括从底层到顶层/从顶层到底层的数据,和若干随机数据
- 主要测试启动部分
-
双请求主题
- 仅两个请求
- 包括从零开始和等一段时间开始
- 包括从底层到顶层/从顶层到底层的数据,和若干随机数据
- 主要测试启动部分
-
标准主题
- 请求数在最大请求数附近
- 对输入时瞬时输入/密集输入/分散输入进行覆盖测试
- 对起点楼层从低层/从中层/从高层/全楼层随机,终点楼层从低层/从中层/从高层/全楼层随机进行覆盖测试
- 主要测试正确性和性能
-
极限主题
- 请求数在最大请求数附近
- 包括从零开始和等一段时间开始,全部输入时间戳相同
- 包括全部从底层到顶层的数据/从顶层到底层的数据,和若干随机数据
- 主要测试正确性
-
交叉主题
- 采用分散输入
- 请求数在最大请求数附近
- 输入的请求特征为一条较长距离上行接一条较长距离下行/两条较长距离上行接两条较长距离下行
- 主要测试性能
-
分裂主题
- 采用分散输入
- 请求数在最大请求数附近
- 输入的请求特征为十六条较长距离上行接十六条较长距离下行/八条较长距离上行接八条较长距离下行
- 主要测试性能
-
-
Night部分
-
分为如下三个主题
inline void getNight() { singleTheme_N(12); //12 tot //start at zero/wait before start //only one request, random //with max-min //-------------------------------------- doubleTheme_N(18); //18 tot //start at zero/wait before start //only two request, random //with max-min //-------------------------------------- normalTheme_N(4); //20 tot //start at zero/wait before start //i-low/i-mid/i-high/i-random return; } -
单请求主题
- 仅一个请求
- 包括从零开始和等一段时间开始
- 包括从顶层到底层的数据,和若干随机数据
- 主要测试启动部分
-
双请求主题
- 仅两个请求
- 包括从零开始和等一段时间开始
- 包括从顶层到底层的数据,和若干随机数据
- 主要测试启动部分
-
标准主题
- 请求数在最大请求数附近
- 包括从零开始和等一段时间开始
- 包括全部从顶层到底层的数据,和若干随机数据
- 主要测试正确性和性能
-
-
部分逻辑的实现技巧
-
请求的对象化
class Request { public: double time; int id,from,to; inline Request(double time) { this->from=this->to=0; this->id=idPool[now++]; this->time=time; return; } inline Request(double time,int from,int to) { this->time=time; this->from=from; this->id=idPool[now++]; this->to=to; return; } inline bool isWrong() { return from==to; } inline void change(int from,int to) { this->from=from; this->to=to; return; } inline bool operator<(const Request &rhs) const { return this->time<rhs.time; } inline bool operator>(const Request &rhs) const { return this->time>rhs.time; } inline void print(FILE* out) { fprintf(out,"[%3.1lf]%d-FROM-%d-TO-%d\n",this->time, this->id,this->from,this->to); return; } };还是CPP的面向对象写起来顺手- 造出来的请求丢vector里sort之后调用print即可
-
分离用于控制的参数
const char* mode[]={"Morning","Random","Night"}; const int floorPool[4][2]={{1,4},{9,12},{17,20},{1,20}}; const double timePool[4][2]={{0.0,0.5},{0.0,10.0},{0.0,130.0}};- mode[]:参数包括到达模式定义
- floorPool[4][2]:低/中/高/全楼层段定义
- timePool[4][2]:瞬时/短时间/长时间范围定义
-
-
第二次作业总结
-
Alpha版本(集中式调度)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 功能方面,和第一次作业相比差异不大,从本次作业的实现到第三次作业的集中式调度的实现也可以快速完成修改
- 性能方面,依旧在于对控制参数的调整,这些参数和开关中通用部分被集中定义在了Const类,特异性部分被集中定义在了电梯构造函数,添加和修改较为方便
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 基于第一次作业Beta版本迭代开发,保留了此前版本的全部设置
- 新增线程安全类SafeOutput,对其中的静态方法safeOutput进行了同步
-
锁的选择
- 基于第一次作业Beta版本迭代开发,保留了此前版本的全部设置
- 对safeOutput采用synchronized加锁,因为实在是太简单了,直接放代码了
public static synchronized void safeOutput(String s) { TimableOutput.println(s); } - 说就题外话,不加互斥输出强测是不会有问题的,但互测提交的标准答案要求输出时间戳严格单调,为了方便的刀人于是加上了这一功能,此外笔者实现的评测机也支持强测和互测两种标准下的检测
-
锁与同步块中处理语句的直接关系
- 基于第一次作业Beta版本迭代开发,保留了此前版本的全部设置
- 对safeOutput的处理见上文中的代码,不再赘述
-
-
调度器设计与交互
-
调度器设计
- 与第一次作业Beta版本中的相应处理完全一致
-
调度器与线程的交互
- 与第一次作业Beta版本中的相应处理基本一致
- 调度器与一个RequestReader和一个Controller交互变为与一个RequestReader和多个Controller交互
-
-
调度策略分析
-
调度逻辑
- 集中式调度,多部电梯从请求队列中自由竞争请求进行处理
-
参数设置
- 考虑到多部电梯自由竞争的情况下每部电梯跑单电梯最优解不一定对应整体最优解,对各个电梯的包括缓冲式Look优化/楼层聚集检测/归中等待等的参数再次进行了枚举,考虑了不同电梯执行不同策略的情况,在我的实现下的最优参数如下
- 多部电梯执行普通Look优化,开启楼层聚集检测,MEMORY_VAL取值为8,PREDICT_RANGE取值为5,同时开启归中等待优化
- 此外,在笔者测试中还发现,如果将一部电梯的优化全部关闭,即退化为扫描,面对随机数据性能会有一定提升,但对不那么随机的数据性能会有更大的下降,故舍弃
- 此版本为本次作业最终提交版本,强测总分98.4734,后分析发现存在一个测试点,在等待一段时间后给出若干1-20的请求,而此时我的电梯因归中等待机制额外跑了半栋楼,导致性能0分
-
-
-
Beta版本(分布式调度)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 功能方面,和第一次作业相比差异不大,从本次作业的实现到第三次作业的分布式调度的实现也可以快速完成修改
- 性能方面,仍然在于对控制参数的调整,这些参数和开关中通用部分被集中定义在了Const类,特异性部分被集中定义在了电梯构造函数,添加和修改较为方便
- 此外考虑到调参的简便性,程序支持从命令行读入参数覆盖预设好的参数,对评测机略作修改即可自动枚举测试的参数
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 与Alpha版本中的相应处理完全一致
-
锁的选择
- 与Alpha版本中的相应处理完全一致
-
锁与同步块中处理语句的直接关系
- 与Alpha版本中的相应处理完全一致
-
-
调度器设计与交互
-
调度器设计
- 本实现中没有单独的调度器
- 将读入线程RequestReader作为第一级调度器
- 将对每个电梯,实例化一个请求托盘Building作为第二级调度器
-
调度器与线程的交互
- RequestReader线程作为第一级调度器,可向合适的第二级调度器发送请求或向全部第二级调度器发送结束信号
- Controller线程可从对应的第二级调度器获取请求或询问上/下方是否存在请求
-
-
调度策略分析
-
调度逻辑
- 分布式调度,由第一级调度器决定完成请求的电梯,电梯之间彼此完全独立
- 考虑到独立性,每部电梯的运行策略只需采用第一次作业中的最终策略
- 对每部电梯进行公共感知优化,每个电梯拥有公共的历史请求队列用于楼层聚集检测
- 按惩罚函数进行分配,传入参数包括等待进入此电梯中的人数A,在此电梯中的人数B,此电梯所在楼层与请求出发楼层的距离C,以0或1表示的请求出发楼层是否在电梯运行方向上D
- 对惩罚函数实现,考虑两种形式,其一为计算ABCD的加权和,其二为计算ABC的加权和后,乘以1+kD,对此两种形式,均需要四个系数
-
参数设置
- 对上述优化的参数进行了枚举,在我的实现下的最优参数如下
- 采用第二种函数形式,开启公共感知优化,ABC的权重依次是13/7/1,且k取1时电梯具有最佳性能
- 本地测试结果显示,在我的实现下,相比于Alpha版本,Beta版本的性能有一定落后,且差异远大于波动误差范围,因此提交过这个版本但没有将其作为最终版本
- 此外测试结果显示,只要AB的系数远大于CD的系数,两种函数形式均可以得到相近的性能且差异略大于波动误差范围
-
-
-
测试数据构造
-
Morning部分
-
向上支持原有的三个主题
- 补充了如下所述的电梯添加逻辑
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增随机主题
- 请求数在最大请求数附近
- 终点楼层完全随机生成
- 主要测试和性能,对正确性也是不弱的测试
-
-
Random部分
-
向上支持原有的六个主题
- 补充了如下所述的电梯添加逻辑
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增随机主题
- 请求数在最大请求数附近
- 包括分散输入和密集输入两种类型
- 起点和终点楼层完全随机生成
- 主要测试和性能,对正确性也是不弱的测试
-
-
Night部分
-
向上支持原有的三个主题
- 补充了如下所述的电梯添加逻辑
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增随机主题
- 请求数在最大请求数附近
- 起点楼层完全随机生成
- 主要测试和性能,对正确性也是不弱的测试
-
-
部分逻辑的实现
-
对增加电梯的请求的处理
inline void getPos() { ppr=0; memset(pos,0,sizeof(pos)); if (ans.size() && generator()&generator()&1) return; int tot=rand(1,3),rem=2; for (int i=0,cur,flg;rem;i++) { cur=1+(generator()&generator()&generator()&generator()&1); if (cur>rem || i+1==tot) cur=rem; int tpos=rand(0,ans.size()); rem-=cur; flg=1; for (int j=0;j<ppr && flg;j++) if (pos[j][0]==tpos) {pos[j][1]+=cur; flg=0;} if (flg) pos[ppr][0]=tpos,pos[ppr++][1]=cur; } return; }- 采用打印时再随机注入的方式进行添加
- 通过以上函数获得请求添加位置
- 两个不同位置/一个位置两个均可能生成
-
-
第三次作业总结
-
Alpha版本(集中式调度)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 功能方面,相比于第二单元主要修改在于Request类,通过Request自身完成换乘,整体结构变化不大,性能方面,又双叒叕在于对控制参数的调整,依旧向上支持命令行输入参数,在此不再赘述
- 实现中为请求的拆分设置了单独的方法,对于可能的更复杂的需求,如可达楼层动态输入等均可方便的支持,只是算法上从枚举变成最短路
- 由于请求队列的线程安全性,对于删除电梯吐出电梯中请求等需求等亦可方便的支持
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 基于第二次作业Alpha版本迭代开发,保留了此前版本的全部设置
- 本次作业中无新增内容
-
锁的选择
- 基于第二次作业Alpha版本迭代开发,保留了此前版本的全部设置
- 本次作业中无新增内容
-
锁与同步块中处理语句的直接关系
- 基于第二次作业Alpha版本迭代开发,保留了此前版本的全部设置
- 本次作业中无新增内容
-
-
调度器设计与交互
-
调度器设计
- 与第二次作业Alpha版本中的相应处理完全一致
-
调度器与线程的交互
- 与第二次作业Alpha版本中的相应处理基本一致
- 调度器与Controller的交互增加了一部分,即Controller控制电梯完成请求后,会调用请求的destroy方法,如请求存在下一阶段,会将自己的下一阶段放入调度器
-
-
调度策略分析
-
调度逻辑
- 集中式调度,多部电梯从请求队列中自由竞争可以处理的请求进行处理
- 对于每个MyRequest,使用PersonRequest构造,构造过程中会对请求进行拆分,只显示第一部分的请求,其余部分存于LinkedList,同时提供destory方法,调用后将下一阶段请求放入请求队列Building
- 实现请求伙伴优化,即将请求放入请求队列时,删除同ID的伙伴请求并将请求的下一阶段作为伙伴请求放入伙伴请求队列,电梯调入时,完成普通Look优化后开始楼层聚集检测前进行伙伴请求意义上的普通Look优化,从而实现第二阶段电梯提前去往换乘点节省时间
- 对请求的拆分采用静态二级拆分,即对x->y计算惩罚值f(x,y)后枚举全部楼层,对每个楼层k,计算惩罚值f(x,i)+f(i,y)+p,其中函数f(m,n)只静态的考虑两楼层间的距离和各电梯运行一层的时间
-
参数设置
- 考虑到多部电梯自由竞争的情况下每部电梯跑单电梯最优解不一定对应整体最优解,在枚举p的同时,对各个电梯的包括楼层聚集检测/归中等待等的参数再次进行了枚举,在我的实现下的最优参数如下
- 全模式通用优化为全部电梯执行普通Look优化,请求伙伴优化,MEMORY_VAL取值为8,PREDICT_RANGE取值为5,同时开启归中等待优化
- 对Morning模式,开启楼层聚集检测,p取值800,对Random模式,关闭楼层聚集检测,p取值800,对Night模式,关闭楼层聚集检测,p取值1600
- 此版本为本次作业最终提交版本,强测总分99.8309,除一个Night点得分98.6761外,没有低于99分的测试点
-
-
-
Beta版本(分布式调度)
-
架构与可扩展性分析
-
UML类关系图
-
-
UML类协作图
-
-
可扩展性分析
- 与Alpha版本基本一致,差别主要在于结束上
- 电梯在输出时会记录每个请求的到达情况,当所有请求都到达目的地且输入结束时,更新结束信号使各个Controller结束
-
基于度量的结构分析
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 类规模统计
-
-
同步块设置和锁的选择
-
同步块设置
- 基于第二次作业Beta版本迭代开发,保留了此前版本的全部设置
- 对RequestReader类中的sendRequest方法,考虑到MyRequest在destory时也要通过sendRequest方法分配下一阶段请求,额外进行了同步保护
-
锁的选择
- 基于第二次作业Beta版本迭代开发,保留了此前版本的全部设置
- 对sendRequest方法,同样由于需要实现的功能比较简单,采用synchronized加锁
-
锁与同步块中处理语句的直接关系
- 基于第二次作业Beta版本迭代开发,保留了此前版本的全部设置
- 对sendRequest方法,同步块中处理语句即动态分配请求给特定电梯
-
-
调度器设计与交互
-
调度器设计
- 与第二次作业Beta版本中的相应处理完全一致
-
调度器与线程的交互
- 与第二次作业Beta版本中的相应处理基本一致
- 调度器与Controller的交互增加了一部分,即Controller控制电梯完成请求后,会调用请求的destroy方法,如请求存在下一阶段,会将自己的下一阶段放入一级调度器
-
-
调度策略分析
-
调度逻辑
- 分布式调度,由第一级调度器决定完成请求的电梯,电梯之间彼此完全独立,对换乘的处理和请求伙伴优化同Alpha版本
- 对于请求分配给电梯的过程,理论上应该重新枚举训练,但因本地枚举训练效率的问题,直接将第二次作业中得到的惩罚函数结果乘以电梯运行一层的时间作为分配惩罚值,而将训练重点放在了请求的拆分上
- 对请求的拆分采用动态二级拆分,即修改静态二级拆分的惩罚函数,考虑各个电梯的等待进入人数/电梯中的人数/电梯所在楼层与请求出发楼层的距离和换乘惩罚系数四个参数对应的系数
-
参数设置
- 对上述优化的参数进行了枚举,在我的实现下的最优参数如下
- 全模式通用优化为全部电梯执行普通Look优化,请求伙伴优化,MEMORY_VAL取值为8,PREDICT_RANGE取值为5,同时开启归中等待优化
- 对Morning模式,开启楼层聚集检测,上述四个系数依次取4/1/110/200,对Random模式,关闭楼层聚集检测,上述四个系数依次取5/4/90/170,对Night模式,关闭楼层聚集检测,上述四个系数依次取1/6/90/150
- 本地测试结果显示,在我的实现下,相比于Alpha版本,Beta版本的性能有一定落后,且差异远大于波动误差范围,因此提交过这个版本但没有将其作为最终版本
-
-
-
测试数据构造
-
Morning部分
-
向上支持原有的四个主题
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增换乘主题
- 保证所有请求是换乘请求,换乘请求的定义如下所述
- 除此之外的逻辑同上文所述的随机主题
-
-
Random部分
-
向上支持原有的七个主题
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增换乘主题
- 保证所有请求是换乘请求,换乘请求的定义如下所述
- 包括随机换乘请求和随机大跨度换乘请求两部分
- 除此之外的逻辑同上文所述的随机主题
-
-
Night部分
-
向上支持原有的四个主题
- 对请求数/时间跨度等进行了调整以适应本次作业
-
新增换乘主题
- 保证所有请求是换乘请求,换乘请求的定义如下所述
- 除此之外的逻辑同上文所述的随机主题
-
-
部分逻辑的实现
-
对换乘型数据的构造
inline void fillChange() { for (int i=1;i<=20;i++) for (int j=1;j<=20;j++) { if (i==j) continue; if (i&j&1) continue; if (i<4 && j<4) continue; if (i<4 && j>17) continue; if (i>17 && j<4) continue; if (i>17 && j>17) continue; last[lastCnt].src=i; last[lastCnt++].dst=j; } std::sort(last,last+lastCnt,[](const node a,const node b){ return abs(a.src-a.dst)>abs(b.src-b.dst); }); return; }- 换乘型数据是指不换乘的情况下仅A类电梯可以完成的请求
- 采用枚举所有组合,选择符合要求的存入last数组的方式构造
- 对last数组按请求距离从大到小排序是为了便于筛选
- 例如需要获得大跨度换乘请求时可以从0到四分之一元素数量范围内随机数组下标
-
-
程序Bug分析
-
本地测试方式
-
本地自动测试特性简述
- 主要逻辑采用CPP实现
- 支持指定测试数据/使用数据生成器
- 支持Windows/Linux双平台,Linux下额外支持CPU时间测试
- 层次化测试,并发测试,自动统计分析性能
-
层次化自动测试之第一层,单进程跑点
-
AutoTest
- 采用CPP实现,调用Runner和Checker进行测试
- 数据传递主要分为两种形式,少量数据通过命令行传递,大量数据通过文件传递
- 通过条件编译完成Windows/Linux双平台支持
-
Checker
- 采用java实现,模拟了电梯运行过程
- 支持强测标准允许少量输出乱序和互测标准强制输出严格单调
- 之所以没有采用过程化按条目检查结果,主要基于可扩展性考虑,另一方面是为了避免按条目检查时可能发生的遗漏
- 在设计报错信息的时候充分考虑了趣味性,部分摘录如下,
从此WA也是一种快乐"wow! your elevator is flying", "wow! your elevator is digging", "wow! your elevator can born baby", "wow! your elevator is teleporting", "wow! your elevator move with door open", "wow! your elevator can predict the future", "wow! your elevator can expand itself", "wow! your elevator can eat passenger", "wow! your elevator open in a wrong floor", "wow! your passenger is able to split up", "wow! your passenger can go through wall" - 既然也是面向对象写的,类图与基于度量的结构分析结果如下
- 类关系图
-
- 类规模统计
-
- 类复杂度统计
-
- 方法复杂度统计
-
- 复杂度比较高的是Checker中用于过程性预处理和善后等的方法
-
Runner
- 采用Python实现,核心为subprocess定时投喂输入
- 因process_time方法的subprocess支持问题,Windows下没有记录CPU时间,Linux下采用time命令记录CPU时间
- Whidows下采用taskkill /f /pid杀死超时进程,Linux下采用timeout命令限制进程运行时间
-
-
层次化自动测试之第二次,多进程跑点
-
MultTest
- 根据命令行输入或控制台输入,确定并发数
- 根据并发数复制上述自动测试之第一层
- 按照对并发数的余数分别对测试点进行测试
-
ResultMerger
- 汇总MultTest运行的结果,清理不必要的文件和测试时产生的临时文件
- 执行ResultMerger后,文件夹中内容将变为和执行AutoTest单进程完成全部测试点完全一致
- 对更高层的自动测试,通过ResultMerger实现本层完全透明
-
-
层次化自动测试之第三层,多程序联测
-
LimitTest
- 主要用于惩罚函数调参,也可用于多重测试,仅适配了Linux
- 评测机会枚举准备好的参数的全部组合,按数量复制上述自动测试之第二层进行测试
- 测试所采用的参数将通过命令行输入给被测程序
- 由于测试规模过大,同时测试电脑扛不住,对并发数进行了限制
- 使用ps -ef | wc -l获得当前进程数,若当前进程数超过启动时进程数300以上,则等待当前批次进程跑完再继续进行测试
-
SpeedTest
- 根据程序数复制上述自动测试之第二层
- 对多个程序,按照指定的并发数测试全部测试点
-
TimeAnalyse
- 对每个子文件夹,先调用其ResultMerger,再统计程序性能
- 统计内容包括每个主题的平均运行时间,总运行时间和平均运行时间和
- 第三次作业额外统计每个主题的平均等待时间,总等待时间和平均等待时间和
- 在输出每个程序性能的同时,对每个性能指标输出前若干名以便查看
-
-
-
本人程序Bug
-
本地发现的Bug
- 调完样例和随便随机的三五个点之后基本没啥Bug了
- 第三次作业换乘完输出信息和塞回请求队列写反了,本地高并发强测下可能出现先进换乘电梯再出原来电梯的情况,调整两句话位置即可
- 倒是评测机在用的时候被发现了一大堆Bug,大部分是使用体验上的无关紧要,除此之外主要是评测机误判了一些其实正确的(逃)
-
OJ发现的Bug
- 自认为本地测试还算充分
- 三次作业强测和互测均没有发现Bug
-
-
他人程序Bug
-
三次互测均发现了其它同学的Bug
-
保留了本地评测记录,部分内容摘录如下
-
第一次作业
person1: 00 WA person2: 03 WA //dispatch algorithm tle, done person3: 01 WA //refuse to send passenger, done person4: 04 WA //may steal time, unable to hack person5: 00 WA person6: 00 WA person7: 00 WA person8: 26 WA //elevator expand, done- may steal time指可能存在未Sleep满导致输出先于请求输入的情况
- person4的Bug在本地Ubuntu下超过200路并发时,每百点大概率复现一次
-
第二次作业
person1: 000 WA person2: 064 WA //elevator is digging, done person3: 007 WA //unable to stop, missed person4: 011 WA //run time error, unable to hack person5: 292 WA //passenger split, born baby, run time error, done person6: 000 WA person7: 004 WA //cpu time in danger, but not ctle person8: 000 WA- 在此对person5表示歉意,除稳定刀您的点外,我本地稳定刀person3小几率刀您的点没刀到person3却把您刀了,给您带来的压力与不便敬请谅解
- person4的Bug在本地Ubuntu下超过200路并发时,每百点大概率复现一次
- 考虑到本地性能,本地评测对CPU时间限制为2s,person7少量测试点的CPU时间在2s出头,发生ctle主要是本地限制过强的问题
-
第三次作业
person1: 000 WA person2: 000 WA person3: 026 WA //born baby and run time error, done person4: 000 WA person5: 000 WA person6: 010 WA //run time error, done person7: 000 WA person8: 070 WA //cpu time limit exceed, done- 错误较为清晰直观,在此不过多解释
-
-
测试策略与有效性
-
有效性分析
- 三次作业中均发现了他人的Bug
- 存在这样的Bug:只被我发现而未被同房间其它人发现
- 不存在这样的Bug:未被我发现而被同房间其它人发现
- 所有发现的Bug本地均可复现,只是有些需要高并发跑多点才可复现,故无法在互测下复现
- 综上所述,自认为目前的测试方案是有一定有效性的
-
对线程安全针对性测试
- 主要采用高并发多重测试的方式,分为弱鸡模式,普通模式,强测模式,至尊模式,以下测试在Ubuntu20.04(非WSL/虚拟机)下进行
- 弱鸡模式下8路并发跑16个随机测试点,进行最基本的测试
- 普通模式下160路并发跑准备好的全部测试点,进行基本功能和性能测试
- 强测模式下多人联测,以约240至320路总并发数跑准备好的全部测试点,主要进行性能测试,也进行进一步的功能测试
- 至尊模式下对一份程序,本地三个随机种子跑总共120遍,全程并发不低于250路,存在间歇性轮询的标程模拟强测中可能出现的因轮询导致的计算资源进一步紧张
- 的确,本地跑一遍没出Bug不代表程序没有Bug,但经过至尊模式依旧没有测出Bug,自认为还是可以一定程度说明问题的
(OJ上的并发可能也就这么多)
- 主要采用高并发多重测试的方式,分为弱鸡模式,普通模式,强测模式,至尊模式,以下测试在Ubuntu20.04(非WSL/虚拟机)下进行
-
与第一单元的差异分析
-
测试模式的差异
- 第一单元全程单进程跑点,一方面是因为不存在线程安全问题,另一方面是因为每个测试点测试速度很快,且第一单元是计算密集型任务,并发加速效果不见得会好
- 第二单元需要并发跑点,一方面是因为高并发下比较容易测出线程安全问题,另一方面平均每个测试点的运行时间较长但CPU时间不长,顺序跑点过于低效,故并发加速
-
主要难点的差异
- 第一单元的评测机构造是相对简单的,在sympy帮助下简单搭出来一个能用的评测机基本有手就行
- 第二单元的评测机构造是有难度的,一方面实现定时投喂需要一定的技巧,另一方面本单元的SPJ需手工实现
- 第一单元的数据构造是有难度的,暴力随机的数据强度大多不太行,需要进行大量的半手工/手工数据构造,具体可参考本人上一单元的博客
- 第二单元的数据构造是相对简单的,暴力随机的数据本就有一定的强度,实现对更高强度数据的构造也只要对构造逻辑进行简单的丰富
-
-
心得体会
-
线程安全方面
- 学习的过程中研读了MY学长的博客,深以为然收获很大
- 想明白线程安全之后写就很容易了,基本没有出过线程安全问题
-
层次化设计方面
- 个人感觉层次化设计在作业中体现的不是很明显,个人比较层次化的实现(分布式)性能都不咋地
- 个人感觉层次化设计在构造评测机的时候体现非常明显,相比于第一单元,本单元的评测机有明显的层次化,这一单元的评测机的构造让我真正体会到了迭代开发的感觉,回看第一版评测机与最终版评测机,甚至有些不可思议,部分迭代内容如下
修复了错误信息写入异常问题 修复了Linux下墙钟时间异常的问题 修复了Night模式下的数据格式错误的问题 修改了AutoTest中临时文件的清理方式 修改了Runner中的输入基准时间 修改了SpeedTest中获取测试程序总数的方式,为控制台读入 修改了TimeAnalyse中获取测试程序总数的方式,为控制台读入 修改了TimeAnalyse中获取测试用例总数的方式,为控制台读入 修复了Runner存在投喂时间偏移的问题 修复了Checker没有输出增加电梯信息的问题 修复了AutoTest将未正常结束程序时间记为0的问题,已修改为按210s计算 修复了TimeAnalyse将未正常结束程序时间记为0的问题,已修改为按210s计算 修复了TimeAnlyse的输出格式错误问题 调整了Cpp文件后缀,统一进行了编译优化 调整了AutoTest的结束机制,批量测试模式下结束即关闭窗口 调整了MultTest释放测试用例逻辑,增强了性能 - 在此也要特别感谢在进行评测机开发和性能测试的过程中,对我提出宝贵意见和建议的朋友们
-
补充内容
- 建议增加Pre4电梯输出序列检查,对一个电梯输出序列,如果合法输出True,否则输出任意非True字符串,这样到第二单元就可以直接用了,本地搭评测机的成本就低了一些(逃)
- 建议给k次连测机会,对每次hack数据提交,可以选择测试1+x次,其中x限制为不超过k的非负整数,对提交的全部hack数据,要求x的总和不超过k,这样刀不好复现的Bug会舒服不少(逃)
- 建议提供更多的hack信息,如哪一个数据刀了哪个人,交上去若干刀发现只中了一部分的时候就很尴尬,不知道没刀中谁也不好补刀,另一种尴尬情况是想刀A实际刀中了B,却误以为刀中了A没接着刀把A放过了(逃)
- 建议加强题目,
增加负楼层,Morning/Random/Night模式动态切换,在程序运行一开始输入不同种类电梯的可达楼层,保证任意两个楼层可达,将这份惊喜与感动送给我们的学弟学妹们(逃)