DANCE WITH JAVA

开发出高质量的系统

常用链接

统计

积分与排名

好友之家

最新评论

开启一个新的问题(关于声明变量的性能问题) (问题结束)

在我们编程的过程中经常会遇到这样的问题。
for (int i=0;i<n;i++){
    String str 
= //
}

String str 
= null;
for(int i=0;i<n;i++){
    str 
= //
}



在印象中一直认为方法二的性能好于方法一,但是差距应该很小。但因为一位别人的回文说方法一极大的影响了性能,所以想写个例子证明一下相差很小。例子如下:
public class TestOt {
    
public static void main(String[] args) {
        
long n=1000000000;
        
long start = System.currentTimeMillis();
        test2(n);
        
long end = System.currentTimeMillis();
        System.out.println(end
-start);
    }

    
public static void test1(long n){
        
for (int i=0;i<n;i++){
            String str 
= "";
        }

    }

    
public static void test2(long n){
        String str 
= null;
        
for (int i=0;i<n;i++){
            str 
= "";
        }

    }

}
测试的结果是当n=10亿次的时候差距是1秒,所以说差距应该是很小的,符合我原始的记忆,但是另一个问题来了,测试的结果是
方法一:3300毫秒左右
方法二:4300毫秒左右
结果刚好相反,于是更改方法

public class TestOt {
    
public static void main(String[] args) {
        
long n=1000000000;
        
long start = System.currentTimeMillis();
        test1(n);
        
long end = System.currentTimeMillis();
        System.out.println(end
-start);
    }

    
public static void test1(long n){
        
for (int i=0;i<n;i++){
            String str 
= null;
        }

    }

    
public static void test2(long n){
        String str 
= null;
        
for (int i=0;i<n;i++){
        }

    }

}

结果依旧。

没办法,取得字节码,对比
public class TestOt extends java.lang.Object{
public TestOt();
  Code:
   
0:   aload_0
   
1:   invokespecial   #8//Method java/lang/Object."<init>":()V
   4:   return

public static void test1(int);
  Code:
   
0:   iconst_0
   
1:   istore_1
   
2:   goto    10   
   
5:   aconst_null
   
6:   astore_2
   
7:   iinc    11
   
10:  iload_1   
   
11:  iload_0
   
12:  if_icmplt       5
   
15:  return

public static void test2(int);
  Code:
   
0:   aconst_null
   
1:   astore_1
   
2:   iconst_0
   
3:   istore_2
   
4:   goto    10   
   
7:   iinc    21
   
10:  iload_2   
   
11:  iload_0  
   
12:  if_icmplt       7
   
15:  return
}

结果是感觉还是应该是方法二快,那为什么反而方法一快了1秒左右呢?
不得而知,现在我个人猜测的想法是可能有两种情况:
1,JLS的底层定义决定的,有什么特殊的优化?
2,因为方法二比方法一虽然少了在循环中的部分,但是引用的声明周期反而是更长了,是否因为引用存在造成了方法二的栈操作消耗了大部分时间?
猜想一有待于JLS文档的查阅,我会在有空的时候查询,猜想二正在想办法证明。
看文章的朋友,如果谁了解麻烦指点一下,是我的测试方法写的有问题,还是别的原因,谢谢。

最后:问题已经基本了解了原因,见回复中的讨论,谢谢--daydream 的帮忙。

posted on 2007-02-11 03:26 dreamstone 阅读(2046) 评论(27)  编辑  收藏 所属分类: jdk相关

评论

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 03:29 dreamstone

最开始的测试是在Eclipse中测试的,怕是eclipse的问题,在控制台下也做了测试,虽然得到的数字有微小偏差,但依然是方法一比方法二快1秒左右。问题继续。。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 06:10 JonneyQuest

据说是因为局部变量放在堆栈中的原因。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 09:26 daydream

