memmove가 memcpy보다 빠른 이유는 무엇입니까?

memmove (3)에서 시간의 50 %를 소비하는 응용 프로그램의 성능 핫스팟을 조사하고 있습니다. 응용 프로그램은 수백만 개의 4 바이트 정수를 정렬 된 배열에 삽입하고 삽입 된 값을위한 공간을 만들기 위해 memmove를 사용하여 데이터를 “오른쪽으로”이동합니다.

나의 기대는 메모리 복사가 매우 빠르다는 것이었고, memmove에 너무 많은 시간을 소비하는 것에 놀랐습니다. 그러나 memmove가 겹치는 영역을 이동하기 때문에 느리다는 생각이 들었습니다. 이는 큰 메모리 페이지를 복사하는 대신 긴밀한 루프로 구현해야합니다. memcpy와 memmove 사이에 성능 차이가 있는지 알아보기 위해 작은 마이크로 벤치 마크를 작성했습니다. memcpy가 이길 것으로 예상했습니다.

두 대의 컴퓨터 (코어 i5, 코어 i7)에서 벤치 마크를 실행 한 결과 memmove가 실제로 memcpy보다 빠르다는 것을 알았습니다. 구형 코어 i7에서는 거의 두 배나 빠릅니다! 이제 설명을 찾고 있습니다.

여기 내 벤치 마크가 있습니다. memcpy로 100mb를 복사 한 다음 memmove로 약 100mb를 이동합니다. 소스와 대상이 겹칩니다. 소스 및 대상에 대한 다양한 “거리”가 시도됩니다. 각 테스트는 10 회 실행되며 평균 시간이 인쇄됩니다.

https://gist.github.com/cruppstahl/78a57cdf937bca3d062c

