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


184

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

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

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

@Keparo : 그것은이다 루프 "각"이 아닌 "에 대한-에서"루프
ANDE 터너

Java에서는 "for each"라고하지만 Objective C에서는 "for In"루프라고합니다.
damithH


확장 된 for 루프 성능은 여기에서 설명합니다 : stackoverflow.com/questions/12155987/…
eckes

경우에도 차이가있을 것입니다, 그것은 코드의이 작품은 초당 시대의 수조를 실행하지 않는 한 가독성을 선호해야 작은 있도록 할 것이다. 그리고 어쨌든 조기 최적화를 피하려면 적절한 벤치 마크가 필요합니다.
Zabuzard

답변:


212

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) 프로그래머가 항상 그렇게하는 것은 아닙니다.


48
for-each 루프에는 인덱스 카운터에 액세스 할 수있는 방법이 없습니다 (존재하지 않기 때문에)
basszero

예, 그러나 해당 카운터는 이제 루프 외부에서 볼 수 있습니다. 물론, 그것은 간단한 수정이지만 각각에 대한 것입니다!
Indolering

74
반복자를 할당하면 성능이 저하됩니다. 나는 안드로이드 라이브 배경 화면에서 매우 평행 한 코드를 가지고있었습니다. 가비지 수집기가 미쳐가는 것을 보았습니다. for-each 루프가 많은 다른 (짧은 수명) 스레드에 임시 반복자를 할당했기 때문에 가비지 수집기가 많은 작업을 수행했기 때문입니다. 일반 인덱스 기반 루프로 전환하면 문제가 해결되었습니다.
gsingh2011

1
@ gsingh2011 그러나 이것은 또한 임의 액세스 목록을 사용하는지 여부에 따라 다릅니다. 임의 액세스 목록이없는 for-each를 사용하는 것보다 비 랜덤 액세스 목록에 대한 인덱스 기반 액세스를 사용하는 것이 훨씬 나쁩니다. 인터페이스 List로 작업하고 실제 구현 유형을 모른다면 목록이 (구현) RandomAccess의 인스턴스인지 확인할 수 있습니다. docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce

3
@ gsingh2011 Android 문서 ( developer.android.com/training/articles/perf-tips.html#Loops )는 다른 컬렉션이 아닌 ArrayList에서만 foreach를 사용할 때 성능이 저하된다고 언급합니다. 그게 당신의 사건인지 궁금합니다.
Viccari

29

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

먼저 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 = -1; ++i < size;) { /* do something using strings.get(i) */ }

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

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

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


5
++ i <= size의 for 루프는 "1 기반"입니다. 예를 들어 루프 내부의 get-method는 값 1, 2, 3 등을 위해 호출됩니다.
volley

15
마이크로 최적화 루프를 작성하는 더 좋은 방법은 for (int i = 0, size = strings.size (); ++ i <= size;) {} 이것은 크기의 범위를 최소화하기 때문에 바람직합니다.
Dónal December

1
첫 번째 요소는 첫 번째 요소를 건너 뛰고 루프를 통과 할 때 i = 1부터 시작하지 않습니다. for 루프는 불필요합니다. int n = 문자열. 길이; while (n-> 0) {System.out.println ( ""+ n + ""+ strings [n]); }
Lassi Kinnunen

1
@ Dónal 루프 패턴은 첫 번째 패턴을 그리워하고 IOOBE를 제공합니다. 이것은 방법 : 대 (INT I = -1, 크기는 list.size = (); I ++ <크기)
네이 아담스

1
"이러한 루프는 모두 똑같습니다"가 잘못되었습니다. 하나는 임의 액세스를 사용 get(int)하고 다른 하나는을 사용합니다 Iterator. n 번 수행하기 때문에 LinkedList성능 for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }이 훨씬 나쁜 곳을 고려하십시오 get(int).
Steve Kuo

13

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

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

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


12

성능에 미치는 영향은 거의 중요하지 않지만 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 루프가 가장 빠르지 않습니다.


정말요? 배열이있는 것조차? 여기에서 stackoverflow.com/questions/1006395/… 를 읽습니다 . 반복자와 관련이 없습니다.
Ondra Žižka

@ OndraŽižka : for-each 루프는 Iterables를 반복 할 때 반복자를 사용 하지만 배열 은 사용 하지 않습니다. 배열에는 인덱스 변수가있는 for-loop가 사용됩니다. 이에 대한 정보는 JLS에 있습니다.
Lii

예, 먼저 toArray를 사용하여 배열로 생성해야하며 비용이 듭니다.
Lassi Kinnunen

6

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

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

다음은 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로 이것을 시도했습니다.


7
디스 어셈블리를 읽으려고하는 사람들에게는 결과는 루프 내부에서 생성 된 코드가 동일하지만 for-each 설정이 두 번째 인수에 대한 참조를 포함하는 추가 임시 변수를 만든 것 같습니다. 여분의 숨겨진 변수가 등록되어 있지만 매개 변수 자체가 코드 생성 중이 아닌 경우 for-each가 더 빠릅니다. 매개 변수가 for (;;) 예제에 등록 된 경우 실행 시간은 동일합니다. 벤치 마크가 필요하십니까?
로빈 데이비스

4

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


10
성능 최적화에 "항상"과 같은 것은 없다고 생각합니다.)
eckes

4

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