在我的机器上测试,两者可以看作是一样快的。对每一个方法运行多次,结果也会稍有偏差,第一个方法:3375(次数比较多)、3390。
第二个方法:3375(次数比较多)、3390、3391、3406。

对于下面2段代码来说:
for (int i=0;i<n;i++){
String str = //
}
String str = null;
for(int i=0;i<n;i++){
str = //
}
区别只是str变量的作用域不同---意味着:代码1的str变量的偏移位置在出了循环的作用域以后,可以被分配给下一个出现的局部变量,而代码2str的位置会一直占有,直到方法结束。

之所以,有人感觉代码2快,我想是一种错觉吧,就是以为代码1会在循环中重复声明变量str--实际上不是这样。

另外,JLS是什么东东?  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-11 10:32 dreamstone

to:JonneyQuest
详细说说?
  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-11 10:36 dreamstone

to:daydream
你怎么运行的,不是两个一块调用的吧,如果是用循环多次调用求平均值,活着两个一块调用是不准的。如果你是循环调用,活着同时调用,你可以把两个函数的调用顺序换一下,会有较大的差距。
JLS=The Java Language Specification
讲述的是Java语言的特性,很多东西C++和Java是不同的,JLS中有描述。例如lazyloading的单态在Java中是不可实现的,这个就因为Java的优化造成的,通过JLS可以查到。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 12:40 daydream

没有,我是分开运行的,直接运行你的代码。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 13:55 sinoly

这里问题大了。创建对象第一忌:不要在循环体中创建对象。这种做法会在内存中保存N份这个对象的引用会浪费大量的内存空间(虽说内存便宜,可以进行硬件升级),同时JVM的GC机制会因为这些无谓的对象做大量的回收工作,系统不慢都不行呀
.........................................................................................

很简单的一个道理,你用String对象根本看不出效果。如果你换成个自定义对象或者图形对象呢?你的第一种做法会在堆内存中生成大量的垃圾对象,这些对象首先占用内存,二则在对于速度的影响上它不会马上体现出来(毕竟在内存够用的情况下无法体现),一旦堆内存中的eden area满了,GC机制开始起作用了那么你就会觉得你的程序速度狂降。。。。
所以说,第二种做法才是王道!

呵呵,刚刚写了一篇关于java优化编程的文字,希望可以提供帮助
http://www.blogjava.net/sinoly/archive/2007/02/11/99205.html  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 14:05 sinoly

相对而言,性能的影响并不只是这段代码的执行速度。需要考虑在JVM种它的处理方式,以及这种方式对资源占用的情况。很多性能问题都是在日积月累中体现的。只是丂一条语句所谓的执行速度来判断效率,个人感觉很不合理  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 17:05 daydream

to sinoly :

“创建对象第一忌:不要在循环体中创建对象。这种做法会在内存中保存N份这个对象的引用会浪费大量的内存空间”

这是误解。完全没有在内存中保存N份对象的引用,循环体内声明的对象也只是在java栈中占据一个位置。

反而在循环体内声明的对象因为其作用域只是在循环体内,更节约内存(虽然微乎其微)。
  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-11 17:06 dreamstone

to:sinoly
我在平时写代码的时候也不是在循环体内创建,但是记忆中差别不大。
对于你的说法,
1,String看不出效果,自定义对象和图形对象能有效果?
在印象中无论是什么,这里保留的都是一个引用,应该是一样大的。所以应该没有对象和图形的差别。而且第一种做法不会产生垃圾对象,只会出现大量的引用。一个引用占用的内存是很小的,不会是大量的。但是如果循环次数很多,也是可观的,所以我平时也是写在循环体之外。
2,如果写在外边,其实并不一定就快,因为在里边写的话过了循环体就过了它的有效范围,可以被回收了,虽然并不一定立即回收,但如果第二种写法对象则不能回收。恰恰相反,如果对象很大,例如图形控件,活着保存大量数据的Bean,这个时候这个对象要到函数结束才会被回收,如果函数体很长,活着函数的执行时间很长,那么这个才是更消耗内存的。所以说哪种写法要看情况而定。
3,如何判定一个程序的好坏?这个是个综合问题,要考虑很多因素,但是在印象种无论如何方法二应该是比方法一快的,结果刚好相反,开启这个帖子主要是为了这个问题。就是为什么会这样?而不是讨论哪个方法更好。
  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 17:17 daydream

