Programming

for 루프와 for-each 루프간에 성능 차이가 있습니까?

procodes 2020. 5. 23. 23:14
반응형

for 루프와 for-each 루프간에 성능 차이가 있습니까?


다음 두 루프 사이의 성능 차이는 무엇입니까?

for (Object o: objectArrayList) {
    o.DoSomething();
}

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}

Joshua Bloch의 효과적인 Java 항목 46에서 :

릴리스 1.5에서 도입 된 for-each 루프는 반복자 또는 인덱스 변수를 완전히 숨겨서 혼란을 없애고 오류가 발생할 기회를 제거합니다. 결과 관용구는 컬렉션과 배열에 동일하게 적용됩니다.

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

콜론 (:)이 보이면 "in"으로 읽습니다. 따라서 위의 루프는 "요소의 각 요소 e에 대해"로 읽습니다. 배열에 대해서도 for-each 루프를 사용하면 성능이 저하되지 않습니다. 실제로, 배열 인덱스의 한계를 한 번만 계산하므로 일부 상황에서는 일반 for 루프보다 약간의 성능 이점을 제공 할 수 있습니다. 이 작업을 직접 수행 할 수 있지만 (항목 45) 프로그래머가 항상 그렇게하는 것은 아닙니다.


이 모든 루프는 똑같이 작동합니다. 두 센트를 던지기 전에 이것을 보여주고 싶습니다.

먼저 List를 반복하는 고전적인 방법입니다.

for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }

둘째, 오류가 덜 발생하기 때문에 선호되는 방법입니다 (루프 내의 루프에서 변수 i와 j를 혼합 한 횟수는 몇 번입니까?).

for(String s : strings) { /* do something using s */ }

셋째, 마이크로 최적화 된 for 루프 :

int size = strings.size();
for(int i=0;++i<=size;) { /* do something using strings.get(i) */ }

이제 실제 2 센트 : 적어도 이것을 테스트 할 때 세 번째는 간단한 조작으로 각 유형의 루프에 걸리는 시간에 대해 밀리 초를 계산할 때 가장 빠르며 수백만 번 반복되었습니다. 누군가가 관심이 있다면 Windows에서 jre1.6u10으로.

적어도 세 번째 것이 가장 빠를 것 같지만, 실제로 본 루핑은 루핑 코드의 어디에서나이 구멍 최적화를 구현할 위험을 감수하고 싶은지 스스로에게 물어봐야합니다. t 대부분의 실제 프로그램에서 가장 많은 시간을 소비합니다 (또는 아마도 잘못된 분야에서 일하고 있습니다). 또한 Java for-each 루프 의 서문에서 언급 한 것처럼 (일부는 Iterator 루프라고 하고 다른 루프for-in 루프라고 함 ) 사용하면 특정 바보 같은 버그에 부딪 칠 가능성이 적습니다. 그리고 이것이 어떻게 다른 것보다 더 빠를 수 있는지에 대해 토론하기 전에 javac는 바이트 코드를 전혀 최적화하지 않는다는 것을 기억하십시오 (거의 거의 어쨌든). 그냥 컴파일합니다.

그래도 마이크로 최적화에 있고 소프트웨어가 많은 재귀 루프를 사용하는 경우 세 번째 루프 유형에 관심이있을 수 있습니다. for 루프를 이처럼 미세하게 최적화 된 루프로 변경하기 전후에 소프트웨어를 벤치마킹해야합니다.


for-each 루프가 일반적으로 선호됩니다. 사용중인 List 구현이 임의 액세스를 지원하지 않으면 "get"접근 방식이 느려질 수 있습니다. 예를 들어, LinkedList가 사용되면 순회 비용이 발생하지만 for-each 방식은 목록에서 해당 위치를 추적하는 반복자를 사용합니다. for-each 루프뉘앙스 에 대한 자세한 정보 .

나는 기사가 지금 여기에 있다고 생각한다 : 새로운 위치

여기에 표시된 링크가 죽었습니다.


성능에 미치는 영향은 거의 중요하지 않지만 0은 아닙니다. RandomAccess인터페이스의 JavaDoc을 보면 :

경험상, 클래스의 일반적인 인스턴스에 대해 다음 루프가 발생하면 List 구현에서이 인터페이스를 구현해야합니다.

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

이 루프보다 빠르게 실행됩니다.

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

for-each 루프는 반복자와 함께 버전을 사용하므로 ArrayListfor-each 루프가 가장 빠르지 않습니다.


불행히도 차이가있는 것 같습니다.

두 종류의 루프에 대해 생성 된 바이트 코드를 보면 서로 다릅니다.

다음은 Log4j 소스 코드의 예입니다.

/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java에는 다음을 정의하는 Log4jMarker라는 정적 내부 클래스가 있습니다.

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

표준 루프 사용시 :

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

for-each로 :

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

THAT Oracle은 무엇입니까?

Windows 7에서 Java 7 및 8로 이것을 시도했습니다.


