Java 소스에서 System.arraycopy가 기본 메서드라는 사실에 놀랐습니다.
물론 그 이유는 더 빠르기 때문입니다. 그러나 코드가 더 빠르게 사용할 수있는 기본 트릭은 무엇입니까?
원래 배열을 반복하고 각 포인터를 새 배열에 복사하지 않는 이유는 무엇입니까? 확실히 느리고 성가신 일이 아닙니다.
답변:
네이티브 코드에서는 n 개의 고유 한 복사 작업 과는 반대로 단일 memcpy
/ 로 수행 할 수 있습니다 . 성능의 차이는 상당합니다.memmove
arraycopy
사용하여 구현할 수 있습니다 . 다른 것들은 복사 된 각 요소에 대한 런타임 유형 검사를 필요로합니다. memcpy
memmove
Java로 작성할 수 없습니다. 네이티브 코드는 Object 배열과 기본 배열 간의 차이를 무시하거나 제거 할 수 있습니다. 자바는 최소한 효율적으로 할 수 없습니다.
그리고 중첩 배열에 필요한 의미 체계 때문에 단일으로 작성할 수 없습니다memcpy()
.
memmove
그럼. 이 질문의 맥락에서 큰 차이가 있다고 생각하지 않지만.
물론 구현에 따라 다릅니다.
HotSpot은이를 "내재"로 취급하고 호출 사이트에 코드를 삽입합니다. 그것은 느린 오래된 C 코드가 아니라 기계 코드입니다. 이것은 또한 메서드의 서명 문제가 크게 사라짐을 의미합니다.
단순 복사 루프는 명백한 최적화를 적용 할 수있을만큼 간단합니다. 예를 들어 루프 풀기. 정확히 발생하는 일은 다시 구현에 따라 다릅니다.
내 자신의 테스트에서 다중 차원 배열을 복사하는 System.arraycopy ()는 for 루프를 인터리빙하는 것보다 10-20 배 빠릅니다.
float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();
for (int i = 0; i < foo.length; i++)
{
for (int j = 0; j < foo[0].length; j++)
{
fooCpy[i][j] = foo[i][j];
}
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
for (int j = 0; j < foo[0].length; j++)
{
if (fooCpy[i][j] != foo[i][j])
{
System.err.println("ERROR at " + i + ", " + j);
}
}
}
이것은 다음을 인쇄합니다.
System.arraycopy() duration: 1 ms
loop duration: 16 ms
System.arraycopy
얕은 복사 ( 내부 s에 대한 참조 만 float[]
복사 됨)를 for
수행하는 반면 중첩 루프는 전체 복사 ( float
by float
)를 수행합니다 . 에 대한 변경 사항 fooCpy[i][j]
은 foo
using에 반영 System.arraycopy
되지만 중첩 for
루프는 사용하지 않습니다 .
몇 가지 이유가 있습니다.
JIT는 수동으로 작성된 C 코드만큼 효율적인 저수준 코드를 생성하지 않을 것입니다. 낮은 수준의 C를 사용하면 일반 JIT 컴파일러에서 수행하기 거의 불가능한 많은 최적화가 가능합니다.
수작업으로 작성된 C 구현의 몇 가지 트릭과 속도 비교를 보려면이 링크를 참조하십시오 (memcpy, 원칙은 동일 함).이 최적화 Memcpy를 확인 하면 속도가 향상됩니다.
C 버전은 배열 구성원의 유형과 크기에 거의 독립적입니다. 배열 내용을 원시 메모리 블록 (예 : 포인터)으로 가져올 수있는 방법이 없기 때문에 Java에서 동일한 작업을 수행 할 수 없습니다.