`
DigitalSonic
  • 浏览: 210425 次
社区版块
存档分类
最新评论

isInfoEnabled究竟多有用?

阅读更多

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

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

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

 

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

public class LogUtil {

    public static void info(Logger logger, String message) {
        if (logger.isInfoEnabled()) {
            logger.info(message);
        }
    }

    public static void debug(Logger logger, String message) {
        if (logger.isDebugEnabled()) {
            logger.debug(message);
        }
    }

    public static void debug(Logger logger, String message, Throwable throwable) {
        if (logger.isDebugEnabled()) {
            logger.debug(message, throwable);
        }
    }
}

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

 

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

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

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

public class StringAddDemo {
    public static void main(String[] args) {
        String a = "abc";
        String b = a + "def";
        System.out.println(b + "ghi");
    }
}

在编译后,通过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."<init>":()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."<init>":()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."<init>":()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万次,每次的拼接在最后都加了些运算:

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        Profiler.start();

        Profiler.enter("Ignore");
        for (int i = 0; i < 200000; i++) {
            LogUtil.info(logger, "test" + "test" + i + i*2);
        }
        Profiler.release();
        
        Profiler.enter("InfoUtil");
        for (int i = 0; i < 200000; i++) {
            LogUtil.info(logger, "test1" + "test2" + i + i*2);
        }
        Profiler.release();

        Profiler.enter("isInfoEnabled");
        for (int i = 0; i < 200000; i++) {
            if (logger.isInfoEnabled()) {
                logger.info("test3" + "test4" + i + i*2);
            }
        }
        Profiler.release();

        Profiler.enter("info");
        for (int i = 0; i < 200000; i++) {
            logger.info("test5" + "test6" + i + i*2);
        }
        Profiler.release();
        
        Profiler.enter("DebugUtil");
        for (int i = 0; i < 200000; i++) {
            LogUtil.debug(logger, "test7" + "test8" + i + i*2);
        }
        Profiler.release();

        Profiler.enter("isDebugEnabled");
        for (int i = 0; i < 200000; i++) {
            if (logger.isDebugEnabled()) {
                logger.debug("test9" + "test0" + i + i*2);
            }
        }
        Profiler.release();

        Profiler.enter("debug");
        for (int i = 0; i < 200000; i++) {
            logger.debug("tes1" + "tes2" + i + i*2);
        }
        Profiler.release();

        Profiler.release();
        System.out.println(Profiler.dump());
    }
}

( 说明:这里的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判断,想通过这种处理来优化效果的作用不会很明显,还是把精力从日志移到数据库和远程调用上效果更好些。 (特殊情况下,如果在日志中有复杂的操作,可以酌情考虑,但个人不倾向于复杂的日志)

 

2
2
分享到:
评论
12 楼 jin8000608172 2015-01-21  
Profiler类是自己写的还是开源jar里面的,如果是开源jar包 那是哪个jar呢?
11 楼 bevis.cn 2011-09-09  
好像是高版本的jdk对字符串连接“+”做了优化的,原来好像不是的。
10 楼 zhangna307 2011-04-14  
我也杯具的现在要加上这些判断。。。要求。。。
9 楼 hareamao 2010-06-28  
mercyblitz 写道
hareamao 写道
AOP可以精细到某一行代码前后了吗?仅仅是函数级别没有什么意义。


完全可以啊。关键看你怎么拦截。

能给个例子看看吗?另外,还好看么?
8 楼 mercyblitz 2010-06-28  
hareamao 写道
mercyblitz 写道
hareamao 写道
DigitalSonic 写道

是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。

反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。



用AOP的方式就不难看了。

AOP可以精细到某一行代码前后了吗?仅仅是函数级别没有什么意义。


完全可以啊。关键看你怎么拦截。
7 楼 hareamao 2010-06-28  
mercyblitz 写道
hareamao 写道
DigitalSonic 写道

是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。

反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。



用AOP的方式就不难看了。

AOP可以精细到某一行代码前后了吗?仅仅是函数级别没有什么意义。
6 楼 mercyblitz 2010-06-28  
hareamao 写道
DigitalSonic 写道

是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。

反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。



用AOP的方式就不难看了。
5 楼 hareamao 2010-06-28  
DigitalSonic 写道

是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。

反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。
4 楼 DigitalSonic 2010-06-28  
mercyblitz 写道


 public
  void debug(Object message) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
...


在 Log4j 1.2.15中,其实源代码中已经判断是否开放了debug等权限。

还有一点,楼主可能没有注意,在低版本的实现中,可能没有加这个类似的判断,并且可读性更好。


是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
3 楼 mercyblitz 2010-06-28  


 public
  void debug(Object message) {
    if(repository.isDisabled(Level.DEBUG_INT))
      return;
...


在 Log4j 1.2.15中,其实源代码中已经判断是否开放了debug等权限。

还有一点,楼主可能没有注意,在低版本的实现中,可能没有加这个类似的判断,并且可读性更好。
2 楼 DigitalSonic 2010-06-28  
如果真有这种情况,还是加上避免大的性能浪费。但就像我说的,日志代码里不应该出现大量的运算或者复杂的操作,是不是真的有必要在日志里特意调用数据库,这个本身就值得商榷。
1 楼 hareamao 2010-06-28  
如果日志字串拼接的时候需要jdbc调用呢?

相关推荐

    机械设计家用手摇面条机sw18可编辑非常好的设计图纸100%好用.zip

    机械设计家用手摇面条机sw18可编辑非常好的设计图纸100%好用.zip

    机械设计托盘库推车sw12可编辑非常好的设计图纸100%好用.zip

    机械设计托盘库推车sw12可编辑非常好的设计图纸100%好用.zip

    node-v12.18.4-linux-arm64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    YOLOV5 分类实战:7种小麦叶片病害分类

    YOLOV5 实战对小麦叶片病害检测分类数据集,包含代码、数据集、训练好的权重参数,经测试,代码可以直接使用。 【yolov5】项目总大小:300MB 训练了10个epoch,top1准确度分别达到了0.93+。这里仅仅训练了10个epoch,网络还没收敛,加大轮次可以获取更高的网络性能 【如何训练】摆放好datasets数据,即可训练 更多yolov5改进介绍、或者如何训练,请参考: https://blog.csdn.net/qq_44886601/category_12605353.html

    单片机课程实验-秒表实现

    1.了解LED数码管的工作原理,为秒表时钟模块的实现打下基础。 LED数码管是一种常用的数字显示器件,通过控制每个LED的亮灭来显示数字。在秒表时钟模块中,我们需要利用LED数码管的这一特性,通过单片机控制数码管的显示,从而实现时钟的功能。因此,了解LED数码管的工作原理对于实现秒表时钟模块至关重要。 2.掌握51单片机与LED数码管的接口技术,是实现秒表时钟模块的关键。 51单片机是一种常用的微控制器,可以通过接口与外部设备进行通信。在秒表时钟模块中,我们需要通过单片机与LED数码管之间的接口,控制数码管的显示。因此,掌握51单片机与LED数码管的接口技术是实现秒表时钟模块的关键。在实际操作中,我们需要根据接口协议和数据传输方式,编写相应的程序来控制数码管的显示。 3.合理利用定时器/计数器,是实现秒表时钟模块的效率保障。 在秒表时钟模块中,我们需要实现计时功能,这需要使用到定时器/计数器。定时器/计数器可以用来产生计时脉冲,从而控制秒表的计时。通过合理利用定时器/计数器,可以提高秒表时钟模块的计时精度和效率。在实际操作中,我们需要根据具体的应用场景和需求,选择合适的定时器/计数器参

    机械设计2T双轴变位机L型sw12非常好的设计图纸100%好用.zip

    机械设计2T双轴变位机L型sw12非常好的设计图纸100%好用.zip

    node-v10.16.3-sunos-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    STM32F4通过串口屏设置ADF4351输出信号频率,HAL库开发

    本项目基于HAL库开发,使用STM32F407作为主控芯片,串口通信过程使用了特殊的帧头帧尾,实现了通过串口触摸屏设置ADF4351输出信号频率。项目包含了陶晶弛串口屏的界面设计。

    金融产品设计报告.docx

    金融产品设计报告.docx

    C++ 实验三 基本图形生成算法

    C++ 实验三 基本图形生成算法

    6金融需求理论.docx

    6金融需求理论.docx

    node-v10.22.1-sunos-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    数据结构实验代码行编辑.rar

    数据结构实验代码

    新版广州社区智慧养老解决方案ppt.docx

    新版广州社区智慧养老解决方案ppt

    Python实现Python解释器 优秀课程作业

    python实现python解释器 1.该python解释器是一个模拟堆栈机器的虚拟机 2.基本指令:LOAD_VALUE,将数据压入栈中,STORE_NAME,将栈顶内容存入变量,LOAD_NAME,将变量的内容压入栈中 dis是一个字节码反汇编器 3.LOAD_CONST相当于LOAD_VALUE,STORE_FAST相当于STORE_NAME 4.帧:帧包含了一段代码运行所需要的信息与上下文环境,帧在代码执行时被动态地创建与销毁,每一个帧的创建对应一次函数调用,所以每一个帧都有一个code object与其关联,同时一个code object可以拥有多个帧,因为一个函数可以递归调用自己多次 5.调用栈:每当你在当前函数内调用一次函数就在当前调用栈上压入所调用的函数的帧,在所调用函数返回时再将该帧弹出 数据栈:执行字节码操作时使用的栈 块栈:用于特定的控制流,比如循环与异常处理;每一个帧都拥有自己的数据栈与块栈

    index.vue

    index.vue

    AIGC-Stable Diffusion图片精准高效生成,AI能实现,并能真正落地的电商应用案例-课程网盘链接提取码下载

    课程大纲 真实场景专题(理论+实操) 1)产品AI创意图案例 2)产品图生成真实场景案例 3)AI产品图设计流程思路分享 二、服装模特专题(实操) 1)人台图生成AI模特案例 2)服装图换脸换背景案例 3)平面衣服上身A1模特案例 4)如何精准控制人物姿势 家居场景专题(实操)三、 1)家居产品生成AI场景 2)精准控制更换家具材质 3)家居装修风格快速切换

    App商业化的平衡之道:左手营收,右手用户,如何找到平衡点?.docx

    App商业化的平衡之道:左手营收,右手用户,如何找到平衡点?.docx

    设计沉思录 二手车工具产品设计.docx

    设计沉思录 二手车工具产品设计.docx

    文件预览kkFileView安装包下载【win+Linux】4.0版本

    文件预览kkFileView安装包下载【win+Linux】4.0版本

Global site tag (gtag.js) - Google Analytics