인덱싱 된 루프를 볼 때마다 그것이 생각 하는 대로 작동하는지 확인하기 위해 조금 더 구문 분석해야합니다 .

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


4

다음 코드 :

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 루프에서) 반복자를 사용할 때 성능 저하를 피할 수 있습니다.


3

"get"이 간단한 배열 조회 인 ArrayList 또는 Vector와 같은 경우에도 두 번째 루프에는 첫 번째 루프에는없는 추가 오버 헤드가 있습니다. 나는 그것이 첫 번째보다 조금 느릴 것으로 기대합니다.


첫 번째 루프는 각 요소도 가져와야합니다. 이를 위해 장면 뒤에 반복자를 만듭니다. 그들은 실제로 동등합니다.
Bill the Lizard

C의 관점에서, 이터레이터는 포인터를 증가시킬 수 있지만, get은 매번 i의 값에 포인터의 너비를 곱해야합니다.
Paul Tomblin

사용하는 목록 유형에 따라 다릅니다. 그래도 get이 사용하는 것이 결코 빠르지 않을 때가 있다고 생각합니다.
Bill the Lizard


3

다음은 Android 개발 팀의 차이점에 대한 간략한 분석입니다.

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

그 결과가 있다는 것입니다 이다 차이, 그리고 매우 큰 목록에 매우 절제 환경이 눈에 띄는 차이가있을 수 있습니다. 테스트에서 각 루프마다 두 배의 시간이 걸렸습니다. 그러나 그들의 테스트는 400,000 개의 정수로 이루어진 배열 목록에 대한 것이었다. 배열의 요소 당 실제 차이는 6 마이크로 초 입니다. 나는 테스트하지 않았지만 그들은 말하지 않았지만, 기본 요소가 아닌 객체를 사용하여 차이가 약간 더 커질 것으로 기대하지만, 요구되는 것의 규모를 모르는 라이브러리 코드를 작성하지 않는 한 여전히 반복하기 위해, 그 차이는 강조 할 가치가 없다고 생각합니다.


2

변수 이름으로 objectArrayList, 나는 그것이java.util.ArrayList . 이 경우 성능 차이는 눈에 띄지 않습니다.

반면에의 인스턴스 인 java.util.LinkedList경우 두 번째 방법은List#get(int) O (n) 연산 .

따라서 루프의 논리에 인덱스가 필요하지 않은 경우 첫 번째 방법이 항상 선호됩니다.


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

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

둘 다 동일하지만 쉽고 안전한 프로그래밍 사용을 위해 두 번째 사용 방식에서 오류가 발생할 가능성이 있습니다.


1

foreach가 메모리를 (반복기 형태로) 할당하는 반면, 정상적인 for 루프는 메모리를 할당하지 않습니다. Android 게임의 경우 가비지 수집기가 주기적으로 실행되므로 문제가됩니다. 게임에서는 가비지 컬렉터가 실행되는 것을 원하지 않습니다. 따라서 draw (또는 render) 메소드에서 foreach 루프를 사용하지 마십시오.


1

허용 된 답변은 예외적 인 ArrayList 사례를 제외하고 질문에 대답합니다 ...

대부분의 개발자는 ArrayList에 의존하기 때문에 (거의 믿습니다)

따라서 여기에 정답을 추가해야합니다.

개발자 문서에서 직접 :-

Iterable 인터페이스를 구현하는 컬렉션과 배열에 대해 향상된 for 루프 ( "for-each"루프라고도 함)를 사용할 수 있습니다. 콜렉션을 사용하면 hasNext () 및 next ()에 대한 인터페이스 호출을 수행하기 위해 반복자가 할당됩니다. ArrayList를 사용하면 손으로 쓴 카운트 루프가 JIT의 유무에 관계없이 약 3 배 빠르지 만 다른 컬렉션의 경우 향상된 for 루프 구문은 명시적인 반복자 사용법과 정확히 동일합니다.

배열을 반복하는 몇 가지 대안이 있습니다.

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;
    }
}

JIT는 루프를 반복 할 때마다 배열 길이를 한 번만 얻는 비용을 아직 최적화 할 수 없기 때문에 zero ()가 가장 느립니다.

one ()이 빠릅니다. 조회를 피하면서 모든 것을 로컬 변수로 가져옵니다. 어레이 길이 만 성능 이점을 제공합니다.

two ()는 JIT가없는 장치에 가장 빠르며 JIT가있는 장치에 대해서는 one ()과 구별 할 수 없습니다. Java 프로그래밍 언어 버전 1.5에 도입 된 향상된 for 루프 구문을 사용합니다.

따라서 기본적으로 Enhanced for 루프를 사용해야하지만 성능에 중요한 ArrayList 반복을 위해 수작업으로 카운트 된 루프를 고려하십시오.


-2

예, for-each변형이 normal보다 빠릅니다 index-based-for-loop.

for-each변형 사용 iterator. 따라서 순회는 for인덱스 기반의 일반 루프 보다 빠릅니다 .
이는 다음 요소 바로 앞과 이전 요소 바로 다음을iterator 가리 키기 때문에 이송에 최적화되어 있기 때문입니다 . 속도가 느려지는 이유 중 하나 는를 사용 하지 않는 매번 요소 위치계산하고 이동해야하기 때문입니다 .index-based-for-loopiterator


-3
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+" ");
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.