다음은 Core i5의 결과입니다 (Linux 3.5.0-54-generic # 81 ~ precise1-Ubuntu SMP x86_64 GNU / Linux, gcc는 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)). 괄호 안의 숫자는 다음과 같습니다. 소스와 목적지 사이의 거리 (간격 크기) :

memcpy        0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633

Memmove는 SSE 최적화 된 어셈블러 코드로 구현되어 뒤에서 앞으로 복사됩니다. 하드웨어 프리 페치를 사용하여 데이터를 캐시에로드하고 128 바이트를 XMM 레지스터에 복사 한 다음 대상에 저장합니다.

( memcpy-ssse3-back.S , 라인 1650 ff)

L(gobble_ll_loop):
    prefetchnta -0x1c0(%rsi)
    prefetchnta -0x280(%rsi)
    prefetchnta -0x1c0(%rdi)
    prefetchnta -0x280(%rdi)
    sub $0x80, %rdx
    movdqu  -0x10(%rsi), %xmm1
    movdqu  -0x20(%rsi), %xmm2
    movdqu  -0x30(%rsi), %xmm3
    movdqu  -0x40(%rsi), %xmm4
    movdqu  -0x50(%rsi), %xmm5
    movdqu  -0x60(%rsi), %xmm6
    movdqu  -0x70(%rsi), %xmm7
    movdqu  -0x80(%rsi), %xmm8
    movdqa  %xmm1, -0x10(%rdi)
    movdqa  %xmm2, -0x20(%rdi)
    movdqa  %xmm3, -0x30(%rdi)
    movdqa  %xmm4, -0x40(%rdi)
    movdqa  %xmm5, -0x50(%rdi)
    movdqa  %xmm6, -0x60(%rdi)
    movdqa  %xmm7, -0x70(%rdi)
    movdqa  %xmm8, -0x80(%rdi)
    lea -0x80(%rsi), %rsi
    lea -0x80(%rdi), %rdi
    jae L(gobble_ll_loop)

memmove가 memcpy보다 빠른 이유는 무엇입니까? memcpy가 메모리 페이지를 복사 할 것으로 예상하는데, 이는 루핑보다 훨씬 빠릅니다. 최악의 경우 memcpy가 memmove만큼 빠르기를 기대합니다.

추신 : 내 코드에서 memmove를 memcpy로 바꿀 수 없다는 것을 알고 있습니다. 코드 샘플이 C와 C ++를 혼합한다는 것을 알고 있습니다. 이 질문은 정말 학문적 목적을위한 것입니다.

업데이트 1

다양한 답변을 바탕으로 다양한 테스트를 실행했습니다.

  1. memcpy를 두 번 실행하면 두 번째 실행이 첫 번째 실행보다 빠릅니다.
  2. memcpy ( memset(b2, 0, BUFFERSIZE...)) 의 대상 버퍼를 “접촉”하면 memcpy 의 첫 번째 실행도 더 빠릅니다.
  3. memcpy는 여전히 memmove보다 약간 느립니다.

결과는 다음과 같습니다.

memcpy        0.0118526
memcpy        0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648

내 결론 : @Oliver Charlesworth의 의견에 따르면 운영 체제는 memcpy 대상 버퍼에 처음으로 액세스하자마자 물리적 메모리를 커밋해야합니다 (누군가가 이것을 “증명”하는 방법을 알고 있다면 답을 추가하십시오! ). 또한 @Mats Petersson이 말했듯이 memmove는 memcpy보다 캐시 친화적입니다.

모든 훌륭한 답변과 의견에 감사드립니다!



답변

당신의 memmove당신의 동안 전화 2 128 바이트로를 따라 메모리를 걸어 갔다하는 memcpy소스와 대상이 완전히 다르다. 어떻게 든 이것이 성능 차이를 설명합니다. 같은 장소에 복사하면 ideone.com과 같이 memcpy더 빨리 끝날 수 있습니다 .

memmove (002) 0.0610362
memmove (004) 0.0554264
memmove (008) 0.0575859
memmove (016) 0.057326
memmove (032) 0.0583542
memmove (064) 0.0561934
memmove (128) 0.0549391
memcpy 0.0537919

거기에 거의 아무것도하지만 – 이미 메모리 페이지에 오류가 발생한 다시를 작성하는 것은이 있다는 증거 많은 영향을, 우리는 확실히 시간의 반감을 표시되지 않는 …하지만 그것을 만드는 아무것도 잘못이 있다고 보여 않는 memcpy사과를 비교했을 때 불필요하게 느리게 -사과.


답변

를 사용 memcpy하는 경우 쓰기가 캐시로 이동해야합니다. memmove작은 단계 앞으로 복사 할 때 사용 하는 경우 복사중인 메모리는 이미 캐시에 있습니다 (2, 4, 16 또는 128 바이트 “뒤”로 읽었 기 때문). memmove대상이 몇 메가 바이트 (> 4 * 캐시 크기) 인 곳에서 시도해보십시오 . 유사한 결과를 얻을 수있을 것이라고 생각합니다 (테스트 할 수는 없습니다).

대용량 메모리 작업을 할 때 ALL은 캐시 유지 관리에 관한 것임을 보증합니다.


답변

역사적으로 memmove와 memcopy는 동일한 기능입니다. 그들은 동일한 방식으로 작동하고 동일한 구현을 가졌습니다. 그런 다음 memcopy가 특정 방식으로 겹치는 영역을 처리하기 위해 정의 할 필요가없고 정의 할 필요가 없다는 것을 깨달았습니다.

최종 결과는 memmove가 성능에 영향을 미치는 경우에도 특정 방식으로 겹치는 영역을 처리하도록 정의 된 것입니다. Memcopy는 겹치지 않는 영역에 사용할 수있는 최상의 알고리즘을 사용합니다. 구현은 일반적으로 거의 동일합니다.

당신이 겪은 문제는 x86 하드웨어의 변형이 너무 많아서 메모리를 이동하는 방법이 가장 빠른 방법을 알 수 없다는 것입니다. 그리고 한 상황에서 결과가 있다고 생각하더라도 메모리 레이아웃에 다른 ‘스트라이드’를 갖는 것과 같은 단순한 것은 캐시 성능이 크게 다를 수 있습니다.

실제로 수행중인 작업을 벤치마킹하거나 문제를 무시하고 C 라이브러리에 대해 수행 된 벤치 마크에 의존 할 수 있습니다.

편집 : 아, 그리고 마지막 한가지; 많은 메모리 내용을 이동하는 것은 매우 느립니다. 정수를 처리하는 간단한 B-Tree 구현과 같은 방식으로 응용 프로그램이 더 빨리 실행될 것이라고 생각합니다. (오, 그래요)

Edit2 : 코멘트에서 나의 확장을 요약하면 : 마이크로 벤치 마크가 여기서 문제이며, 당신이 생각하는 것을 측정하지 않습니다. memcpy와 memmove에 주어진 작업은 서로 크게 다릅니다. memcpy에 주어진 작업이 memmove 또는 memcpy로 여러 번 반복되면 최종 결과는 영역이 겹치지 않는 한 사용하는 메모리 이동 기능에 의존하지 않습니다.


답변

“memcpy는 memmove보다 효율적입니다.” 귀하의 경우에는 두 기능을 실행하는 동안 동일한 작업을 수행하지 않을 것입니다.

일반적으로 필요한 경우에만 memmove를 사용하십시오. 소스 및 대상 영역이 겹칠 가능성이 매우 높을 때 사용하십시오.

참조 : https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Stanford Intro Systems Lecture-7) 시간 : 36:00


답변