배열 복사를 위해 for 루프보다 System.arraycopy (…)를 사용하는 것이 더 낫습니까?


90

두 개의 작은 배열을 모아 새로운 객체 배열을 만들고 싶습니다.

null 일 수 없지만 크기는 0 일 수 있습니다.

이 두 가지 방법 중 하나를 선택할 수 없습니다. 동등합니까 아니면 하나 더 효율적입니까 (예 : system.arraycopy () 전체 청크 복사)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

또는

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

유일한 차이점은 코드의 모양입니까?

편집 : 연결된 질문에 감사하지만 해결되지 않은 토론이있는 것 같습니다.

it is not for native typesbyte [], Object [], char [] 이면 정말 더 빠릅니까? 다른 모든 경우에는 유형 검사가 실행됩니다. 이것은 내 경우이므로 동등합니다.

또 다른 연결된 질문에서 그들은 the size matters a lot크기가> 24 인 경우 system.arraycopy ()가 이기고 10보다 작은 경우 수동 for 루프가 더 좋다고 말합니다 .

이제 정말 혼란 스러워요.


16
arraycopy()가장 확실하게 더 빠른 네이티브 호출입니다.
— Sotirios Delimanolis 2013 년

4
두 가지 다른 구현을 벤치마킹 해 보셨습니까?
— Alex


15
가장 읽기 쉽고 향후 유지 관리하기 쉬운 것을 선택해야합니다. 이것이 병목 현상의 원인이라고 판단한 경우에만 접근 방식을 변경해야합니다.
— arshajii

1
바퀴를 재발 명하지 마십시오!
— camickr 2014 년

답변:


87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

나는 JUnit 테스트 정말 벤치마킹을위한 최상의하지 알고 있지만
testHardCopyBytes 완료에 0.157s했다
및
testArrayCopyBytes 완료에 0.086s했다.

가상 머신에 의존한다고 생각하지만 단일 배열 요소를 복사하는 대신 메모리 블록을 복사하는 것처럼 보입니다. 이것은 절대적으로 성능을 향상시킬 것입니다.

편집 :
System.arraycopy의 성능이 온통있는 것 같습니다. 바이트 대신 문자열이 사용되고 배열이 작 으면 (크기 10) 다음과 같은 결과가 나타납니다.

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

배열의 크기가 0x1000000 일 때의 모습은 다음과 같습니다. System.arraycopy가 더 큰 배열에서 확실히 승리하는 것처럼 보입니다.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

얼마나 특이한가!

참고 문헌이 다르게 복사된다는 점을 지적 해 주신 Daren에게 감사드립니다. 이것은 이것을 훨씬 더 흥미로운 문제로 만들었습니다!


2
당신의 노력에 감사하지만, 당신은 겉보기에 중요한 요점을 놓쳤습니다 : 비 네이티브 유형 (배열에 참조가 포함되도록 임의의 클래스를 생성) 및 크기 ... 작은 배열 크기 인 것 같고 수동 for-loop가 더 빠릅니다. 이 문제를 해결 하시겠습니까?
— Daren

2
오 와우, 맞아요! 흥미 롭군요. 바이트 대신 해당 배열에 문자열을 배치하면 큰 차이가 있습니다. <<< testHardCopyStrs : 0.161s >>> <<< testArrayCopyStrs : 0.170s >>>
— Trent Small

그러면 어떤 결과를 얻고 있습니까? 또한 흥미로운 것은 배열 크기 = 10 ... 감사합니다! (여기에 IDE가 있었으면 좋겠습니다. 컴파일러없이 코딩하고 있습니다).
— Daren

이제 System.nanoTime () 호출에이를 래핑하고 size = 10을 설정하여 각각 몇 나노초가 걸리는지 확인했습니다. 작은 원시 배열처럼 보이며 루프가 더 좋습니다. 참조의 경우 arrayCopy가 더 좋습니다. : <<< testHardCopyBytes : 4491 ns >>> <<< testHardCopyStrs : 56778 ns >>> <<< testArrayCopyBytes : 10265 ns >>> <<< testArrayCopyStrs : 4490 ns >>>
— Trent 작은

매우 흥미로운 결과! 정말 고맙습니다! 이 내용을 포함하도록 귀하의 답변을 편집 해 주시면 기꺼이 수락하여 모든 사람이 가장 먼저 볼 수 있도록하겠습니다. 이미 투표를 받았습니다. :)
— Daren

36

Arrays.copyOf(T[], int)읽기 쉽습니다. 내부적으로는 System.arraycopy()네이티브 호출을 사용합니다.

더 빨리 얻을 수 없습니다!


꽤 많은 것에 의존 할 수있는 것 같지만, 내가 몰랐고 실제로 읽기 쉬운 기능을 지적 해 주셔서 감사합니다. :)
— Daren

예! @Svetoslav Tsolov가 말한 것처럼 꽤 많은 것에 달려 있습니다. 난 그냥 Arrays.copyOf 지적하고 싶었
— 필립 샌더

2
copyOf항상을 바꿀 수는 없지만 arraycopy이 사용 사례에 적합합니다.
— Blaisorblade 2014

1
NB 성능을보고 있다면 메모리 할당이 필요하므로 System.arraycopy ()만큼 빠르지 않습니다. 이것이 루프에 있다면 반복 할당은 가비지 콜렉션으로 이어질 것이며 이는 엄청난 성능 저하가 될 것입니다.
— Will Calderwood

1
@PhilippSander 내가 어리석지 않았는지 확인하기 위해 GC를 거의 실행하지 않는 게임 루프의 1MB 배열 사본에 코드를 추가했습니다. Array.copyOf ()를 사용하면 DVM이 초당 5 번 GC를 호출했고 게임이 매우 느려졌습니다. 메모리 할당이 일어나고 있다고 말하는 것이 안전하다고 생각합니다.
— Will Calderwood 2015 년

