场景 : 最近,有客户反应某些功能执行得很慢,我们于是对代码日志进行了定位,我们的系统架构是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循环变为多线程来优化。

       --> 后续补充