热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

isInfoEnabled究竟多有用?

前段时间,公司里组织了一次代码检查,其中有一条检查项让我有些费解:所有INFO和DEBUG级别的日志,必须加上isInfoEnabled和isDebugEnabled的判断。理由是大量的不

前段时间,公司里组织了一次代码检查,其中有一条检查项让我有些费解:

所有INFO和DEBUG级别的日志,必须加上isInfoEnabled和isDebugEnabled的判断。

理由是大量的不输出的日志对性能会有影响(日志中存在字符串拼接)。如果说只是DEBUG的加上,我也就认了,可是在系统中写成INFO的日志如果不输出,那还写它干嘛,我就是想看到关键路径的日志。而且在大多数日志上加上这么一个判断真的很难看。。。

 

所谓上有政策,下有对策,于是有人开始写一些包装了判断的辅助类,当中用这样的代码(截取):

Java代码   收藏代码
  1. public class LogUtil {  
  2.   
  3.     public static void info(Logger logger, String message) {  
  4.         if (logger.isInfoEnabled()) {  
  5.             logger.info(message);  
  6.         }  
  7.     }  
  8.   
  9.     public static void debug(Logger logger, String message) {  
  10.         if (logger.isDebugEnabled()) {  
  11.             logger.debug(message);  
  12.         }  
  13.     }  
  14.   
  15.     public static void debug(Logger logger, String message, Throwable throwable) {  
  16.         if (logger.isDebugEnabled()) {  
  17.             logger.debug(message, throwable);  
  18.         }  
  19.     }  
  20. }  

但这样的代码真的就能解决问题了吗?答案是“不能”!字符串拼接还存在。

 

那让我们分两个部分来看一下这个问题:

  1. 日志中的字符串拼接真的对性能影响很大吗
  2. 加不加isInfoEnabled的性能差距究竟有多少

关于第一个问题,String的拼接是不修改原来的字符串的,而是创建一个新的String对象,道理上是这么说的,我们也该这么理解。但Java编译器并不傻,实际情况是怎么样的呢,它会做些优化。请看如下代码:

Java代码   收藏代码
  1. public class StringAddDemo {  
  2.     public static void main(String[] args) {  
  3.         String a = "abc";  
  4.         String b = a + "def";  
  5.         System.out.println(b + "ghi");  
  6.     }  
  7. }  

在编译后,通过javap -c StringAddDemo看看结果:

javap结果
Compiled from "StringAddDemo.java"
public class StringAddDemo extends java.lang.Object{
public StringAddDemo();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc     #2; //String abc
   2:   astore_1
   3:   new     #3; //class java/lang/StringBuilder
   6:   dup
   7:   invokespecial   #4; //Method java/lang/StringBuilder."":()V
   10:  aload_1
   11:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/
String;)Ljava/lang/StringBuilder;
   14:  ldc     #6; //String def
   16:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/
String;)Ljava/lang/StringBuilder;
   19:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/la
ng/String;
   22:  astore_2
   23:  getstatic       #8; //Field java/lang/System.out:Ljava/io/PrintStream;
   26:  new     #3; //class java/lang/StringBuilder
   29:  dup
   30:  invokespecial   #4; //Method java/lang/StringBuilder."":()V
   33:  aload_2
   34:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   37:  ldc     #9; //String ghi
   39:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   42:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   45:  invokevirtual   #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   48:  return

}

实际这里的拼接用的是StringBuilder.append。情况没想的这么糟糕,而且1000次字符串拼接的开销都比不上一次远程调用的开销大,与其想着从这里挤性能,还不如去优化远程调用和数据库访问。

 

第二点,同样用实验来做说明,这里对比了使用辅助类,使用isInfoEnabled判断和不使用判断的情况,日志级别为INFO,另外再加了使用辅助类,使用isDebugEnabled判断和不使用判断的情况,都是循环输出20万次,每次的拼接在最后都加了些运算:

