OO_Unit2 关于性能优化与测试的那些事

  OO的第2单元到本周也就正式完结了。尽管这个单元的主旋律是多线程,但“面向对象”的基本思想仍然是我们一切架构与优化的出发点与前提。因此笔者在设计优化策略时,也是本着尽量减少类与类之间的耦合度的原则,去从各个类的内部进行细粒度的功能优化。当然,这样一来,也就没有什么完整的优化策略可言咯。

  那么既然是为了尽可能提高性能,我们首先就需要明确具体的性能指标,这样优化才能有针对性。前两个Task的优化指标是整个电梯系统的总运行时间,task3则引入了所有乘客的总等待时间。但无论是哪一个指标,它们都有一个共同的特点,那就是优化策略与数据之间存在客观依赖,而这正是Unit2与Unit1最大的不同。在Unit1中,导函数表达式的长度客观上必然存在最小值,且这个最小值仅仅取决于最开始输入的原函数,因此理论上我们可以找到一种最优策略(但可能需要搜索);而在Unit2中,由于是强制在线,因此总的运行时间取决于整个运行过程中的输入流,这就意味着在任何一个时刻,针对电梯的不同状态,我们都可以在下一个时刻针对性地投放一组新数据使得这个状态对应的性能指标不为全部状态集中的最优值。简单来说就是不存在绝对最优的优化策略

  既然不存在绝对最优,那么我们又是否可以退而求其次,找到一种期望最优的算法呢?很遗憾,答案也是否定的。因为,我们并不知道评测机在随机生成数据时采取的期望分布,是均匀分布还是指数分布还是正态分布,楼层数据与时序之间是否存在相关性,我们一概不知。但事实上,即便我们知道这些,在强测只有至多20个数据点,输入数据不超过50条这样的前提下,其实意义也不是太大,毕竟只是个期望。

  到此,我们可以得出一个初步的结论,也就是全局最优是不存在的。但是电梯还得运行呀,那策略的设计又该从哪里入手呢?一个比较常规的切入点是使用一种市面上常见的调度算法,再在其基础上进行一定的改造。笔者也不例外,不过这里笔者选择的不是理论中的那些算法,而是索性就以现实中的电梯为基本参照,首先复现现实中的电梯策略,再看看能否在局部对其进行优化。至于如何进行局部优化,由于既然是局部,那么贪心策略就显然是一种不错的选择了。

  综上,笔者在进行性能优化时的基本原则大概可以归结为以下几点:

  1. 从OO的角度出发,将优化策略化整为零地放在各个类中,类与类之间尽量避免因为优化而增加耦合度,同时尽可能地做到细粒度的优化。

  2. 由于不存在全局最优,因此调度策略的设计上笔者希望尽可能做到简洁有效,那么现实中的电梯就是一个很好的参考。同时在局部笔者也会进行一些基于贪心原则的细节优化,贪心的目标是尽量优先满足使得性能损耗较小的需求。

  3. 整体策略上努力避免对输入做出不必要的假设,也就是保证优化不向某一类型的输入数据过度倾斜

  4. 以提高整体性能指标为第一优先,必要时可以牺牲一定的单梯性能(task2, task3)

架构

  无论是什么优化,都离不开具体的底层架构设计。事实上,不同的架构设计可能在一开始就决定了不同的优化思路与方向,而同样的策略基于不同的架构实现其复杂度也不尽相同。

  笔者整个Unit2的架构总体上没有太大的变化,可以参见下图所示:

  其中Config为属性接口,保存了整个系统的一些基本参数常量,由于这些参数本身并不复杂但无奈又种类繁多,所以笔者索性把它们放到一起,这样也便于调整。TransFloorTable为静态换乘表,底层用HashMap嵌套实现,查询换乘的方式为( (所在层, 目标层), ( (电梯类型, 运行方向), 换乘层) )。换乘通过修改对应Passenger的TransFloor属性实现。Passenger就是请求数据类,而Pair和FloorLogOutput都是工具类。真正的核心类是Building, Scheduler与Elevator,主要的优化也正是围绕Scheduler与Elevator展开,而Floor与PollLog类则是为了使得整体实现(包括优化)更加细粒度而设计的。

优化

Elevator类

  • 当前状态由curFloor属性表达,状态转移方向由direction属性表达。对于电梯线程而言,在任意时刻(楼层),只需要知道其下一刻的状态该如何更新即可,无需更多的信息(比如目标层之类的),这样可以使得电梯的运行更加灵活,从而有利于贪心策略的贯彻。

  • 电梯应优先服务梯内乘客(这与现实中的电梯一致),直到电梯内没有乘客时,才向调度器scheduler请求新的方向。

  • 每到一层,先下客,再上客。上客前若无人下客,会对因开门所导致的梯内乘客等待时间增加与潜在新乘客等待时间的减少进行一个比较权衡,再决定是否开门。同时,若上客完毕后外面仍然有乘客未上,会对梯内乘客的最远目标层与梯外乘客的最近目标层进行比较,若,会考虑进行换客。