java字节码中,对于每一个方法都有一个max_locals属性,指出方法的局部变量所需要的存储空间(以字为单位)。

对于一楼的例子,如果把
String str = //
移到循环体外,则max_locals会比在循环体内更大。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-11 17:35 dreamstone

to:sinoly
看了你写的关于对象创建的问题,我们说的不是一个问题啊。
你的问题是:在循环体种创建相同的对象,就是作用一样的对象,这个当然是浪费内存了。这种问题不需要再讨论。
我的问题是这样的,并不再创建对象上,例如,如下问题,从List种取出对象,可以有两种写法,
List<Object> list= //...一个已经存在的List
方法一
for(int i=0;i<list.size();i++){
Object obj = list.get(i);
}
方法二
Object obj= null;
for(int i=0;i<list.size();i++){
obj= list.get(i);
}
这个里边根本没有创建对象的问题,有的问题是方法一会多很多引用,方法二会让一个引用保存期很长,同时对象有效期也变的很长。(其实你的性能优化的文章种应该指出这个问题的。)

另外提示一下,对于我第一个例子中写的:
for (int i=0;i<n;i++){
String str = "";
}
在这个函数中只会创建一个对象,因为String是非可变对象,虚拟机会自动重用,这个你可以参照一下JLS中的解释。只有这种情况才是浪费
for (int i=0;i<n;i++){
String str = new String("");
}
最后感谢你参与,另外提一点建议:
1,回文或者写文章前应该先确认一下自己的观点是否是对的,最好给出证明,虽然确认了也不能保证一定是对的,但是至少做过了,这是一种态度。我也是一直这么要求自己,无论多么简单的问题,都给出一个思考的过程,因为这样对看文章的人有帮助。例如你上边说到的,如果是自定义对象活着图形对象的观点,刚好是错误的证明,你可以这样试试。虚拟机内存设置64M,如果你在List中取出一个60M的对象,然后在循环之后再new一个10M的对象,方法一是可以运行的,虽然说慢,但方法二就OutOfMemory了。除非你在循环之后设置
变量= null,但这种做法是否更好只得商榷.

最后说明一点,应用开发和底层框架开发其实有很多东西是不同的。如果实际负责过项目就了解的。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 17:41 dreamstone

@daydream
谢谢再次回复,想问一下关于max_locals这个属性,
1,为什么放到循环体外反而会更大呢?能给简单讲一下为什么吗?活着给一个能查到原因的方向。
2,另外这个max_locals变大后为什么会影响性能呢?在什么时候会使用到max_locals这个属性呢?
谢谢
  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 18:40 daydream

对下面2个方法,foo1需要的max_locals是4,foo2是5,

foo1需要的4大概是:this变量占1个字、方法参数x占1个字、
第一段循环的时候,变量i占一个字、s1占一个字,第二个循环的时候,i、s1已经超出作用域,所以,变量j、s2占用了和i、s1重叠的空间,所以最多需要4个字就够了。

foo2方法中s1的作用域直到方法结束,所以需要5个字长度。

max_locals变大后应该不会影响到性能,但是我这儿的意思是说,将局部变量放在循环体内声明并不会导致性能下降。

void foo1(int x) {
for (int i = 0; i < 1000; i++) {
String s1 = "...";
}
for (int j = 0; j < 1000; j++) {
String s2 = "....";
}
}

void foo2(int x) {
String s1 = "...";
for (int i = 0; i < 1000; i++) {
// other code.....
}
for (int j = 0; j < 1000; j++) {
String s2 = "....";
}
}
  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 18:44 daydream

