다음 두 루프 사이의 성능 차이는 무엇입니까?
for (Object o: objectArrayList) {
o.DoSomething();
}
과
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
다음 두 루프 사이의 성능 차이는 무엇입니까?
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 = -1; ++i < size;) { /* do something using strings.get(i) */ }
이제 실제 2 센트 : 적어도 이것을 테스트 할 때 세 번째는 간단한 조작으로 각 유형의 루프에 걸리는 시간에 대해 밀리 초를 계산할 때 가장 빠르며 수백만 번 반복되었습니다. 누군가가 관심이 있다면 Windows에서 jre1.6u10으로.
적어도 세 번째 것이 가장 빠를 것 같지만, 실제로 본 루핑은 루핑 코드의 어디에서나이 구멍 최적화를 구현할 위험을 감수하고 싶은지 스스로에게 물어봐야합니다. t 대부분의 실제 프로그램에서 가장 많은 시간을 소비합니다 (또는 아마도 잘못된 분야에서 일하고 있습니다). 또한 Java for-each 루프 의 서문에서 언급 한 것처럼 (일부는 Iterator 루프라고 하며 다른 것은 for-in 루프라고합니다. ) 사용할 때 특정 바보 같은 버그를 칠 가능성이 적습니다. 그리고 이것이 어떻게 다른 것보다 더 빠를 수 있는지에 대해 토론하기 전에 javac는 바이트 코드를 전혀 최적화하지 않는다는 것을 기억하십시오 (거의 거의 어쨌든). 그냥 컴파일합니다.
마이크로 최적화에 있고 소프트웨어가 재귀 루프를 많이 사용하는 경우 세 번째 루프 유형에 관심이있을 수 있습니다. for 루프를 이처럼 미세하게 최적화 된 루프로 변경하기 전후에 소프트웨어를 벤치마킹해야합니다.
get(int)
하고 다른 하나는을 사용합니다 Iterator
. n 번 수행하기 때문에 LinkedList
성능 for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
이 훨씬 나쁜 곳을 고려하십시오 get(int)
.
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 루프는 반복자와 함께 버전을 사용하므로 ArrayList
for-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로 이것을 시도했습니다.
다음 코드 :
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 루프에서) 반복자를 사용할 때 성능 저하를 피할 수 있습니다.
"get"이 간단한 배열 조회 인 ArrayList 또는 Vector와 같은 경우에도 두 번째 루프에는 첫 번째 루프에는없는 추가 오버 헤드가 있습니다. 나는 그것이 첫 번째보다 조금 느릴 것으로 기대합니다.
확실하게 알 수있는 유일한 방법은 벤치마킹하는 것입니다. 심지어 그것이 들리는 것처럼 간단하지는 않습니다 . JIT 컴파일러는 코드에 예상치 못한 작업을 수행 할 수 있습니다.
다음은 Android 개발 팀의 차이점에 대한 간략한 분석입니다.
https://www.youtube.com/watch?v=MZOf3pOAM6A
그 결과가 있다는 것입니다 이다 차이, 그리고 매우 큰 목록에 매우 절제 환경이 눈에 띄는 차이가있을 수 있습니다. 테스트에서 각 루프마다 두 배의 시간이 걸렸습니다. 그러나 그들의 테스트는 400,000 개의 정수로 이루어진 배열 목록에 대한 것이었다. 배열의 요소 당 실제 차이는 6 마이크로 초 입니다. 나는 테스트하지 않았지만 그들은 말하지 않았지만, 기본 요소가 아닌 객체를 사용하여 차이가 약간 더 커질 것으로 기대하지만, 요구되는 것의 규모를 모르는 라이브러리 코드를 작성하지 않는 한 여전히 반복하기 위해, 그 차이는 강조 할 가치가 없다고 생각합니다.
허용 된 답변은 예외적 인 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 반복을 위해 수작업으로 카운트 된 루프를 고려하십시오.