Java代码   收藏代码
  1. public class LogDemo {  
  2.     private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);  
  3.   
  4.     public static void main(String[] args) {  
  5.         Profiler.start();  
  6.   
  7.         Profiler.enter("Ignore");  
  8.         for (int i = 0; i < 200000; i++) {  
  9.             LogUtil.info(logger, "test" + "test" + i + i*2);  
  10.         }  
  11.         Profiler.release();  
  12.           
  13.         Profiler.enter("InfoUtil");  
  14.         for (int i = 0; i < 200000; i++) {  
  15.             LogUtil.info(logger, "test1" + "test2" + i + i*2);  
  16.         }  
  17.         Profiler.release();  
  18.   
  19.         Profiler.enter("isInfoEnabled");  
  20.         for (int i = 0; i < 200000; i++) {  
  21.             if (logger.isInfoEnabled()) {  
  22.                 logger.info("test3" + "test4" + i + i*2);  
  23.             }  
  24.         }  
  25.         Profiler.release();  
  26.   
  27.         Profiler.enter("info");  
  28.         for (int i = 0; i < 200000; i++) {  
  29.             logger.info("test5" + "test6" + i + i*2);  
  30.         }  
  31.         Profiler.release();  
  32.           
  33.         Profiler.enter("DebugUtil");  
  34.         for (int i = 0; i < 200000; i++) {  
  35.             LogUtil.debug(logger, "test7" + "test8" + i + i*2);  
  36.         }  
  37.         Profiler.release();  
  38.   
  39.         Profiler.enter("isDebugEnabled");  
  40.         for (int i = 0; i < 200000; i++) {  
  41.             if (logger.isDebugEnabled()) {  
  42.                 logger.debug("test9" + "test0" + i + i*2);  
  43.             }  
  44.         }  
  45.         Profiler.release();  
  46.   
  47.         Profiler.enter("debug");  
  48.         for (int i = 0; i < 200000; i++) {  
  49.             logger.debug("tes1" + "tes2" + i + i*2);  
  50.         }  
  51.         Profiler.release();  
  52.   
  53.         Profiler.release();  
  54.         System.out.println(Profiler.dump());  
  55.     }  
  56. }  

( 说明:这里的Profiler类是个工具类,作用是记录调用的时间;代码有截取;第一个循环主要是预热一下,不在统计范围内)

 

经过了几次测试,结果如下:

实验结果
+---14,766 [15,171ms, 26%, 26%] - InfoUtil
+---29,937 [14,187ms, 24%, 24%] - isInfoEnabled
+---44,124 [13,938ms, 24%, 24%] - info
+---58,062 [31ms, 0%, 0%] - DebugUtil
+---58,093 [16ms, 0%, 0%] - isDebugEnabled
`---58,109 [46ms, 0%, 0%] - debug

+---14,968 [14,656ms, 25%, 25%] - InfoUtil
+---29,624 [14,141ms, 24%, 24%] - isInfoEnabled
+---43,765 [14,265ms, 25%, 25%] - info
+---58,030 [47ms, 0%, 0%] - DebugUtil
+---58,077 [0ms] - isDebugEnabled
`---58,077 [47ms, 0%, 0%] - debug

+---14,999 [14,922ms, 26%, 26%] - InfoUtil
+---29,921 [14,093ms, 24%, 24%] - isInfoEnabled
+---44,014 [14,219ms, 24%, 24%] - info
+---58,233 [47ms, 0%, 0%] - DebugUtil
+---58,280 [0ms] - isDebugEnabled
`---58,280 [47ms, 0%, 0%] - debug

+---14,656 [14,812ms, 26%, 26%] - InfoUtil
+---29,468 [14,344ms, 25%, 25%] - isInfoEnabled
+---43,812 [13,890ms, 24%, 24%] - info
+---57,702 [31ms, 0%, 0%] - DebugUtil
+---57,733 [16ms, 0%, 0%] - isDebugEnabled
`---57,749 [47ms, 0%, 0%] - debug

+---16,219 [16,719ms, 25%, 25%] - InfoUtil
+---32,938 [17,703ms, 27%, 27%] - isInfoEnabled
+---50,641 [15,157ms, 23%, 23%] - info
+---65,798 [47ms, 0%, 0%] - DebugUtil
+---65,845 [0ms] - isDebugEnabled
`---65,845 [47ms, 0%, 0%] - debug

(数据说明:第一列为开始计时的时间点,[]内为时间及统计,15,171ms为该阶段的具体耗时,后面的百分比是该阶段耗时在这个统计内所占的百分比)

 

 对上述数据分析后,可以得到这样的结论:

  1. 日志输出前的判断确实有效,但效果甚微
  2. 在日志级别较低,不会输出的情况下,日志的耗时基本可以忽略 ,20万次的调用耗时在50ms内
  3. 在日志级别满足输出要求时,3种方法差别不大

针对第3条再做些补充说明,日志输出大的开销应该在IO上,计算应该不会很多,也不该很多,如果存在大量的运算请自己考虑下是不是有问题;既然是确认要输出的日志,那增加判断其实是种浪费,虽然这种判断的开销可以忽略。

 

综上所述,个人建议在日常系统中无需对日志增加isInfoEnabled判断,想通过这种处理来优化效果的作用不会很明显,还是把精力从日志移到数据库和远程调用上效果更好些。 (特殊情况下,如果在日志中有复杂的操作,可以酌情考虑,但个人不倾向于复杂的日志)


from: http://digitalsonic.iteye.com/blog/700891


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
author-avatar
我是你的小pig的美丽家园
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有