- 浏览: 216070 次
最新评论
-
jin8000608172:
Profiler类是自己写的还是开源jar里面的,如果是开源j ...
isInfoEnabled究竟多有用? -
u011358822:
www.baidu.com
实战Concurrent -
u011358822:
[url][img][list][*]引用[/list][/i ...
实战Concurrent -
water_lang:
这本书我买了一本,但是我还是没找到该怎么解决事务这块,mong ...
《MongoDB实战》译者序 -
DREAM_UTOPIA:
每周推荐非常好,观点独到,涉及面广,谢谢
每周推荐阅读2013Q2汇总
前段时间,公司里组织了一次代码检查,其中有一条检查项让我有些费解:
理由是大量的不输出的日志对性能会有影响(日志中存在字符串拼接)。如果说只是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); } } }
但这样的代码真的就能解决问题了吗?答案是“不能”!字符串拼接还存在。
那让我们分两个部分来看一下这个问题:
- 日志中的字符串拼接真的对性能影响很大吗
- 加不加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看看结果:
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类是个工具类,作用是记录调用的时间;代码有截取;第一个循环主要是预热一下,不在统计范围内)
经过了几次测试,结果如下:
+---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为该阶段的具体耗时,后面的百分比是该阶段耗时在这个统计内所占的百分比)
对上述数据分析后,可以得到这样的结论:
- 日志输出前的判断确实有效,但效果甚微
- 在日志级别较低,不会输出的情况下,日志的耗时基本可以忽略 ,20万次的调用耗时在50ms内
- 在日志级别满足输出要求时,3种方法差别不大
针对第3条再做些补充说明,日志输出大的开销应该在IO上,计算应该不会很多,也不该很多,如果存在大量的运算请自己考虑下是不是有问题;既然是确认要输出的日志,那增加判断其实是种浪费,虽然这种判断的开销可以忽略。
综上所述,个人建议在日常系统中无需对日志增加isInfoEnabled判断,想通过这种处理来优化效果的作用不会很明显,还是把精力从日志移到数据库和远程调用上效果更好些。 (特殊情况下,如果在日志中有复杂的操作,可以酌情考虑,但个人不倾向于复杂的日志)
评论
完全可以啊。关键看你怎么拦截。
能给个例子看看吗?另外,还好看么?
是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。
用AOP的方式就不难看了。
AOP可以精细到某一行代码前后了吗?仅仅是函数级别没有什么意义。
完全可以啊。关键看你怎么拦截。
是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。
用AOP的方式就不难看了。
AOP可以精细到某一行代码前后了吗?仅仅是函数级别没有什么意义。
是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。
用AOP的方式就不难看了。
是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
反正我现在已经放弃了这种检查了,因为实在太难看了,影响代码阅读,性能就将就一下吧。
public void debug(Object message) { if(repository.isDisabled(Level.DEBUG_INT)) return; ...
在 Log4j 1.2.15中,其实源代码中已经判断是否开放了debug等权限。
还有一点,楼主可能没有注意,在低版本的实现中,可能没有加这个类似的判断,并且可读性更好。
是的,我也同意,不加这个判断,对代码的可读性很有好处,文章中我也说了,加这个很难看。
关于Log4j里是否默认有这个判断,不是每个系统都能用新版本,而且日志实现很多,也不一定直接用Log4j,比如我们在项目里用的就是公司自己封装过的。
public void debug(Object message) { if(repository.isDisabled(Level.DEBUG_INT)) return; ...
在 Log4j 1.2.15中,其实源代码中已经判断是否开放了debug等权限。
还有一点,楼主可能没有注意,在低版本的实现中,可能没有加这个类似的判断,并且可读性更好。
发表评论
-
那些你该了解的Spring子项目
2013-03-20 14:39 1558去年年底,Spring Framework正式发布了3.2G ... -
《实战HotSpot JVM GC》分享Slides
2012-06-22 12:09 1823前阵子在QClub上海站做了一个与JVM GC优化相关的分享, ... -
如何简单模拟Web服务
2011-03-21 18:57 2254在SOA环境中,系统不可 ... -
代码中的坏味道
2011-01-07 04:09 1408最近InfoQ上连载了郑烨 ... -
一个关于基于注解的Spring MVC的简单介绍
2009-05-26 17:52 2901前段时间给同事们做了一个关于Spring MVC的分享,简单介 ... -
实战Concurrent
2009-04-08 13:51 4048编写多线程的程序一直都是一件比较麻烦的事情,要考虑很多事情,处 ... -
关于表单防重复提交一些东东
2009-03-13 22:27 2402前阵子弄了些表单防重复提交的东西,想整理整理,免得下次要用时再 ... -
Eclipse对JDK说“不”了
2008-09-30 21:21 2005最近开始翻译Spring Recipes了,既然是讲Sprin ... -
自己写的第一段AOP代码。
2005-04-13 22:36 1642代码1:使用安全的rand() ... -
FIFO、LRU、OPT的三个简单实现
2005-04-28 16:11 32401.利用随机数产生一个指令序列,共320条指令。其地址按下述原 ... -
JMeter小实验——JSP性能简单测试
2006-04-17 16:56 2497首先要做的当然是到Apache的站点下在一个最 ... -
利用缓存提高小型站点性能
2006-04-23 16:11 1256最近结束了 ... -
都是JDBC-ODBC惹得祸
2006-09-23 11:23 1226前阵子的一个项目需要 ... -
Fedora下瞎折腾了一个半小时,还是Sun的虚拟机可靠
2007-03-23 22:00 1236今天早上跑去了网络学院做技术支持, 他们碰到的问题是一个web ... -
一个泛型Hibernate DAO实现
2007-08-06 11:58 1792自己四处参考,写写抄抄折腾出来的一个泛型Hibernate D ... -
自己出的几道关于Spring和Hibernate的面试题
2008-02-29 16:49 2531很简单的题目,随便出着玩的,如果你是高手就请直接跳过,若有雷同 ... -
Spring MVC快速上手教程
2008-04-01 22:00 3129Spring Framework可以被使用在很多场合之中,考虑 ... -
一些关于Liferay的使用心得
2008-01-07 15:58 2195Liferay是一个出色的Java开源Portal产品,其中整 ...
相关推荐
- **Log Interface**:定义了所有日志操作的基础接口,包括 `isErrorEnabled()`, `isInfoEnabled()`, `info(Object message)`, `error(Object message)` 等方法。 - **JdkLogger Class**:实现 `Log` 接口的一个具体...
if (logger.isInfoEnabled()) { logger.info("Root WebApplication... ``` 这个监听器确保了Spring的单例行为,并在Web应用启动时加载配置,使得所有必要的Bean能够被实例化并注入到其他组件中。 在完成上述配置...
- 可以使用`logger.ErrorException()`或`logger.FatalException()`方法记录带有异常信息的日志,这对于调试异常情况非常有用。 8. **性能考虑**: - 为了提高性能,Log4Net 提供了`IsDebugEnabled`、`...
- **日志输出控制**:为了在运行时控制日志输出,log4net提供了布尔属性如`IsDebugEnabled`、`IsInfoEnabled`等,用于检查当前日志级别的启用状态。 2. **Repository**:Repository充当了log4net的容器,用于维护...
if (log.isInfoEnabled()) { log.info("获得的参数url为: " + tmpHeaderValue); log.info("系统取得的url为:" + currentURI); } if (" ".equals(tmpHeaderValue)) { isValid = false; if (log....
if (logger.isInfoEnabled()) { logger.info("这是一条信息"); } if (logger.isWarnEnabled()) { logger.warn("这是一个警告信息"); } if (logger.isErrorEnabled()) { logger.error("这是一个错误信息"); } if ...
if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();...
if (logger.isInfoEnabled()) { logger.info(String.format("压缩ZIP 花费时间 %s(s) ", (System.currentTimeMillis() - zipStart) / 1000)); } if (zipFile.exists()) { long downloadStart = System....
3. Logger工作原理:在`org.apache.log4j.Logger`类中,每个logger都有一个优先级,通过`isDebugEnabled()`、`isInfoEnabled()`等方法检查当前日志级别是否允许输出特定级别的日志。 4. 配置解析:`org.apache.log4...
if (log.IsInfoEnabled) log.Info(message); } // 添加其他级别的日志方法,如Error(), Debug(), Warn()等 } ``` 配置文件`log4net.config`中,我们可以定义不同的appender(输出目标)如控制台、文件、数据库...
这个类封装了对log4net的调用,避免了在多处代码中重复配置log4net的繁琐操作。MyLog类中包括了Info、Error、Debug等不同级别的日志输出方法,根据实际情况记录不同级别的日志信息。 ```csharp public static class...
if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { // 初始化spring-...
在调用`logger.debug`、`logger.info`等方法前,可以先通过`logger.isDebugEnabled()`、`logger.isInfoEnabled()`等方法来检查当前的日志级别是否允许输出该级别的日志。这样做的好处是可以避免在非debug级别环境下...
if (log.isInfoEnabled()) { log.info("Info message"); } if (log.isWarnEnabled()) { log.warn("Warning message"); } if (log.isErrorEnabled()) { log.error("Error message"); } if (log....
if (logger.isInfoEnabled()) { logger.info("Loading properties file from " + location); } try { PropertiesLoaderUtils.fillProperties( props, new EncodedResource(location, this.fileEncoding), this...