문제

무엇을,어떤 경우는 성과 차이점은 다음과 같은 두 개의 루프가?

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

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
도움이 되었습니까?

해결책

항목 46에서 효과적인 자바 작성자 : Joshua Bloch :

릴리스 1.5에 도입 된 For-Each 루프는 반복자 또는 색인 변수를 완전히 숨겨서 혼란과 오류의 기회를 제거합니다. 결과 관용 관용구는 컬렉션 및 배열에 동일하게 적용됩니다.

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

결장을 볼 때 (:),“in”으로 읽으십시오. 따라서 위의 루프는 "요소의 각 요소 e에 대해"로 읽습니다. 어레이에 대해서도 for-each 루프를 사용하기위한 성능 페널티가 없습니다. 실제로, 배열 인덱스의 한계를 한 번만 계산하므로 일부 상황에서는 루프에 대한 일반적인 성능 이점을 제공 할 수 있습니다. 손 으로이 작업을 수행 할 수 있지만 (항목 45) 프로그래머가 항상 그렇게하지는 않습니다.

다른 팁

이 모든 루프는 정확히 동일합니다. 나는 단지 2 센트를 던지기 전에 이것을 보여주고 싶습니다.

먼저, 목록을 통해 고전적인 루핑 방법 :

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

둘째, 오류가 적기 때문에 선호하는 방법 ( "oops, 루프 내의 루프에서 변수 i와 j를 혼합 한 것"을 몇 번이나 수행 했습니까?).

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

셋째, 루프를 위해 마이크로 최적화 :

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

이제 실제 2 센트 : 적어도 테스트 할 때, 세 번째는 간단한 작업으로 각 유형의 루프에 걸리는 시간에 대해 밀리 초를 계산할 때 가장 빠릅니다. 누구나 관심이있는 경우 Windows에 JRE1.6U10을 사용합니다.

적어도 세 번째는 가장 빠른 것 같지만, 내가 본 것처럼 루핑 코드의 모든 곳 에서이 구멍 최적화를 구현할 위험을 감수하고 싶은지 스스로에게 물어봐야합니다. t는 일반적으로 실제 프로그램에서 가장 시간이 많이 걸리는 부분입니다 (또는 아마도 내가 아는 잘못된 분야에서 일하고있을 수도 있습니다). 그리고 내가 자바의 구실에서 언급 한 것처럼 각각의 루프 (일부는 그것을 의미합니다 반복 루프 그리고 다른 사람은 for-in loop) 당신은 그것을 사용할 때 그 하나의 멍청한 버그에 부딪 칠 가능성이 적습니다. 그리고 이것이 다른 것보다 더 빠를 수있는 방법에 대해 토론하기 전에 Javac은 바이트 코드를 전혀 최적화하지 않는다는 것을 기억하십시오 (거의 어쨌든).

그러나 미세 최적화에 빠지거나/또는 소프트웨어가 많은 재귀 루프를 사용하는 경우 세 번째 루프 유형에 관심이있을 수 있습니다. 전후에 소프트웨어를 변경 한 후에도 소프트웨어를 벤치마킹 한 후에도 소프트웨어를 벤치마킹해야합니다.

이를 위해 각 반복해야 하는 일반적으로 좋습니다."Get"접근 느려질 수 있는 경우 목록의 구현을 사용하지 않는 임의 액세스를 지원합니다.는 경우,예를 들어,LinkedList 사용하는 것이 부과 통과 비용,반면 각 항목에 대한 접근 방식을 사용하는 반복기의 추적 위 목록에 있습니다.더 많은 정보에 뉘앙스의 각 항목에 대한 루프.

나는 생각한 문서입니다 지금 여기: 새로운 위치

링크는 다음과 같 여기에 죽었습니다.

글쎄, 성능 영향은 대부분 무의미하지만 0은 아닙니다. Javadoc을 보면 RandomAccess 상호 작용:

경험상, 목록 구현은 클래스의 일반적인 인스턴스에 대해이 루프 인 경우이 인터페이스를 구현해야합니다.

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

이 루프보다 더 빨리 실행됩니다.

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

그리고 For-Each Loop은 반복기와 함께 버전을 사용하고 있습니다. ArrayList 예를 들어, 맞춤형 루프가 가장 빠르지 않습니다.

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

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

다음은 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

그 오라클은 무엇입니까?

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

인덱싱 대신 반복기를 사용하는 것이 항상 좋습니다. 이는 반복자가 목록 구현에 최적화 될 가능성이 높기 때문에 색인 (호출)이 아닐 수도 있습니다. 예를 들어 LinkedList는 목록이지만 요소를 통해 인덱싱은 반복기를 사용하여 반복하는 것보다 느리게됩니다.

Foreach는 코드의 의도를 명확하게 만들고 일반적으로 매우 작은 속도 개선보다 선호됩니다.

인덱스 루프를 볼 때마다 나는 그것이 내가 무엇을할지 확인하기 위해 조금 더 구문 분석해야합니다. 생각한다 예를 들어 0에서 시작합니까, 종말점 등을 포함하거나 배제합니까?

내 시간의 대부분은 코드를 읽는 데 소비되는 것처럼 보이며 (내가 쓴 사람이거나 다른 사람이 쓴) 명확성은 거의 항상 성능보다 더 중요합니다. 핫스팟이 놀라운 일을하기 때문에 요즘 성능을 해산하기 쉽습니다.