인덱싱 대신 반복자를 사용하는 것이 항상 좋습니다. 인덱스 (get 호출)가 그렇지 않은 경우 반복자가 List 구현에 대해 최적화되었을 가능성이 높기 때문입니다. 예를 들어 LinkedList는 List이지만 해당 요소를 통한 색인 작성은 반복자를 사용하여 반복하는 것보다 느립니다.


foreach는 코드의 의도를보다 명확하게 해주 며 일반적으로 아주 작은 속도 향상 (있는 경우)보다 선호됩니다.

인덱싱 된 루프를 볼 때마다 그것이 생각 하는 대로 작동하는지 확인하기 위해 조금 더 구문 분석해야합니다. 예 : 0부터 시작합니까, 끝점 등을 포함 또는 제외합니까?

내 시간의 대부분은 코드 (내가 쓴 또는 다른 사람이 쓴)를 읽는 데 소비되는 것으로 보이며 명확성은 거의 항상 성능보다 중요합니다. Hotspot은 놀라운 일을하기 때문에 요즘에는 성능을 쉽게 해제 할 수 있습니다.


다음 코드 :

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

내 시스템에서 다음과 같은 출력을 제공합니다.

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

OracleJDK 1.7 업데이트 6에서 Ubuntu 12.10 alpha를 실행하고 있습니다.

일반적으로 HotSpot은 많은 간접적 인 지시와 간단한 중복 작업을 최적화하므로, 순서가 많거나 너무 많이 중첩되어 있지 않으면 걱정할 필요가 없습니다.

반면, LinkedList의 색인화 된 get은 LinkedList의 반복자에서 next를 호출하는 것보다 훨씬 느리므로 반복자를 사용할 때 (명시 적으로 또는 암시 적으로 for-each 루프에서) 반복자를 사용할 때 성능 저하를 피할 수 있습니다.


Even with something like an ArrayList or Vector, where "get" is a simple array lookup, the second loop still has additional overhead that the first one doesn't. I would expect it to be a tiny bit slower than the first.


The only way to know for sure is to benchmark it, and even that is not as simple as it may sound. The JIT compiler can do very unexpected things to your code.


Here is a brief analysis of the difference put out by the Android development team:

https://www.youtube.com/watch?v=MZOf3pOAM6A

The result is that there is a difference, and in very restrained environments with very large lists it could be a noticeable difference. In their testing, the for each loop took twice as long. However, their testing was over an arraylist of 400,000 integers. The actual difference per element in the array was 6 microseconds. I haven't tested and they didn't say, but I would expect the difference to be slightly larger using objects rather than primitives, but even still unless you are building library code where you have no idea the scale of what you will be asked to iterate over, I think the difference is not worth stressing about.


By the variable name objectArrayList, I assume that is an instance of java.util.ArrayList. In that case, the performance difference would be unnoticeable.

On the other hand, if it's an instance of java.util.LinkedList, the second approach will be much slower as the List#get(int) is an O(n) operation.

So the first approach is always preferred unless the index is needed by the logic in the loop.


1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

Both does the same but for easy and safe programming use for-each, there are possibilities for error prone in 2nd way of using.


It's weird that no one has mentioned the obvious - foreach allocates memory (in the form of an iterator), whereas a normal for loop does not allocate any memory. For games on Android, this is a problem, because it means that the garbage collector will run periodically. In a game you don't want the garbage collector to run... EVER. So don't use foreach loops in your draw (or render) method.


Accepted answer answers the question, apart from the exceptional case of ArrayList...

Since most developers rely on ArrayList(atleast I believe so)

So I am obligated to add the correct answer here.

Straight from the developer documentation:-

The enhanced for loop (also sometimes known as "for-each" loop) can be used for collections that implement the Iterable interface and for arrays. With collections, an iterator is allocated to make interface calls to hasNext() and next(). With an ArrayList, a hand-written counted loop is about 3x faster (with or without JIT), but for other collections the enhanced for loop syntax will be exactly equivalent to explicit iterator usage.

There are several alternatives for iterating through an array:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero() is slowest, because the JIT can't yet optimize away the cost of getting the array length once for every iteration through the loop.

one() is faster. It pulls everything out into local variables, avoiding the lookups. Only the array length offers a performance benefit.

two() is fastest for devices without a JIT, and indistinguishable from one() for devices with a JIT. It uses the enhanced for loop syntax introduced in version 1.5 of the Java programming language.

So, you should use the enhanced for loop by default, but consider a hand-written counted loop for performance-critical ArrayList iteration.


public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}

Yes, for-each variant is faster than than normal index-based-for-loop.

for-each variant uses iterator. So traversing is faster than normal for loop which is index based.
This is because iterator are optimized for traversing, because it is pointing to just before the next element and just after the previous element. One of the reason for being index-based-for-loop to be slow is that, it have to calculate and move to the element position each time which is not with the iterator.

참고URL : https://stackoverflow.com/questions/256859/is-there-a-performance-difference-between-a-for-loop-and-a-for-each-loop

반응형