17

가상 머신에 따라 다르지만 System.arraycopy는 기본 성능에 가장 근접한 성능을 제공해야합니다.

저는 2 년 동안 임베디드 시스템 (성능이 가장 중요한 곳)을위한 자바 개발자로 일했으며 System.arraycopy를 사용할 수있는 모든 곳에서 주로 사용했거나 기존 코드에서 사용하는 것을 보았습니다. 성능이 문제가 될 때 항상 루프보다 선호됩니다. 성능이 큰 문제가 아니라면 루프를 사용합니다. 훨씬 읽기 쉽습니다.


배열 크기 및 유형 (기본 vs 상속)과 같은 것들이 성능에 영향을 미치는 것 같습니다.
— Daren

2
예, 그것은 그 자체로 '기본 성능'이 아니기 때문에 제가 할 수있는 곳에서 '대부분'사용한다고 말한 이유입니다 (대부분 루프 복사보다 우세합니다). 그 이유는 기본 유형의 작은 배열 일 때 '호출 비용'이 성능 향상보다 큽니다. JNI를 사용하면 동일한 이유로 성능이 저하 될 수 있습니다. 네이티브 코드 자체는 빠르지 만 자바 프로세스에서 호출하는 것은 그다지 많지 않습니다.

사소한 수정, Arrays.copy는 JNI가 아니라 내재적입니다. 내장 함수는 JNI보다 훨씬 빠릅니다. JIT 컴파일러가이를 내장 함수로 변환하는 방법과시기는 사용되는 JVM / 컴파일러에 따라 다릅니다.
— Nitsan Wakart 2013

1
Arrays.copy존재하지 않는 Arrays.copyOf라이브러리 기능입니다.
— Blaisorblade 2014

14

추측과 구식 정보에 의존하는 대신 다음을 사용하여 몇 가지 벤치 마크를 실행했습니다. . 실제로 Caliper에는 CopyArrayBenchmark이 질문을 정확하게 측정하는를 포함하여 몇 가지 예제가 있습니다 ! 당신이해야 할 일은 달리기

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

내 결과는 2010 년 중반 MacBook Pro (Intel Arrandale i7, 8GiB RAM이있는 macOS 10.11.6)에서 실행되는 Oracle의 Java HotSpot (TM) 64 비트 서버 VM, 1.8.0_31-b13을 기반으로합니다. 원시 타이밍 데이터를 게시하는 것이 유용하다고 생각하지 않습니다. 오히려 지원 시각화를 통해 결론을 요약하겠습니다.

요약해서 말하자면:

  • for각 요소를 새로 인스턴스화 된 배열로 복사 하는 수동 루프를 작성하는 것은 짧은 배열이든 긴 배열이든간에 결코 유리하지 않습니다.
  • Arrays.copyOf(array, array.length)그리고 array.clone()둘 다 지속적으로 빠릅니다. 이 두 기술은 성능면에서 거의 동일합니다. 당신이 선택하는 것은 취향의 문제입니다.
  • System.arraycopy(src, 0, dest, 0, src.length)는 및 만큼 빠르지 만 일관성이 없습니다. (50000 s 의 경우를 참조하십시오 .) 그것과 호출의 장황함 때문에 어떤 요소가 어디에서 복사되는지에 대한 미세 제어가 필요한 경우 권장 합니다.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

타이밍 플롯은 다음과 같습니다.

길이 5 배열 복사 타이밍 길이가 500 인 배열을 복사하기위한 타이밍 길이가 50000 인 배열 복사 타이밍


3
int 복사에 대해 이상한 점이 있습니까? 대규모의 int에서 arraycopy가 느릴 것이라는 것이 이상하게 보입니다.
— whaleberg

6

같은 네이티브 메서드를 실행 Arrays.copyOf(T[], int)하면 약간의 오버 헤드가 있지만 JNI를 사용하여 실행하는 것만 큼 빠르지 않다는 의미는 아닙니다.

가장 쉬운 방법은 벤치 마크를 작성하고 테스트하는 것입니다.

Arrays.copyOf(T[], int)정상적인 for루프 보다 빠른지 확인할 수 있습니다 .

여기 에서 벤치 마크 코드 :-

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()JNI (Java Native Interface)를 사용하여 배열 (또는 그 일부)을 복사하므로 여기에서 확인할 수 있듯이 매우 빠릅니다.


이 코드는 int []를 사용합니다. String [] (다른 값으로 초기화 됨 : "1", "2"등 불변이기 때문에 초기화 됨
— Daren

1
JNI는 매우 느립니다 . System.arraycopy그것을 사용하지 않습니다.
— Chai T. Rex

아니요, System.arraycopy타사 라이브러리 호출 전용 인 JNI를 사용하지 않습니다. 대신 기본 호출이므로 VM에 기본 구현이 있음을 의미합니다.
— spheenik

6

Arrays.copyOf더 빠른 것은 불가능합니다.System.arraycopy 의 구현이기 때문에copyOf .

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4

System.arraycopy()메모리에서 직접 복사 작업을 수행하는 기본 호출입니다. 단일 메모리 복사본은 항상 for 루프보다 빠릅니다.


3
비 네이티브 유형 (나와 같은 생성 된 클래스)의 경우 그렇게 효율적이지 않을 수 있으며 작은 크기의 경우 (내 경우) 수동 for 루프가 더 좋을 수 있습니다 ... 주석에 관심이 있습니까?
— Daren

실제로 System.arraycopy ()에는 약간의 오버 헤드가 있으므로 작은 배열 (n = ~ 10)의 경우 루프가 실제로 더 빠릅니다.
— RecursiveExceptionException
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.