ArrayList가 있다고 가정합니다.
ArrayList<MyClass> myList;
toArray를 호출하고 싶습니다. 성능상의 이유가 있습니까?
MyClass[] arr = myList.toArray(new MyClass[myList.size()]);
위에
MyClass[] arr = myList.toArray(new MyClass[0]);
?
나는 덜 장황하기 때문에 두 번째 스타일을 선호하며 컴파일러가 빈 배열이 실제로 생성되지 않도록 할 것이라고 가정했지만 그것이 사실인지 궁금합니다.
물론 99 %의 경우에 차이가 없지만 정상적인 코드와 최적화 된 내부 루프 사이에 일관된 스타일을 유지하고 싶습니다 …
답변
직관적으로 Hotspot 8에서 가장 빠른 버전은 다음과 같습니다.
MyClass[] arr = myList.toArray(new MyClass[0]);
jmh를 사용하여 마이크로 벤치 마크를 실행했으며 결과와 코드가 아래에 나와 있습니다. 빈 배열이있는 버전이 미리 구성된 배열의 버전보다 일관되게 성능이 우수하다는 것을 보여줍니다. 올바른 크기의 기존 배열을 재사용 할 수 있으면 결과가 다를 수 있습니다.
벤치 마크 결과 (마이크로 초 단위, 작을수록 좋음) :
Benchmark (n) Mode Samples Score Error Units
c.a.p.SO29378922.preSize 1 avgt 30 0.025 ▒ 0.001 us/op
c.a.p.SO29378922.preSize 100 avgt 30 0.155 ▒ 0.004 us/op
c.a.p.SO29378922.preSize 1000 avgt 30 1.512 ▒ 0.031 us/op
c.a.p.SO29378922.preSize 5000 avgt 30 6.884 ▒ 0.130 us/op
c.a.p.SO29378922.preSize 10000 avgt 30 13.147 ▒ 0.199 us/op
c.a.p.SO29378922.preSize 100000 avgt 30 159.977 ▒ 5.292 us/op
c.a.p.SO29378922.resize 1 avgt 30 0.019 ▒ 0.000 us/op
c.a.p.SO29378922.resize 100 avgt 30 0.133 ▒ 0.003 us/op
c.a.p.SO29378922.resize 1000 avgt 30 1.075 ▒ 0.022 us/op
c.a.p.SO29378922.resize 5000 avgt 30 5.318 ▒ 0.121 us/op
c.a.p.SO29378922.resize 10000 avgt 30 10.652 ▒ 0.227 us/op
c.a.p.SO29378922.resize 100000 avgt 30 139.692 ▒ 8.957 us/op
참고로 코드는 다음과 같습니다.
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
public class SO29378922 {
@Param({"1", "100", "1000", "5000", "10000", "100000"}) int n;
private final List<Integer> list = new ArrayList<>();
@Setup public void populateList() {
for (int i = 0; i < n; i++) list.add(0);
}
@Benchmark public Integer[] preSize() {
return list.toArray(new Integer[n]);
}
@Benchmark public Integer[] resize() {
return list.toArray(new Integer[0]);
}
}
블로그 게시물 Arrays of the Wisdom of the Ancients 에서 유사한 결과, 전체 분석 및 토론을 찾을 수 있습니다 . 요약하자면, JVM 및 JIT 컴파일러에는 올바른 크기의 새 배열을 저렴하게 작성하고 초기화 할 수있는 몇 가지 최적화가 포함되어 있으며, 배열을 직접 작성하는 경우 이러한 최적화를 사용할 수 없습니다.
답변
Java 5 의 ArrayList에서 , 배열의 크기가 올바른 경우 이미 채워져 있습니다. 따라서
MyClass[] arr = myList.toArray(new MyClass[myList.size()]);
하나의 배열 객체를 생성하고 채우고 “arr”로 반환합니다. 반면에
MyClass[] arr = myList.toArray(new MyClass[0]);
두 개의 배열을 만듭니다. 두 번째는 길이가 0 인 MyClass의 배열입니다. 따라서 즉시 버려 질 오브젝트에 대한 오브젝트 작성이 있습니다. 소스 코드가 컴파일러 / JIT가 제안하지 않는 한 컴파일러 / JIT가 컴파일러를 최적화 할 수 없다고 제안하는 한. 또한 길이가 0 인 객체를 사용하면 toArray ()-메서드 내에서 캐스팅이 발생합니다.
ArrayList.toArray ()의 소스를 참조하십시오.
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
첫 번째 방법을 사용하여 하나의 오브젝트 만 작성하고 내재적이지만 비용이 많이 드는 캐스팅을 피하십시오.
답변
JetBrains Intellij Idea 검사에서 :
컬렉션을 배열로 변환하는 두 가지 스타일이 있습니다 : 미리 크기가 지정된 배열 ( c.toArray (new String [c.size ()]) )을 사용하거나 빈 배열 ( c.toArray (new String [ 0]) .
이전 크기의 Java 버전에서는 적절한 크기의 배열을 만드는 데 필요한 리플렉션 호출이 상당히 느리기 때문에 사전 크기 배열을 사용하는 것이 좋습니다. 그러나 OpenJDK 6의 최신 업데이트 이후로이 호출은 내재화되어 비어있는 어레이 버전의 성능을 사전 크기 버전과 동일하고 때로는 더 좋게 만들었습니다. 또한 크기 와 toArray 호출 간에 데이터 경쟁이 가능 하므로 작업 중 수집이 동시에 축소 된 경우 배열 끝에서 추가 null이 발생할 수 있으므로 사전 크기 조정 된 배열을 전달하는 것은 동시 또는 동기화 된 콜렉션에 위험
합니다.이 검사를 통해 빈 어레이 (현대 Java에서 권장 됨)를 사용하거나 미리 크기가 지정된 어레이 (이전 Java 버전 또는 HotSpot 기반이 아닌 JVM에서는 더 빠름)를 사용하는 균일 한 스타일을 따를 수 있습니다.
답변
최신 JVM은이 경우 반사 형 어레이 구성을 최적화하므로 성능 차이는 미미합니다. 그러한 상용구 코드에서 콜렉션의 이름을 두 번 명명하는 것은 좋은 생각이 아니므로 첫 번째 방법을 피할 것입니다. 두 번째의 또 다른 장점은 동기화 및 동시 수집과 함께 작동한다는 것입니다. 최적화하려면 빈 배열을 재사용하거나 (빈 배열은 변경할 수 없으며 공유 가능) 프로파일 러 (!)를 사용하십시오.
답변
toArray는 전달 된 배열의 크기가 올바른지 (즉, 목록의 요소에 맞을만큼 충분히 큰지) 확인한 경우 해당 배열을 사용합니다. 결과적으로 어레이의 크기가 필요한 것보다 작 으면 새 어레이가 재귀 적으로 생성됩니다.
귀하의 경우 크기가 0 인 배열은 변경할 수 없으므로 정적 최종 변수로 안전하게 상승하여 코드를 조금 더 깨끗하게 만들 수 있으므로 각 호출에서 배열을 생성하지 않아도됩니다. 어쨌든 메소드 내부에 새로운 배열이 생성되므로 가독성 최적화입니다.
아마도 더 빠른 버전은 올바른 크기의 배열을 전달하는 것이지만 이 코드가 성능 병목 임을 증명할 수 없으면 달리 입증 될 때까지 런타임 성능에 대한 가독성을 선호하십시오.
답변
첫 번째 경우가 더 효율적입니다.
두 번째 경우에 있기 때문입니다.
MyClass[] arr = myList.toArray(new MyClass[0]);
런타임은 실제로 빈 배열 (크기가 0 인)을 만든 다음 toArray 메서드 안에 실제 데이터에 맞는 다른 배열을 만듭니다. 이 코드는 jdk1.5.0_10에서 가져온 다음 코드를 사용하여 리플렉션을 사용하여 수행됩니다.
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
첫 번째 형식을 사용하면 두 번째 배열을 만들지 않고 반사 코드도 피할 수 있습니다.
답변
두 번째는 거의 읽을 수 없지만 개선이 거의 없으므로 그만한 가치가 없습니다. 첫 번째 방법은 더 빠르며 런타임에 단점이 없으므로 사용하는 것입니다. 그러나 입력하는 것이 더 빠르기 때문에 두 번째 방법으로 작성합니다. 그런 다음 IDE가 경고로 표시하고 수정을 제안합니다. 한 번의 키 입력으로 코드를 두 번째 유형에서 첫 번째 유형으로 변환합니다.