programing

.toArray(새로운 MyClass[0]) 또는 .toArray(새로운 MyClass[myList.size()])?

nicescript 2022. 8. 9. 22:15
반응형

.toArray(새로운 MyClass[0]) 또는 .toArray(새로운 MyClass[myList.size()])?

어레이 리스트가 있는 경우

ArrayList<MyClass> myList;

어레이에 문의하고 싶은데 퍼포먼스상의 이유로

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 Wise of the Ancients에서 유사한 결과, 완전한 분석 및 토론을 볼 수 있습니다.요약하면 JVM 및 JIT 컴파일러에는 적절한 크기의 새로운 어레이를 저렴한 비용으로 만들고 초기화할 수 있는 몇 가지 최적화가 포함되어 있습니다.또한 어레이를 직접 작성하는 경우에는 최적화를 사용할 수 없습니다.

Java 5 의 Array List 에서는, 어레이의 사이즈가 적절한(또는 큰) 경우, 어레이는 이미 가득 찬 상태가 됩니다.결과적으로

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

는 하나의 배열 개체를 생성하여 채우고 "arr"로 되돌립니다.반면에

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

는 2개의 어레이를 작성합니다.두 번째는 길이가 0인 MyClass 배열입니다.즉, 오브젝트에 대한 오브젝트가 생성되어 즉시 폐기됩니다.소스 코드가 시사하는 한 컴파일러/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개만 만들고 (암시적이지만 비용이 많이 드는) 캐스팅을 회피합니다.

JetBrains Intellij Idea 검사에서:

컬렉션을 배열로 변환하는 스타일에는 사전 크기 배열(c.toArray[c.size()] 등) 또는 빈 배열(c.toArray[0] 등)이 있습니다.

이전 Java 버전에서는 적절한 크기의 배열을 작성하기 위해 필요한 반사 호출이 매우 느렸기 때문에 사전 크기의 배열을 사용하는 것이 권장되었습니다.그러나 OpenJDK 6의 최신 업데이트 이후 이 콜이 내장되어 빈 어레이 버전의 퍼포먼스가 이전 크기 버전과 동일하고 경우에 따라서는 더 향상되었습니다.또, 사이즈가 미리 설정된 어레이를 건네주는 것은, 동시 또는 동기한 수집에서는 위험합니다.이는 사이즈가 있는 콜과 어레이 콜 사이에 데이터 레이스가 발생할 수 있기 때문에, 조작중에 수집이 동시에 축소되었을 경우 어레이의 마지막에 여분의 늘이 발생할 가능성이 있기 때문입니다.

이 검사에서는 빈 어레이(현대 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가 경고 플래그를 지정하고 수정을 제안합니다.단일 키 입력으로 코드를 두 번째 유형에서 첫 번째 유형으로 변환합니다.

올바른 크기의 어레이에서 'toArray'를 사용하면 먼저 제로 크기의 어레이를 작성한 후 올바른 크기의 어레이를 작성하기 때문에 성능이 향상됩니다.하지만 당신이 말한 것처럼 그 차이는 무시할 수 있을 것 같습니다.

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

따라서 질문에 대한 답변은 대부분 스타일의 문제이지만 일관성을 위해 (문서화되어 있든 없든) 모든 코딩 표준의 일부가 되어야 합니다.

integer의 샘플코드:

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

언급URL : https://stackoverflow.com/questions/174093/toarraynew-myclass0-or-toarraynew-myclassmylist-size

반응형