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

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

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


답변

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가 경고로 표시하고 수정을 제안합니다. 한 번의 키 입력으로 코드를 두 번째 유형에서 첫 번째 유형으로 변환합니다.