다음 코드 :

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를 실행하고 있습니다.

일반적으로 핫스팟은 많은 간접 및 간단한 붉은 런닝 작업을 최적화하므로 일반적으로 분명한 곳이 많거나 많이 중첩되지 않으면 걱정하지 않아야합니다.

반면, INDEXED GET on inlinkedList는 ITERATOR on LinkedList를 호출하는 것보다 훨씬 느리므로 반복자를 사용할 때 (명시 적으로 또는 암시 적으로) 가독성을 유지하면서 성능을 피할 수 있습니다.

Arraylist 또는 Vector와 같은 것이 있더라도 "get"은 간단한 배열 조회입니다. 두 번째 루프에는 여전히 첫 번째 루프가없는 추가 오버 헤드가 있습니다. 나는 그것이 첫 번째보다 약간 느리기를 기대합니다.

확실히 알 수있는 유일한 방법은 그것을 벤치마킹하는 것입니다. 소리가 들리는 것만 큼 간단하지 않습니다. JIT 컴파일러는 코드에 예기치 않은 작업을 수행 할 수 있습니다.

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

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

결과는 거기에 있습니다 ~이다 차이, 그리고 매우 큰 목록을 가진 매우 구속 된 환경에서는 눈에 띄는 차이가 될 수 있습니다. 테스트에서 각 루프마다 두 배가 걸렸습니다. 그러나 그들의 테스트는 400,000 정수의 배열 목록을 초과했습니다. 배열의 요소 당 실제 차이는 6입니다. 마이크로 초. 나는 테스트하지 않았고 그들은 말하지 않았지만, 프리미티브가 아닌 객체를 사용하여 차이가 약간 더 크다고 기대하지만, 당신이 요청할 내용의 규모를 모르는 도서관 코드를 구축하지 않는 한 여전히 반복하기 위해, 나는 그 차이가 강조 할 가치가 없다고 생각합니다.

변수 이름으로 objectArrayList, 나는 그것이 인스턴스라고 가정합니다 java.util.ArrayList. 이 경우 성능 차이는 눈에 띄지 않습니다.

반면에, 그것이 인스턴스라면 java.util.LinkedList, 두 번째 접근법은 List#get(int) O (n) 작업입니다.

따라서 루프의 논리에 의해 색인이 필요하지 않으면 첫 번째 접근 방식은 항상 선호됩니다.

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

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

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

아무도 명백한 것을 언급하지 않았다는 것이 이상합니다. foreach는 메모리를 반복자의 형태로 할당하는 반면, 루프의 정상은 메모리를 할당하지 않습니다. 안드로이드 게임의 경우 이는 쓰레기 수집가가 주기적으로 실행되기 때문에 문제가됩니다. 게임에서는 쓰레기 수집가가 실행되기를 원하지 않습니다. 따라서 드로우 (또는 렌더링) 메소드에서 Foreach 루프를 사용하지 마십시오.

수락 된 답변은 Arraylist의 예외적 인 사례를 제외하고 질문에 답변합니다 ...

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

그래서 나는 여기에 정답을 추가해야합니다.

개발자 문서에서 바로 :-

루프 용으로 향상된 (때때로 "for-each"루프라고도 함)는 반복 가능한 인터페이스를 구현하는 컬렉션과 배열에 사용할 수 있습니다. 컬렉션을 사용하면 반복자가 할당되어 Hasnext () 및 Next ()에 인터페이스 호출을합니다. Arraylist를 사용하면 손으로 쓴 카운트 루프가 약 3 배 빠르지 만 (JIT와 함께) 약 3 배 빠르지 만 다른 컬렉션의 경우 루프 구문의 향상된 것이 명시 적 반복자 사용과 정확히 동일합니다.

배열을 통해 반복하기위한 몇 가지 대안이 있습니다.

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는 루프를 통해 모든 반복마다 배열 길이를 한 번 얻는 비용을 아직 최적화 할 수 없기 때문에 0 ()은 가장 느립니다.

하나 ()가 더 빠릅니다. 모든 것을 로컬 변수로 끌어 당겨 조회를 피합니다. 배열 길이 만 성능 이점을 제공합니다.

2 ()는 JIT가없는 장치의 경우 가장 빠르며 JIT가있는 장치의 경우 하나와 구별 할 수 없습니다. Java 프로그래밍 언어의 버전 1.5에 도입 된 루프 구문을 위해 향상된 것을 사용합니다.

따라서 기본적으로 루프 용으로 향상된 루프를 사용해야하지만 성능 크리티컬 Arraylist 반복을위한 손으로 쓴 카운트 루프를 고려하십시오.

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+" ");
        }
    }
}

예, for-each 변형은 정상보다 빠릅니다 index-based-for-loop.

for-each 변형 사용 iterator. 따라서 순회하는 것이 정상보다 빠릅니다 for 인덱스 기반 루프.
이 때문입니다 iterator 가리키기 때문에 트래버스에 최적화됩니다. 다음 요소 직전과 이전 요소 직후. 존재의 이유 중 하나 index-based-for-loop 느리게 말하면해야합니다 매번 요소 위치를 계산하고 이동하십시오 그 어느 것도 아닙니다 iterator.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top