为了提高性能,在循环中暂时使用String还是直接调用它会产生任何性能差异吗?

时间:2014-05-16 15:44:57

标签: java algorithm

ArrayList<String> strings = new ArrayList<String>(){{
    add("a");
    add("b");
    add("c");
    add("d");
}};
int size = strings.size();

这是我想知道哪一个使用的部分以及原因。

for(int i = 0; i < size; i++){
    String temp_string = strings.get(i);
    someMethod(temp_string);
}

for(int i = 0; i < size; i++){
       someMethod(strings.get(i));
}

是否会产生任何性能差异? temp_string是否指向保存在内存的arraylist部分中的String,或者它是否在每个循环的内存中分配另一个空格来保存temp_string

4 个答案:

答案 0 :(得分:2)

如果它有所不同,我会感到惊讶,不仅因为在堆栈上创建临时引用然后将其复制到函数调用参数的成本非常低,而且主要是因为没有严重的编译器会错过明显的优化。

但是,如果没有测试,你无法确定。

答案 1 :(得分:1)

在两种情况下都可以帮助您了解正在发生的事情,看看您应该期待什么样的差异:

  • 在使用临时变量的情况下,您获取对象引用,将其存储在临时变量中,将其传递给someMethod,然后让临时变量超出范围
  • 在没有临时变量的情况下,您获得ab对象引用,并直接将其传递给someMethod

从这里你应该立即得出结论,如果存在任何差异,那么优势就是在实现方面做更少的事情,即没有临时变量的实现。

在某些情况下,制作临时变量可以帮助您提高性能。以下是一个示例,基于您的代码段:

for(int i = 0; i < size; i++){
    String temp_string = strings.get(i);
    for (int j = 0 ; j != 10000 ; j++) {
        someMethod(temp_string, 2*j+1);
    }
}

此代码段会重复使用strings.get(i)一万次的结果,因此当someMethod非常快时,您可能会看到一些差异。但是,这不是一个保证的胜利:你需要进行分析,看看变化是否有所作为。

可能对快速someMethod实施和长strings列表产生影响的另一个变化是foreach循环:

for(String s : strings){
    someMethod(s);
}

当您不需要在循环内使用索引时,这是适用的。代码可能会快一点,因为在内部它可以避免在数组列表中进行索引检查。

答案 2 :(得分:0)

首先,我相信第二个算法你的意思是strings.get(i)not temp_string.get(i)因为没有声明temp_string。

现在,第一种算法需要更长的时钟周期。请注意,您具有所有相同的调用以及分配操作。在大多数情况下,这可以忽略不计,但在高性能环境中,这可能会很昂贵。

答案 3 :(得分:0)

temp_string的范围在两种情况下都是相同的,即仅限于for循环。生成的字节代码几乎相同。

  public void foo1(List<String> list) {
    for (int i=0; i<list.size(); i++) {
      String str = list.get(i);
      someMethod(str);
    }
  }

  public void foo2(List<String> list) {
    for (int i=0; i<list.size(); i++) {
      someMethod(list.get(i));
    }
  }


public void foo1(java.util.List);
  Code:
   0:   iconst_0
   1:   istore_2
   2:   iload_2
   3:   aload_1
   4:   invokeinterface #2,  1; //InterfaceMethod java/util/List.size:()I
   9:   if_icmpge   34
   12:  aload_1
   13:  iload_2
   14:  invokeinterface #3,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   19:  checkcast   #4; //class java/lang/String
   22:  astore_3
   23:  aload_0
   24:  aload_3
   25:  invokevirtual   #5; //Method someMethod:(Ljava/lang/String;)V
   28:  iinc    2, 1
   31:  goto    2
   34:  return

public void foo2(java.util.List);
  Code:
   0:   iconst_0
   1:   istore_2
   2:   iload_2
   3:   aload_1
   4:   invokeinterface #2,  1; //InterfaceMethod java/util/List.size:()I
   9:   if_icmpge   32
   12:  aload_0
   13:  aload_1
   14:  iload_2
   15:  invokeinterface #3,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   20:  checkcast   #4; //class java/lang/String
   23:  invokevirtual   #5; //Method someMethod:(Ljava/lang/String;)V
   26:  iinc    2, 1
   29:  goto    2
   32:  return

与第一个循环(分配给String,然后将String传递给方法)的唯一区别是额外的astore_3aload_3。这应该很容易让JVM进行优化。

另请注意,第二个循环(无额外变量赋值)更易于阅读。