.toArray (new MyClass [0]) 또는 .toArray (new MyClass [myList.size ()])?


176

ArrayList가 있다고 가정합니다.

ArrayList<MyClass> myList;

toArray를 호출하고 싶습니다. 성능상의 이유가 있습니까?

MyClass[] arr = myList.toArray(new MyClass[myList.size()]);

위에

MyClass[] arr = myList.toArray(new MyClass[0]);

?

나는 덜 장황하기 때문에 두 번째 스타일을 선호하며 컴파일러가 빈 배열이 실제로 생성되지 않도록 할 것이라고 가정했지만 그것이 사실인지 궁금합니다.

물론 99 %의 경우에 차이가 없지만 정상적인 코드와 최적화 된 내부 루프 사이에 일관된 스타일을 유지하고 싶습니다 ...


6
이 질문은 Aleksey Shipilёv ( 고대의 지혜의 배열)에 의해 새 블로그 게시물에서 해결 된 것 같습니다 !
glts

6
블로그 게시물에서 : "맨 아래 줄 : toArray (new T [0])이 더 빠르고 안전하며 계약 상 깨끗해 보이므로 이제 기본 선택이되어야합니다."
DavidS

답변:


109

직관적으로 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 컴파일러에는 올바른 크기의 새 배열을 저렴하게 작성하고 초기화 할 수있는 몇 가지 최적화가 포함되어 있으며, 배열을 직접 작성하는 경우 이러한 최적화를 사용할 수 없습니다.


2
매우 흥미로운 의견. 아무도 이것에 대해 언급하지 않은 것에 놀랐습니다. 속도에 관한 한 다른 답변과 모순되기 때문입니다. 또한 흥미롭게도,이 사람들의 명성은 다른 모든 답변 (ers)을 합한 것보다 거의 높습니다.
Pimp Trizkit

나는 산다. 또한에 대한 벤치 마크를보고 싶습니다 MyClass[] arr = myList.stream().toArray(MyClass[]::new);. 또한 배열 선언과의 차이점에 대한 벤치 마크를보고 싶습니다. MyClass[] arr = new MyClass[myList.size()]; arr = myList.toArray(arr);MyClass[] arr = myList.toArray(new MyClass[myList.size()]);... 의 차이점에서와 같이 또는 차이가 없어야합니까? 이 두 가지 toArray기능이 발생 하지 않는 문제라고 생각합니다 . 그러나 이봐! 다른 복잡한 차이점에 대해서는 배우지 않을 것이라고 생각했습니다.
Pimp Trizkit

1
@PimpTrizkit 단지 체크 예상대로 차이 추가 변수하게 사용하지 않고, 전화 등의 스트림은 60 %와 100 %의 시간 사이에 걸리는하여 toArray직접 (작은 크기, 큰 상대 헤드)
assylias을

와우, 그것은 빠른 응답이었다! 감사! 예, 나는 그것을 의심했다. 스트림으로 변환하는 것이 효율적이지 않았습니다. 그러나 당신은 모른다!
Pimp Trizkit

2
이와 동일한 결론이 여기에서 발견되었습니다 : shipilev.net/blog/2016/arrays-wisdom-ancients
user167019

122

Java 5ArrayList에서 , 배열의 크기가 올바른 경우 이미 채워져 있습니다. 따라서

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

첫 번째 방법을 사용하여 하나의 오브젝트 만 작성하고 내재적이지만 비용이 많이 드는 캐스팅을 피하십시오.


1
누군가에게 관심을 가질만한 두 가지 의견 : 1) LinkedList.toArray (T [] a)는 더 느리고 (반사 사용 : Array.newInstance) 더 복잡합니다. 2) 반면, JDK7 릴리스에서는 일반적으로 고통스럽게 느린 Array.newInstance 가 일반적인 배열 생성 속도 와 거의 비슷하다는 것을 알게되어 매우 놀랐습니다 !
java.is.for.desktop

