배열 복사를 위해 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.