在Java中,将str.length()的值存储在变量中再在for循环中使用,是否会提高性能?

10

简而言之,JVM是否会在内部优化以下代码

public void test(String str)
{
    int a = 0;

    for( int i = 0; i < 10; i++)
    {
        a = a + str.length();
    }
}

要像下面这个一样高效地表现:

public void test(String str)
{
    int len = str.length();
    int a = 0;

    for( int i = 0; i < 10; i++)
    {
        a = a + len;
    }
}
如果它进行了优化,那么它是否通过在内部缓存str.length()的值来完成?

请注意:public int length() {return count;} - jmj
2个回答

6
我创建了以下两种方法:
public void test(String str) {
    int len = str.length();
    int a = 0;
    for (int i = 0; i < 10; i++) {
        a = a + len;
    }
}

public void test2(String str) {
    int a = 0;
    for (int i = 0; i < 10; i++) {
        a = a + str.length();
    }
}

然后我使用 javap -v 为第一个方法 test 生成了代码。

  public void test(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=2
         0: aload_1
         1: invokevirtual #16                 // Method java/lang/String.length:()I
         4: istore_2
         5: iconst_0
         6: istore_3
         7: iconst_0
         8: istore        4
        10: goto          20
        13: iload_3
        14: iload_2
        15: iadd
        16: istore_3
        17: iinc          4, 1
        20: iload         4
        22: bipush        10
        24: if_icmplt     13
        27: return

对于test2

并且
  public void test2(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: iconst_0
         1: istore_2
         2: iconst_0
         3: istore_3
         4: goto          17
         7: iload_2
         8: aload_1
         9: invokevirtual #16                 // Method java/lang/String.length:()I
        12: iadd
        13: istore_2
        14: iinc          3, 1
        17: iload_3
        18: bipush        10
        20: if_icmplt     7
        23: return

因此,答案似乎是存储长度有一定的优势(它会产生更短的字节码,相当于23行与27行),这表明它可能会表现得更好,但我怀疑它实际上是否可以被测量。特别是在代码已经JIT编译之后。

最后,您可能需要考虑

public void test(String str)
{
  int a = 0;
  for( int i = 0, len = str.length(); i < 10; i++) {
    a = a + len;
  }
}

或者只是
int a = 10 * str.length();

它确实会生成更短的字节码。但是在test(..)中,由于在for循环内计算str.length() 10次,运行时是否会增加? - deepak
@deepak 在我的finally注释中吗?不是的。那个len在循环开始之前只设置一次;因此它相当于在循环外部设置它(但它仅在循环体的词法范围内)。 - Elliott Frisch
1
@deepak 我真的怀疑它是可以被测量的。一旦JIT已经运行,你可能无论如何都看不到任何实际差异。如果你没有时间或精力在你的环境中完全测试它们,尝试编写愚蠢的代码并避免尝试微观优化。编写愚蠢的代码 - Elliott Frisch
@gknicker 请查看另一个答案,该答案直接进行了基准测试。我要指出的是,观察到的差异大约为(3/5000000)。 - Elliott Frisch
2
我喜欢你指出避免微观优化,但个人认为这是一种“免费的优化”,在某些情况下可以增加清晰度。只要您合理地处理作用域等问题,我认为声明一个变量来保存该值没有任何问题,特别是在string.length()someObject.someDeceptiveSometimesSlowCallThatOccassionallyChecksInOnTheCaymanIslands()的时候,我认为这样做也没有什么问题。 - Selali Adobor
显示剩余2条评论

6

很好的答案,Elliot F。

我进行了一个简单得多的测试,并运行了这两种方法,每个方法重复了很多次,然后计时。

第一种方法(只计算长度一次)始终比第二种方法更快。

这是我创建的整个测试类;

package _testing;

import java.util.Date;

public class Speed {

    long count = 5000000;
    public static void main(String[] args) {

        long start, finish; 
        Speed sp = new Speed(); 

        start = new Date().getTime();
        sp.test("test");
        finish = new Date().getTime();
        System.out.println("test 1:"+(finish - start));

        start = new Date().getTime();
        sp.test2("test");
        finish = new Date().getTime();
        System.out.println("test 2:"+(finish - start));

    }


    public void test(String str) {
        int len = str.length();
        int a = 0;
        for (int i = 0; i < count; i++) {
            a = a + len;
        }
    }

    public void test2(String str) {
        int a = 0;
        for (int i = 0; i < count; i++) {
            a = a + str.length();
        }
    }   
}

输出结果如下:
test 1:7
test 2:22

3
我很喜欢你写了一个基准测试,它确实表明有真正可测量的差异,但我要指出的是,你观察到~(3/5000000) x的差异,这在统计学上并不显著。 - Elliott Frisch
你应该将整个方法包含在循环中,否则 "test()" 将获得不公平的优势。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接