另外,将局部变量放在循环体内声明,也不会导致多出来很多引用。
因为,局部变量对应于java栈的偏移是在编译时就确定的,并不是在运行期动态分配的。循环体内的局部变量对应的是同一个偏移位置。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 18:48 daydream

另外,max_locals只是编译器在编译时确定,存放在字节码中,供JVM在运行期调用方法时分配java栈帧大小用的,对于程序员应该没什么用,因为程序也没办法访问java栈。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 19:07 dreamstone

现在大概明白了max_locals的作用,但是我这测试出的问题还在。不知道为什么第一个方法要比第二个方法快。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 19:10 dreamstone

另外我看你上边写的用我的代码,测试结果是接近的,可我怎么测都是差距一秒啊,你的运行环境 ?
我是xp下,试过eclipse运行,试过直接控制台用java命令运行。
jdk1.5  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) 2007-02-11 19:37 daydream

运行你的第二段测试代码:
先运行test1共5次,结果:3406、3453、3391、3453、3391
然后把代码改成test2,运行5次,结果:
3406、3406、3406、3437、3391

运行环境:XP、512M内存、Eclipse3.2下,JDK6.0,没有加启动参数,默认最大内存好像是64M。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-12 06:00 dreamstone

郁闷了,我的两台电脑,测试的结果都是稳定的差不到1秒
xp sp2 eclipse3.2.1 jdk1.5 启动参数也没加,最大内存开始是512,后来改成256和64都试了。
结果test1:2938 2953 2954 2954 2969
test2:3797 3797 3781 4000 3797 3797
我今天再找别人试一下,看看什么情况。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题)[未登录] 2007-02-12 09:56 dreamstone

又找了几个同事帮忙测试了一下,果然,有的差距是100毫秒,有的200,有的基本没差距。我公司的电脑差距是200,也就是说我家里的测试不准确,真实不可思议,我在家测了很多次,都稳定在差距1000毫秒,呵呵。不过问题总算解决了。
另外经过测试发现在这个问题上,amd的cpu比intel的快,单核的比双核的快。。有意思。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束)[未登录] 2007-02-12 18:13 JIm

注意jdk版本,一些基本的东西随着JDK不断的更新,都会有改变。对JAVA没什么好印象!  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束) 2007-02-12 19:15 dreamstone

有些是跟版本有关的,这个跟版本没关系,呵呵。
为什么对java印象不好呢,每个语言都有它的好处和坏处。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束) 2007-02-12 20:10 dreamstone

刚才又做了个测试,一个很有意思的结果:使用ibm的ibm_sdk50测试结果刚好相反,执行10亿次,方法二比方法一快了200毫秒。挺有意思。

不过结论是一样的,就是两种方法性能差别很小,但方法二让对象的有效期变长了,如果是大对象(例如图形对象,数据bean对象)则不好,所以一般情况下应选择方法一的写法。或者方法二的写法加上手动清空释放对象。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束) 2007-02-13 09:02 Welkin Hu

牛啊!
俺可很少关注到这一层。俺通常来说会用第一种的。可以省下俺4300ms写代码的时间。  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束) 2007-07-05 14:27 coffey

我在你的代码中分别添加了如下代码:

public static void test1(long n){
for (int i=0;i<n;i++){
String str = "";
}
System.gc();
}
public static void test2(long n){
String str = null;
for (int i=0;i<n;i++){
str = "";
}
System.gc();
}

测试结果分别为:

test1: 3787, 3710, 3756, 3616, 3523

test2: 3538, 3678, 3507, 3756, 3460

我的观点与sinoly一致!  回复  更多评论   

# re: 开启一个新的问题(关于声明变量的性能问题) (问题结束) 2007-07-06 12:52 dreamstone

@coffey
你一定是没看完评论,可以看看我回复sinoly的评论,你就知道为什么了。
很多东西不要想当然,要测试、分析。只有理论上和实践上都通过才是正确地。否则一定是有错误的地方。  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: