场景 : 最近,有客户反应某些功能执行得很慢,我们于是对代码日志进行了定位,我们的系统架构是nginx+tomcat; 我们可以直接定位到tomcat的catalina日志,但是后来吧,我们这边统一要整理响应时间超过5S以上的,对这些都要进行整改;所以我们先直接分析nginx的日志文件,查看请求跟响应超过5S以上的统计出来,然后在tomcat的日志当中定位这些请求,查找到具体的时间,以及上下文,最后我们定位到某个方法执行超过一些时间的(比如一个方法超过2S了,这肯定不行啦)。
现在,我们抛开掉业务场景,从最近本的优化方案看。
1. 嵌套循环应该内大外小,还是内小外大?
内大外小指的是 : 内层循环比外层循环次数多。
代码实例 :
@Test
public void testForNeiWai() {
// 方法说明 : 测试内外循环
// 1. 测试外循环比内循环大
Long startTime = System.nanoTime();
for (int i = 0; i<1000000; i++){
for ( int j = 0; j<100 ; j++){
}
}
Long endTime = System.nanoTime();
System.out.println("外大内小耗时: "+(endTime-startTime));
}
这里我们运行3次,取3次结果的平均值 : (我这里是8G内存,普通的笔记本,没有SSD什么的)
运行时间为 : 外大内小耗时: 325459668; 外大内小耗时: 168779176; 外大内小耗时: 317150703;
平均耗时为 : 270463182.33333~
接下来,我们看下,外小内大的情况:
@Test
public void testForWaiNei() {
// 方法说明 : 测试内外循环
// 1. 测试外循环比内循环小
Long startTime = System.nanoTime();
for (int i = 0; i<100; i++){
for ( int j = 0; j<1000000 ; j++){
}
}
Long endTime = System.nanoTime();
System.out.println("内大外小耗时: "+(endTime-startTime));
}
运行的结果是:内大内外耗时: 355928863; 内大内外耗时: 353038045; 内大内外耗时: 352683093;
结果为 : 353883333.666~
由此可见,其实相差这么多倍其实差距并不是太大。虽然网上要求我们坚持外小内大的原则,实际上可能应该按当时的业务场景+测试来得到最终的方案吧。
2. 提取与循环无关的表达式
@Test
public void testForWuGuan() {
// 方法说明 : 测试内外循环
// 1. 测试外循环比内循环小
int a = 10,b=12;
Long startTime = System.nanoTime();
for ( int j = 0; j<1000000 ; j++){
j = j*a*b;
}
Long endTime = System.nanoTime();
System.out.println("耗时: "+(endTime-startTime));
}
同理,运行的结果是: 内大内外耗时: 21758;内大内外耗时: 14931; 内大内外耗时: 17065
我们大致定位到这是 18000;我们再看下把a*b提取出去之后的 :
同理,运行时间是:耗时: 13652;耗时: 15785;耗时: 14078;这个耗时现在看是减少了一些,然后如果你这个里面有很多这种无关循环的表达式的话,那确实可以提升性能。
3. 消除循环终止判断时的方法调用
@Test
public void testForWuXunHuanBianLiang() {
String sql = "select * from in_applyinfo where status_lookup_code='140' allow filtering";
List<ApplyInfoMigration> applyInfoMigrationList = applyInfoDao.selectList(sql,ApplyInfoMigration.class ); // A实体
int a = 10,b=12, c=a*b;
Long startTime = System.nanoTime();
for ( int j = 0; j<applyInfoMigrationList.size() ; j++){
// j = j*c;
}
Long endTime = System.nanoTime();
System.out.println("耗时: "+(endTime-startTime));
}
同理 : 耗时: 37116;耗时: 34556; 耗时: 38397;平均:37000;现在让我们把上面的list.size()使用一个常量来代替,然后看执行结果是多少。
同理: 耗时: 14505;耗时: 17492; 耗时: 14506;由此可见,此性能提升了多少,相信大家都有数了,不要让这一点点代码而影响我们这么多性能。
4. 对异常进行优化
@Test
public void testForException() {
// 方法说明 : 测试for循环优化
Long startTime = System.nanoTime();
for ( int j = 0; j<1000000 ; j++){
try {
}catch (Exception e){
}
}
Long endTime = System.nanoTime();
System.out.println("耗时: "+(endTime-startTime));
}
同理 : 耗时: 4753466; 耗时: 6354166; 耗时: 5265843。接下来,我们把try-catch语句块移到外面 :
@Test
public void testForExceptionTwo() {
// 方法说明 : 测试for循环优化
Long startTime = System.nanoTime();
try {
for ( int j = 0; j<1000000 ; j++){
}
}catch (Exception e){
}
Long endTime = System.nanoTime();
System.out.println("耗时: "+(endTime-startTime));
}
同理 : 耗时: 4925822 ;耗时: 5144255; 耗时: 5748358; 若我们把代码放到一堆比较的话 :
耗时: 7290610 (catch在里面)、4804234
耗时: 3249610 (catch在外面) 、3632720; 从我这里看性能并没有多大的提升,可能是也没有中间业务处理流程,也可能是个人的junit测试环境问题,总之,感觉这里并没太大的提升。不过对于上面的我觉得可能是jdk1.8版本所导致的吧,所以还是根据个人使用环境来,那么我这里就采取把list的size变为常量来吧。
5. 说完了上面的for循环基本的优化之后,我觉得实际上还是各自测试下,按照自己当前的业务跟jdk版本来确定优化策略,然后下面我想说一下的是把for循环变为多线程来优化。
--> 后续补充