Scheduler类

  • 在电梯请求新的乘客时,直接从对应的Floor中弹出相应的乘客即可。若成功找到符合条件的乘客,会对其inEle属性进行检查,确保他没有被别的电梯线程同时获取。(这主要是为了配合Floor类的实现)

  • 当电梯请求方向时,首先优先选择一个离电梯所在层最近的乘客的相对方向作为其方向。若此时该电梯不存在等待的乘客,Scheduler还会去查找之前的pollLogs信息,看看是否有乘客的目标换乘层恰好是此类电梯的可达层,若是,则会将此方向作为电梯的新方向。

Floor类

  • 采用PriorityBlockingQueue实现各个电梯的等待队列,优先原则是目标层距离所在层近的乘客优先。

  • 允许同一个乘客同时出现在不同电梯不同方向的等待队列中(考虑到不同换乘策略的存在),也就是尽可能允许电梯之间的公平竞争,而不是做任何不必要的预分配。

  • 在调度器向其请求乘客时,若对方向无要求,则会根据楼层的相对位置决定一个优先方向。即高层优先向上,底层优先向下。这主要是顾及到A类电梯的可达楼层分布在大楼的两端,所以试图尽量避免A电梯的来回奔波。

TransFloorTable类

  • 换乘表在设计时也尽量考虑到了不同的权重(楼层间移动时间)对于性能的影响,因此在设计时的初衷就是提高整体的性能,允许单梯的性能损失,即:

    • 能够让A去做的尽量让A去做,比如1-14层除了B直达外,也可以允许A先将其送到15层。

    • 尽量提高C类电梯的利用率,比如若请求在偶数层,且B类电梯此时运行方向恰与之相反,可以让其先把该乘客稍带到最近的奇数层,这样C类电梯就能派上用场,同时也不会太过消耗B电梯的容量资源。

  以上就是笔者在整个Unit2所采用的全部优化策略了,从最后的强测表现来看,效果可以说还是挺不错的吧,但要说给这些优化的小trick起个共同的名字什么的,倒也真的是有些为难笔者了。但不管怎么说,绕来绕去都还是那句话,架构与优化从来都是辩证统一的,一方面明确了优化的基本原则有助于我们厘清架构的大致思路,另一方面好的架构也可以使得一些优化的策略显得更加自然简单,希望类似的思想在接下来的规格化设计中也可以祝各位看官一臂之力吧。

测试

  除了优化,测试这块笔者也有几句话想同看官们叨叨。当然,相信厉害的mina一定都有自己本地的一套完整的测试体系了吧。因此笔者这里也不多做展开,还是只讲几个可能会有用的小trick。

CTLE的检查——如何获取程序的CPU时间?

  • 关于这一点呢,其实Java的库中就已经提供了这样的方法,也就是getProcessCpuTime()。请见下方代码:

 1 import java.lang.management.ManagementFactory;
2 import java.util.concurrent.CountDownLatch;
3
4 import com.sun.management.OperatingSystemMXBean;
5
6 public class Main {
7 private static CountDownLatch doneLatch;
8
9 public static void main(String[] args) {
10 doneLatch = new CountDownLatch(5); // only if more threads need to start
11 // do something
12 try {
13 doneLatch.await();
14 } catch (InterruptedException e) {
15 e.printStackTrace(System.err);
16 }
17 getCpuTime();
18 }
19
20 private static void getCpuTime() {
21 OperatingSystemMXBean opSys = (OperatingSystemMXBean)
22 ManagementFactory.getOperatingSystemMXBean();
23 long nanoTime = opSys.getProcessCpuTime();
24 System.err.println(nanoTime / 1e6); // ms
25 }
26 }
  • 但是要注意,仅仅通过getProcessCpuTime()并不能够应付多线程的情况,因为main方法会在其他线程执行结束前就结束了。为了解决这个问题呢,一种办法是设置守护线程Daemon,还有一种办法就是利用Java提供的CountDownLatch类的await()countDown()之间的配合来实现。具体的使用方法还请感兴趣的同学查阅Java的官方文档,这里就不详细展开了。(什么?你说互测的时候怎么办,当然是放别人一马咯

  • CountDownLatch的使用:https://www.cnblogs.com/54chensongxia/p/12721925.html

数据可视化——肉眼debug的好工具

  在多部电梯的情况下,可能有些同学不仅想借助评测机进行测试,还想自己亲自看一看一些数据的运行效果或者就是想对评测机本身进行功能测试,但是眼花缭乱的输出又使人看的头大。那么这时候,一款可以将所有的输出log转化成图表的工具就显得非常重要了。这里笔者推荐python的pyplotlib模块,功能丰富又容易上手,可以说是可视化的上上之选。笔者借助里面的scatter()plot()text()等API实现的最终效果图如下(实际运行时局部还可以放大):

  相信在后面的单元中,如果数据的形式比较复杂的话(不一定是输出),类似的可视化工具也会给同学们提供一个不错的debug思路吧。毕竟对于这些小玩意,你所需要的只是一个脑洞而已~

OO_Unit2 关于性能优化与测试的那些事的更多相关文章

  1. 阿里巴巴 web前端性能优化进阶路

    Web前端性能优化WPO,相信大多数前端同学都不会陌生,在各自所负责的站点页面中,也都会或多或少的有过一定的技术实践.可以说,这个领域并不缺乏成熟技术理论和技术牛人:例如Yahoo的web站点性能优化 ...

  2. Unity3D - 性能优化之Draw Call

    Unity3D - 性能优化之Draw Call 分类: Unity 3D2012-09-13 11:18 1002人阅读 评论(0) 收藏 举报 性能优化引擎测试脚本图形算法 Unity(或者说基本 ...

  3. 【前端性能优化】高性能JavaScript整理总结

    高性能JavaScript整理总结 关于前端性能优化:首先想到的是雅虎军规34条然后最近看了<高性能JavaScript>大概的把书中提到大部分知识梳理了下并加上部分个人理解这本书有参考雅 ...

  4. 记我的一次 Java 服务性能优化

    背景 前段时间我们的服务遇到了性能瓶颈,由于前期需求太急没有注意这方面的优化,到了要还技术债的时候就非常痛苦了. 在很低的 QPS 压力下服务器 load 就能达到 10-20,CPU 使用率 60% ...

  5. Nginx配置性能优化与压力测试webbench【转】

    这一篇我们来说Nginx配置性能优化与压力测试webbench. 基本的 (优化过的)配置 我们将修改的唯一文件是nginx.conf,其中包含Nginx不同模块的所有设置.你应该能够在服务器的/et ...

  6. ab测试nginx Nginx性能优化

    转自:https://www.cnblogs.com/nulige/p/9369700.html 1.性能优化概述 在做性能优化前, 我们需要对如下进行考虑 1.当前系统结构瓶颈 观察指标 压力测试 ...

  7. Web性能优化:What? Why? How?

    为什么要提升web性能? Web性能黄金准则:只有10%~20%的最终用户响应时间花在了下载html文档上,其余的80%~90%时间花在了下载页面组件上. web性能对于用户体验有及其重要的影响,根据 ...

  8. Web性能优化:图片优化

    程序员都是懒孩子,想直接看自动优化的点:传送门 我自己的Blog:http://cabbit.me/web-image-optimization/ HTTP Archieve有个统计,图片内容已经占到 ...

  9. JavaScript性能优化

    如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...

随机推荐

  1. 高性能环形队列框架 Disruptor 核心概念

    高性能环形队列框架 Disruptor Disruptor 是英国外汇交易公司LMAX开发的一款高吞吐低延迟内存队列框架,其充分考虑了底层CPU等运行模式来进行数据结构设计 (mechanical s ...

  2. 一文学会Dockerfile语法

    接应上篇,续讲前文.今天咱来聊一下Dockerfile的使用 . 虽然可以通过docker commit命令来手动创建镜像,但是通过Dockerfile文件,可以帮助我们自动创建镜像,并且能够自定义创 ...

  3. pyhton的函数

    目录 一.函数引入 二.函数的定义 三.如何定义一个函数 四.定义函数的三种形式 1.空函数 2.有参函数 3.无参函数 五.函数的调用 六.函数的返回值 七.函数的参数 1.形参 1.1 位置形参 ...

  4. Django框架的forms组件与一些补充

    目录 一.多对多的三种创建方式 1. 全自动 2. 纯手撸(了解) 3. 半自动(强烈推荐) 二.forms组件 1. 如何使用forms组件 2. 使用forms组件校验数据 3. 使用forms组 ...

  5. Go - 代码生成工具

    分享两个常用的代码生成工具: gormgen handlergen gormgen 基于 MySQL 数据表结构进行生成 3 个文件: 生成表的 struct 结构体 生成表的 Markdown 文档 ...

  6. 后端程序员之路 29、Thrift

    Apache Thrift是Facebook实现的一个高效的.支持多种编程语言的远程服务调用(RPC)框架. Apache Thrift - Homehttp://thrift.apache.org/ ...

  7. 如何使用 Github Actions 自动抓取每日必应壁纸?

    如何白嫖 Github 服务器自动抓取必应搜索的每日壁纸呢? 如果你访问过必应搜索网站,那么你一定会被搜索页面的壁纸吸引,必应搜索的壁纸每日不同,自动更换,十分精美.这篇文章会介绍如何一步步分析出必应 ...

  8. 微信小程序点击按钮将图片保存到手机

    SaveCard: function(e) { let that = this; console.log('保存'); var imgSrc = e.currentTarget.dataset.img ...

  9. 剑指 Offer 65. 不用加减乘除做加法 + 位运算

    剑指 Offer 65. 不用加减乘除做加法 Offer_65 题目描述 题解分析 java代码 package com.walegarrett.offer; /** * @Author WaleGa ...

  10. ASP.NET Core扩展库之日志

        上一篇我们对Xfrogcn.AspNetCore.Extensions扩展库功能进行了简单的介绍,从这一篇文章开始,我将逐步介绍扩展库中的核心功能.     日志作为非业务的通用领域基础功能, ...