1
@ktaria size는 ArrayList의 비공개 멤버로, **** suprise **** 크기를 지정합니다.
MyPasswordIsLasercats

3
벤치 마크없이 성능을 추측하는 것은 사소한 경우에만 작동합니다. 실제로, new Myclass[0]더 빠릅니다 : shipilev.net/blog/2016/arrays-wisdom-ancients
Karol S

더 이상 JDK6 + 기준으로 유효하지 않습니다.
Антон Антонов

28

JetBrains Intellij Idea 검사에서 :

컬렉션을 배열로 변환하는 두 가지 스타일이 있습니다 : 미리 크기가 지정된 배열 ( c.toArray (new String [c.size ()]) )을 사용하거나 빈 배열 ( c.toArray (new String [ 0]) .

이전 크기의 Java 버전에서는 적절한 크기의 배열을 만드는 데 필요한 리플렉션 호출이 상당히 느리기 때문에 사전 크기 배열을 사용하는 것이 좋습니다. 그러나 OpenJDK 6의 최신 업데이트 이후로이 호출은 내재화되어 비어있는 어레이 버전의 성능을 사전 크기 버전과 동일하고 때로는 더 좋게 만들었습니다. 또한 크기toArray 호출 간에 데이터 경쟁이 가능 하므로 작업 중 수집이 동시에 축소 된 경우 배열 끝에서 추가 null이 발생할 수 있으므로 사전 크기 조정 된 배열을 전달하는 것은 동시 또는 동기화 된 콜렉션에 위험 합니다.

이 검사를 통해 빈 어레이 (현대 Java에서 권장 됨)를 사용하거나 미리 크기가 지정된 어레이 (이전 Java 버전 또는 HotSpot 기반이 아닌 JVM에서는 더 빠름)를 사용하는 균일 한 스타일을 따를 수 있습니다.


이 모든 것이 복사 / 인용 된 텍스트라면, 그에 따라 형식을 지정하고 소스에 대한 링크를 제공 할 수 있습니까? 나는 실제로 IntelliJ 검사 때문에 여기에 왔으며 모든 검사와 그 이유를 찾는 링크에 매우 관심이 있습니다.
Tim Büthe

3
여기 당신은 검사 텍스트를 확인할 수 있습니다 : github.com/JetBrains/intellij-community/tree/master/plugins/…
Антон Антонов


17

최신 JVM은이 경우 반사 형 어레이 구성을 최적화하므로 성능 차이는 미미합니다. 그러한 상용구 코드에서 콜렉션의 이름을 두 번 명명하는 것은 좋은 생각이 아니므로 첫 번째 방법을 피할 것입니다. 두 번째의 또 다른 장점은 동기화 및 동시 수집과 함께 작동한다는 것입니다. 최적화하려면 빈 배열을 재사용하거나 (빈 배열은 변경할 수 없으며 공유 가능) 프로파일 러 (!)를 사용하십시오.


2
'빈 배열 재사용'을 피하는 것은 가독성과 잠재적 성능 사이의 타협이기 때문에 고려해야합니다. 인수가 선언 전달 private static final MyClass[] EMPTY_MY_CLASS_ARRAY = new MyClass[0]반사에 의해 구성되는 반환 된 배열을 방지하지 않지만 않는 추가 배열이 각 때마다 구축되는 것을 방지 할 수 있습니다.
Michael Scheper 1

길이0 인 배열 을 사용하는 경우 Machael이 맞습니다 . (T []) java.lang.reflect.Array.newInstance (a.getClass (). getComponentType (), size); 크기가> = actualSize (JDK7) 인 경우 불필요합니다
Alex

"현대 JVM이이 경우 반사 어레이 구성을 최적화합니다"에 대한 인용을 줄 수 있다면이 답변을 기꺼이지지하겠습니다.
Tom Panning

나는 여기서 배우고있다. 대신에 내가 사용하는 경우 : MyClass[] arr = myList.stream().toArray(MyClass[]::new);동기화 된 동시 수집에 도움이됩니까? 그리고 왜? 부디.
Pimp Trizkit

3

toArray는 전달 된 배열의 크기가 올바른지 (즉, 목록의 요소에 맞을만큼 충분히 큰지) 확인한 경우 해당 배열을 사용합니다. 결과적으로 어레이의 크기가 필요한 것보다 작 으면 새 어레이가 재귀 적으로 생성됩니다.

귀하의 경우 크기가 0 인 배열은 변경할 수 없으므로 정적 최종 변수로 안전하게 상승하여 코드를 조금 더 깨끗하게 만들 수 있으므로 각 호출에서 배열을 생성하지 않아도됩니다. 어쨌든 메소드 내부에 새로운 배열이 생성되므로 가독성 최적화입니다.

아마도 더 빠른 버전은 올바른 크기의 배열을 전달하는 것이지만 이 코드가 성능 병목 임을 증명할 수 없으면 달리 입증 될 때까지 런타임 성능에 대한 가독성을 선호하십시오.


2

첫 번째 경우가 더 효율적입니다.

두 번째 경우에 있기 때문입니다.

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

첫 번째 형식을 사용하면 두 번째 배열을 만들지 않고 반사 코드도 피할 수 있습니다.


toArray ()는 리플렉션을 사용하지 않습니다. 어쨌든 "-casting"을 반영하지 않는 한 ;-).
Georgi

toArray (T []) 않습니다. 적절한 유형의 배열을 작성해야합니다. 최신 JVM은 이러한 종류의 반사를 비 반사 버전과 거의 같은 속도로 최적화합니다.
Tom Hawtin-tackline

나는 그것이 반사를 사용한다고 생각합니다. JDK 1.5.0_10은 확실하게 반영하며 리플렉션은 컴파일 타임에 알지 못하는 유형의 배열을 만드는 유일한 방법입니다.
Panagiotis Korros

그런 다음 소스 코드 예제 중 하나 (위의 것 또는 내 것)가 오래되었습니다. 슬프게도, 나는 내 올바른 하위 버전 번호를 찾지 못했습니다.
Georgi

1
Georgi, 코드는 JDK 1.6에서 왔으며 Arrays.copyTo 메소드의 구현을 보면 구현에서 리플렉션을 사용한다는 것을 알 수 있습니다.
Panagiotis Korros

-1

두 번째는 거의 읽을 수 없지만 개선이 거의 없으므로 그만한 가치가 없습니다. 첫 번째 방법은 더 빠르며 런타임에 단점이 없으므로 사용하는 것입니다. 그러나 입력하는 것이 더 빠르기 때문에 두 번째 방법으로 작성합니다. 그런 다음 IDE가 경고로 표시하고 수정을 제안합니다. 한 번의 키 입력으로 코드를 두 번째 유형에서 첫 번째 유형으로 변환합니다.


-2

올바른 크기의 배열에 'toArray'를 사용하면 대안이 먼저 크기가 0 인 배열을 생성 한 다음 올바른 크기의 배열을 생성하므로 성능이 향상됩니다. 그러나 당신이 말했듯이 그 차이는 무시할 수 있습니다.

또한 javac 컴파일러는 최적화를 수행하지 않습니다. 요즘 모든 최적화는 런타임에 JIT / HotSpot 컴파일러에 의해 수행됩니다. JVM에서 'toArray'에 대한 최적화를 알지 못합니다.

따라서 귀하의 질문에 대한 답변은 스타일의 문제이지만 일관성을 유지하기 위해 준수하는 코딩 표준의 일부 (문서화 또는 기타)를 형성해야합니다.


표준이 길이가 0 인 배열을 사용하는 경우 OTOH는 이탈하는 경우 성능이 문제가된다는 것을 암시합니다.
Michael Scheper

-5

정수 샘플 코드 :

Integer[] arr = myList.toArray(new integer[0]);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.