- 浏览: 123984 次
- 性别:
- 来自: 部落格
文章分类
最新评论
-
只是打工的:
这文章是百度来的吧,不写个引用出处吗??
堆和栈 -
skill_job:
“主键设计有个原则,就是主键不应具有任何实际意义.”奇怪了,这 ...
数据库主键设计原则 -
zhou363667565:
写的还不错.
线程池 -
happyxuzheng:
我个人觉得选择数据类型为字符串的主键并且主键存入的是由数字进行 ...
数据库主键设计原则 -
tongfan:
都罗列出来了 可以!
js刷新页面
摘抄至:http://www.iteye.com/topic/150837[size=x-large][/size]
尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换
很多算法用尾递归方法表示会显得格外简明。编译器会自动把这种方法转换成循环,以提高程序的性能。但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换。这就意味着在 Java 语言中采用尾递归表示可能导致巨大的内存占用,而这并不是我们期望的结果。Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会。他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的同时做尾递归代码转换。
尾递归及其转换
相当多的程序包含有循环,这些循环运行的时间占了程序总运行时间的很大一部分。这些循环经常要反复更新不止一个变量,而每个变量的更新又经常依赖于其它变量的值。
如果把迭代看成是尾递归函数,那么,就可以把这些变量看成是函数的参数。简单提醒一下:如果一个调用的返回值被作为调用函数的值立即返回,那么,这个递归调用就是尾递归;尾递归不必记住调用时调用函数的上下文。
由于这一特点,在尾递归函数和循环之间有一个很好的对应关系:可以简单地把每个递归调用看作是一个循环的多次迭代。但因为所有可变的参数值都一次传给了递归调用,所以比起循环来,在尾递归中可以更容易地得到更新值。而且,难以使用的 break 语句也常常为函数的简单返回所替代。
但在 Java 编程中,用这种方式表示迭代将导致效率低下,因为大量的递归调用有导致堆栈溢出的危险。
解决方案比较简单:因为尾递归函数实际上只是编写循环的一种更简单的方式,所以就让编译器把它们自动转换成循环形式。这样您就同时利用了这两种形式的优点。
但是,尽管大家都熟知如何把一个尾递归函数自动转换成一个简单循环,Java 规范却不要求做这种转换。不作这种要求的原因大概是:通常在面向对象的语言中,这种转换不能静态地进行。相反地,这种从尾递归函数到简单循环的转换必须由 JIT 编译器动态地进行。
要理解为什么会是这样,考虑下面一个失败的尝试:在 Integers 集上,把 Iterator 中的元素相乘。
因为下面的程序中有一个错误,所以在运行时会抛出一个异常。但是,就象在本专栏以前的许多文章中已经论证的那样,一个程序抛出的精确异常(跟很棒的错误类型标识符一样)对于找到错误藏在程序的什么地方并没有什么帮助,我们也不想编译器以这种方式改变程序,以使编译的结果代码抛出一个不同的异常。
清单 1. 一个把 Integer 集的 Iterator 中的元素相乘的失败尝试
import java.util.Iterator;
public class Example {
public int product(Iterator i) {
return productHelp(i, 0);
}
int productHelp(Iterator i, int accumulator) {
if (i.hasNext()) {
return productHelp(i, accumulator * ((Integer)i.next()).intValue());
}
else {
return accumulator;
}
}
}
注意 product 方法中的错误。product 方法通过把 accumulator 赋值为 0 调用 productHelp。它的值应为 1。否则,在类 Example 的任何实例上调用 product 都将产生 0 值,不管 Iterator 是什么值。
假设这个错误终于被改正了,但同时,类 Example 的一个子类也被创建了,如清单 2 所示:
清单 2. 试图捕捉象清单 1 这样的不正确的调用
import java.util.*;
class Example {
public int product(Iterator i) {
return productHelp(i, 1);
}
int productHelp(Iterator i, int accumulator) {
if (i.hasNext()) {
return productHelp(i, accumulator * ((Integer)i.next()).intValue());
}
else {
return accumulator;
}
}
}
// And, in a separate file:
import java.util.*;
public class Example2 extends Example {
int productHelp(Iterator i, int accumulator) {
if (accumulator < 1) {
throw new RuntimeException("accumulator to productHelp must be >= 1");
}
else {
return super.productHelp(i, accumulator);
}
}
public static void main(String[] args) {
LinkedList l = new LinkedList();
l.add(new Integer(0));
new Example2().product(l.listIterator());
}
}
类 Example2 中的被覆盖的 productHelp 方法试图通过当 accumulator 小于“1”时抛出运行时异常来捕捉对 productHelp 的不正确调用。不幸的是,这样做将引入一个新的错误。如果 Iterator 含有任何 0 值的实例,都将使 productHelp 在自身的递归调用上崩溃。
现在请注意,在类 Example2 的 main 方法中,创建了 Example2 的一个实例并调用了它的 product 方法。由于传给这个方法的 Iterator 包含一个 0,因此程序将崩溃。
然而,您可以看到类 Example 的 productHelp 是严格尾递归的。假设一个静态编译器想把这个方法的正文转换成一个循环,如清单 3 所示:
清单 3. 静态编译不会优化尾调用的一个示例
int productHelp(Iterator i, int accumulator) {
while (i.hasNext()) {
accumulator *= ((Integer)i.next()).intValue();
}
return accumulator;
}
于是,最初对 productHelp 的调用,结果成了对超类的方法的调用。超方法将通过简单地在 iterator 上循环来计算其结果。不会抛出任何异常。
用两个不同的静态编译器来编译这段代码,结果是一个会抛出异常,而另一个则不会,想想这是多么让人感到困惑。
您的 JIT 会做这种转换吗?
因此,如清单 3 中的示例所示,我们不能期望静态编译器会在保持语言语义的同时对 Java 代码执行尾递归转换。相反地,我们必须依靠 JIT 进行的动态编译。JIT 会不会做这种转换是取决于 JVM。
要判断您的 JIT 会否转换尾递归的一个办法是编译并运行如下小测试类:
清单 4. 判断您的 JIT 能否转换尾递归
public class TailRecursionTest {
private static int loop(int i) {
return loop(i);
}
public static void main(String[] args) {
loop(0);
}
}
我们来考虑一下这个类的 loop 方法。这个方法只是尽可能长时间地对自身作递归调用。因为它永远不会返回,也不会以任何方式影响任何外部变量,因此如清单 5 所示替换其代码正文将保留程序的语义。
清单 5. 一个动态转换
public class TailRecursionTest {
private static int loop(int i) {
while (true) {
}
}
public static void main(String[] args) {
loop(0);
}
}
而且,事实上这也就是足够完善的编译器所做的转换。
如果您的 JIT 编译器把尾递归调用转换成迭代,这个程序将无限期地运行下去。它所需的内存很小,而且不会随时间增加。
另一方面,如果 JIT 不做这种转换,程序将会很快耗尽堆栈空间并报告一个堆栈溢出错误。
我在两个 Java SDK 上运行这个程序,结果令人惊讶。在 SUN 公司的 Hotspot JVM(版本 1.3 )上运行时,发现 Hotspot 不执行这种转换。缺省设置下,在我的机器上运行时,不到一秒钟堆栈空间就被耗尽了。
另一方面,程序在 IBM 的 JVM(版本 1.3 )上咕噜噜运行时却没有任何问题,这表明 IBM 的 JVM 以这种方式转换代码。
总结
记住:我们不能寄希望于我们的代码会总是运行在会转换尾递归调用的 JVM 上。因此,为了保证您的程序在所有 JVM 上都有适当的性能,您应始终努力把那些最自然地符合尾递归模式的代码按迭代风格编写。
但是请注意:就象我们的示例所演示的那样,以这种方式转换代码时很容易引入错误,不论是由人工还是由软件来完成这种转换。
其实从网上发现这样的文章并不难,希望大家在遇到问题时都能学会如何处理
学习的过程就是学会学习方法的过程,难道不是吗?
尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换
很多算法用尾递归方法表示会显得格外简明。编译器会自动把这种方法转换成循环,以提高程序的性能。但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换。这就意味着在 Java 语言中采用尾递归表示可能导致巨大的内存占用,而这并不是我们期望的结果。Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会。他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的同时做尾递归代码转换。
尾递归及其转换
相当多的程序包含有循环,这些循环运行的时间占了程序总运行时间的很大一部分。这些循环经常要反复更新不止一个变量,而每个变量的更新又经常依赖于其它变量的值。
如果把迭代看成是尾递归函数,那么,就可以把这些变量看成是函数的参数。简单提醒一下:如果一个调用的返回值被作为调用函数的值立即返回,那么,这个递归调用就是尾递归;尾递归不必记住调用时调用函数的上下文。
由于这一特点,在尾递归函数和循环之间有一个很好的对应关系:可以简单地把每个递归调用看作是一个循环的多次迭代。但因为所有可变的参数值都一次传给了递归调用,所以比起循环来,在尾递归中可以更容易地得到更新值。而且,难以使用的 break 语句也常常为函数的简单返回所替代。
但在 Java 编程中,用这种方式表示迭代将导致效率低下,因为大量的递归调用有导致堆栈溢出的危险。
解决方案比较简单:因为尾递归函数实际上只是编写循环的一种更简单的方式,所以就让编译器把它们自动转换成循环形式。这样您就同时利用了这两种形式的优点。
但是,尽管大家都熟知如何把一个尾递归函数自动转换成一个简单循环,Java 规范却不要求做这种转换。不作这种要求的原因大概是:通常在面向对象的语言中,这种转换不能静态地进行。相反地,这种从尾递归函数到简单循环的转换必须由 JIT 编译器动态地进行。
要理解为什么会是这样,考虑下面一个失败的尝试:在 Integers 集上,把 Iterator 中的元素相乘。
因为下面的程序中有一个错误,所以在运行时会抛出一个异常。但是,就象在本专栏以前的许多文章中已经论证的那样,一个程序抛出的精确异常(跟很棒的错误类型标识符一样)对于找到错误藏在程序的什么地方并没有什么帮助,我们也不想编译器以这种方式改变程序,以使编译的结果代码抛出一个不同的异常。
清单 1. 一个把 Integer 集的 Iterator 中的元素相乘的失败尝试
import java.util.Iterator;
public class Example {
public int product(Iterator i) {
return productHelp(i, 0);
}
int productHelp(Iterator i, int accumulator) {
if (i.hasNext()) {
return productHelp(i, accumulator * ((Integer)i.next()).intValue());
}
else {
return accumulator;
}
}
}
注意 product 方法中的错误。product 方法通过把 accumulator 赋值为 0 调用 productHelp。它的值应为 1。否则,在类 Example 的任何实例上调用 product 都将产生 0 值,不管 Iterator 是什么值。
假设这个错误终于被改正了,但同时,类 Example 的一个子类也被创建了,如清单 2 所示:
清单 2. 试图捕捉象清单 1 这样的不正确的调用
import java.util.*;
class Example {
public int product(Iterator i) {
return productHelp(i, 1);
}
int productHelp(Iterator i, int accumulator) {
if (i.hasNext()) {
return productHelp(i, accumulator * ((Integer)i.next()).intValue());
}
else {
return accumulator;
}
}
}
// And, in a separate file:
import java.util.*;
public class Example2 extends Example {
int productHelp(Iterator i, int accumulator) {
if (accumulator < 1) {
throw new RuntimeException("accumulator to productHelp must be >= 1");
}
else {
return super.productHelp(i, accumulator);
}
}
public static void main(String[] args) {
LinkedList l = new LinkedList();
l.add(new Integer(0));
new Example2().product(l.listIterator());
}
}
类 Example2 中的被覆盖的 productHelp 方法试图通过当 accumulator 小于“1”时抛出运行时异常来捕捉对 productHelp 的不正确调用。不幸的是,这样做将引入一个新的错误。如果 Iterator 含有任何 0 值的实例,都将使 productHelp 在自身的递归调用上崩溃。
现在请注意,在类 Example2 的 main 方法中,创建了 Example2 的一个实例并调用了它的 product 方法。由于传给这个方法的 Iterator 包含一个 0,因此程序将崩溃。
然而,您可以看到类 Example 的 productHelp 是严格尾递归的。假设一个静态编译器想把这个方法的正文转换成一个循环,如清单 3 所示:
清单 3. 静态编译不会优化尾调用的一个示例
int productHelp(Iterator i, int accumulator) {
while (i.hasNext()) {
accumulator *= ((Integer)i.next()).intValue();
}
return accumulator;
}
于是,最初对 productHelp 的调用,结果成了对超类的方法的调用。超方法将通过简单地在 iterator 上循环来计算其结果。不会抛出任何异常。
用两个不同的静态编译器来编译这段代码,结果是一个会抛出异常,而另一个则不会,想想这是多么让人感到困惑。
您的 JIT 会做这种转换吗?
因此,如清单 3 中的示例所示,我们不能期望静态编译器会在保持语言语义的同时对 Java 代码执行尾递归转换。相反地,我们必须依靠 JIT 进行的动态编译。JIT 会不会做这种转换是取决于 JVM。
要判断您的 JIT 会否转换尾递归的一个办法是编译并运行如下小测试类:
清单 4. 判断您的 JIT 能否转换尾递归
public class TailRecursionTest {
private static int loop(int i) {
return loop(i);
}
public static void main(String[] args) {
loop(0);
}
}
我们来考虑一下这个类的 loop 方法。这个方法只是尽可能长时间地对自身作递归调用。因为它永远不会返回,也不会以任何方式影响任何外部变量,因此如清单 5 所示替换其代码正文将保留程序的语义。
清单 5. 一个动态转换
public class TailRecursionTest {
private static int loop(int i) {
while (true) {
}
}
public static void main(String[] args) {
loop(0);
}
}
而且,事实上这也就是足够完善的编译器所做的转换。
如果您的 JIT 编译器把尾递归调用转换成迭代,这个程序将无限期地运行下去。它所需的内存很小,而且不会随时间增加。
另一方面,如果 JIT 不做这种转换,程序将会很快耗尽堆栈空间并报告一个堆栈溢出错误。
我在两个 Java SDK 上运行这个程序,结果令人惊讶。在 SUN 公司的 Hotspot JVM(版本 1.3 )上运行时,发现 Hotspot 不执行这种转换。缺省设置下,在我的机器上运行时,不到一秒钟堆栈空间就被耗尽了。
另一方面,程序在 IBM 的 JVM(版本 1.3 )上咕噜噜运行时却没有任何问题,这表明 IBM 的 JVM 以这种方式转换代码。
总结
记住:我们不能寄希望于我们的代码会总是运行在会转换尾递归调用的 JVM 上。因此,为了保证您的程序在所有 JVM 上都有适当的性能,您应始终努力把那些最自然地符合尾递归模式的代码按迭代风格编写。
但是请注意:就象我们的示例所演示的那样,以这种方式转换代码时很容易引入错误,不论是由人工还是由软件来完成这种转换。
其实从网上发现这样的文章并不难,希望大家在遇到问题时都能学会如何处理
学习的过程就是学会学习方法的过程,难道不是吗?
发表评论
-
CDN技术关键点
2013-10-10 14:48 851如何用BIND, GeoIP, Nginx, Varnish来 ... -
Nginx安装
2013-03-21 18:22 0Nginx作为一个后起之秀,他的迷人之处已经让很多人都投入了他 ... -
weblogic初学习
2011-12-08 09:52 1328BEA WebLogic是用于开发、集成、部署和管理大型分布式 ... -
Spring 依赖注入详解
2011-11-28 21:13 751简介: Spring 的依赖配 ... -
CentOs IP NDS的设置
2011-11-27 20:30 1364【学习如何修改centos的IP地址、DNS以及网关】 一、 ... -
反射器模式
2011-11-09 10:16 980Java NIO非堵塞应用通常 ... -
linux ,VI命令编辑器
2011-11-06 23:14 1146本章介绍Linux上最常用 ... -
堆和栈
2011-11-01 16:02 1712Java栈与堆 堆:顺序随意 栈:后进先出(Last-in/F ... -
超时任务
2011-10-31 09:04 1152import java.util.concurrent.Cal ... -
ReentrantLock 到底锁定了哪个对象了?
2011-10-31 09:04 3146一个简单的ReentrantLock的例子, 情景是几个朋友吃 ... -
BlockingQueue使用
2011-10-29 10:40 1574import java.util.concurrent.Arr ... -
线程池
2011-10-24 16:55 3219在什么情况下使用线程池? 1.单个任务处理的时间比 ... -
异步执行线程并可以得到返回值
2011-10-24 16:33 8921import java.util.concurrent.Cal ... -
Oracle rowid 优化分页
2011-10-18 16:15 1511今天看到一个分享,了解到DBA对oracle的分页方法进行优化 ... -
dmp命令导数据库
2011-10-15 22:24 896导出:exp usename/password@SID fil ... -
Struts2.0
2011-08-30 13:48 783struts.properties文件,该文 ... -
全面整合spring管理struts,spring管理hibernate
2011-08-30 13:37 1105黄金组合之全面整合,让spring管理struts,sprin ... -
BoneCp的设置
2011-03-29 08:24 1467一、BoneCP配置文件格式(bonecp-config.xm ... -
tomcat工程部署记录
2011-03-17 16:11 1012第一种方法:在tomcat中的conf目录中,在server. ... -
Spring + Hibernate 配置BoneCp
2011-03-15 21:34 2519为什么 BoneCP 连接池的性能这么高呢?(bonecp-0 ...
相关推荐
Java性能优化是IT行业中至关重要的一个领域,尤其是在大型企业级应用和互联网服务中,高效的Java代码能够显著提升系统运行效率,降低服务器资源消耗。以下是对这四本经典书籍中的核心知识点的详细介绍: 1. **...
Java性能调优是一个复杂而重要的领域,涉及到程序的运行效率、资源消耗以及系统的稳定性。这份“Java性能调优大全”提供了全面的指南,包括VisualVM的使用、JVM的性能优化、OMM(可能是指Oracle Management Monitor...
"提升你的Java应用性能改善数据处理Java开发Java经验技巧"这一主题涵盖了许多关键的知识点,下面将对其进行详细的阐述。 1. **JVM调优**:Java虚拟机(JVM)是Java程序运行的基础,理解和优化JVM参数对提升性能至关...
以下是一些常用的Java性能调优命令及其用法和相关知识点。 1. jps命令用于列出所有的JVM实例。通过该命令,开发者可以快速查看本机上所有Java应用程序的进程ID。例如,使用`jps`可以列出本机所有的JVM实例。 2. ...
微基准测试是评估性能改善的有效方法。 2. **展示代码**:优化应该基于代码本身,阅读并理解JDK源码是提高优化能力的重要步骤。通过查看源码,可以更深入地了解类和方法的工作原理,从而做出明智的优化决策。 **...
在Java虚拟机的上下文中,性能监控主要关注的是如何利用各种工具和技术来监控和改善JVM及其上运行的应用程序的性能。 ##### 目标: - **保持和管理系统健康**:通过持续监控系统的各项指标,可以及时发现潜在问题,...
JProbe在简单易用的集成化套件中,为servlet、JSP和EJB应用代码提供了强大的Java性能分析、内存纠错、代码覆盖及线程分析功能。 JProbe Profiler JProbe Profiler * JProbe Profiler JProbe Profiler内置了Call ...
Java程序性能优化是每个开发人员都需要关注的重要领域,特别是在企业级应用中,高效稳定的Java程序能够带来显著的业务优势。本资源包含一个PDF文档和相关的视频教程,旨在帮助你提升Java程序的速度和稳定性。 首先...
Java性能优化圣经!Java之父重磅推荐! 本书由曾任职于Oracle/Sun的性能优化专家编写,系统而详细地讲解了性能优化的各个方面,帮助你学习Java虚拟机的基本原理、掌握一些监控Java程序性能的工具,从而快速找到程序...
Java性能优化是提升Java应用程序效率的关键步骤,尤其对于大型系统或服务器端应用,优化可以显著改善程序的响应时间和资源消耗。本文将从三个方面探讨Java性能优化:虚拟机选择与优化、应用程序编程实践以及性能测试...
【如何提高Linux Java性能的分析】 Java应用程序在Linux平台上的性能优化是一个复杂而重要的主题,尤其是在高并发和大规模数据处理的场景下。Java线程堆分析是定位和解决性能问题的有效工具,它提供了关于应用程序...
理解JVM的工作原理,如堆内存分配、新生代与老年代的划分,以及垃圾回收器的工作机制(如CMS、G1、ZGC等),并根据应用特点调整合适的JVM参数,如-Xms、-Xmx、-XX:NewRatio等,能有效改善程序启动速度和运行时性能。...
通过Java代码技巧改善性能 在本篇文章中,我们将分享一些Java代码技巧,以提高程序性能。这些技巧虽然看起来微不足道,但是可以为系统性能带来成倍的提升。 首先,让我们来讨论try-catch语句的使用。在Java开发中...
《大话JAVA性能优化》这本书深入探讨了Java性能优化的重要性及其实践方法,旨在帮助开发者提升应用程序的效率,减少资源消耗,提高系统响应速度和用户体验。Java性能优化是软件开发过程中的一个重要环节,尤其是在...
"java重构改善既有代码设计"的主题涵盖了多种实践和原则,包括识别重构的信号、选择合适的重构技术以及如何安全地执行重构。 1. **重构的动机**:当代码变得难以理解、维护成本增加或者性能下降时,重构就显得尤为...
在Java编程领域,性能优化是不可或缺的一环,尤其是在构建大规模、高并发的系统时。"高性能java代码优化建议"这一主题涵盖了众多资深开发者积累的实践经验,旨在帮助开发者编写出更高效、性能更强的代码。以下是一些...