이미지 처리를위한 매우 빠른 memcpy?
-
19-09-2019 - |
문제
메모리 주변의 큰 데이터 덩어리를 복사 해야하는 C에서 이미지 처리를 수행하고 있습니다. 소스와 대상은 결코 겹치지 않습니다.
x86 플랫폼 에서이 작업을 수행하는 가장 빠른 방법은 무엇입니까? GCC (어디 SSE, SSE2이지만 SSE3는 사용할 수 있습니까?)?
솔루션이 어셈블리에 있거나 GCC 내재를 사용하고있을 것으로 기대합니까?
다음 링크를 찾았지만 그것이 가장 좋은 방법인지 전혀 모른다 (저자는 몇 가지 버그가 있다고한다). http://coding.derkeiler.com/archive/assembler/comp.lang.asm.x86/2006-02/msg00123.html
편집 : 사본이 필요하고 데이터를 복사 할 필요가 없습니다 (이유를 설명 할 수는 있지만 설명을 아끼지 않겠습니다 :)).
해결책
예의 윌리엄 찬 그리고 구글. Microsoft Visual Studio 2005의 Memcpy보다 30-70% 빠릅니다.
void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size)
{
__asm
{
mov esi, src; //src pointer
mov edi, dest; //dest pointer
mov ebx, size; //ebx is our counter
shr ebx, 7; //divide by 128 (8 * 128bit registers)
loop_copy:
prefetchnta 128[ESI]; //SSE2 prefetch
prefetchnta 160[ESI];
prefetchnta 192[ESI];
prefetchnta 224[ESI];
movdqa xmm0, 0[ESI]; //move data from src to registers
movdqa xmm1, 16[ESI];
movdqa xmm2, 32[ESI];
movdqa xmm3, 48[ESI];
movdqa xmm4, 64[ESI];
movdqa xmm5, 80[ESI];
movdqa xmm6, 96[ESI];
movdqa xmm7, 112[ESI];
movntdq 0[EDI], xmm0; //move data from registers to dest
movntdq 16[EDI], xmm1;
movntdq 32[EDI], xmm2;
movntdq 48[EDI], xmm3;
movntdq 64[EDI], xmm4;
movntdq 80[EDI], xmm5;
movntdq 96[EDI], xmm6;
movntdq 112[EDI], xmm7;
add esi, 128;
add edi, 128;
dec ebx;
jnz loop_copy; //loop please
loop_copy_end:
}
}
정확한 상황과 귀하가 할 수있는 가정에 따라 더 최적화 할 수 있습니다.
Memcpy 소스 (memcpy.asm)를 확인하고 특별한 케이스 처리를 제거 할 수도 있습니다. 더 최적화 할 수 있습니다!
다른 팁
모든 최적화 수준에서 -O1
또는 위의 GCC는 다음과 같은 기능에 대한 내장 정의를 사용합니다. memcpy
- 오른쪽으로 -march
매개 변수 (-march=pentium4
언급 한 기능 세트의 경우) 매우 최적의 아키텍처 별 인라인 코드를 생성해야합니다.
나는 그것을 벤치마킹하고 무엇이 나오는지 볼 것입니다.
Hapalibashi가 게시 한 SSE 코드는 갈 길입니다.
더 많은 성능이 필요하고 장치 드라이버 작성의 길고 구불 구불 한 도로에서 부끄러워하지 않는다면 : 요즘 모든 중요한 플랫폼에는 CPU 코드와 더 빠르고 병렬로 복사 작업을 수행 할 수있는 DMA 컨트롤러가 있습니다. 할 수 있습니다.
그래도 드라이버를 쓰는 것이 포함됩니다. 보안 위험으로 인해이 기능을 사용자 측에 노출시키는 큰 OS는 없습니다.
그러나 지구상의 코드가 그러한 작업을 수행하도록 설계된 하드웨어를 능가 할 수 없기 때문에 (성능이 필요한 경우) 가치가있을 수 있습니다.
이 질문은 지금 4 살이되었고 아직 아무도 메모리 대역폭을 언급 한 사람이 거의 없다는 것에 약간 놀랐습니다. CPU-Z는 내 기계에 PC3-10700 RAM이 있다고보고합니다. RAM은 10700 mbytes/sec의 피크 대역폭 (일명 전송 속도, 처리량 등)을 갖습니다. 내 컴퓨터의 CPU는 I5-2430m CPU이며 피크 터보 주파수는 3GHz입니다.
이론적으로는 무한히 빠른 CPU와 내 램으로 Memcpy는 5300 Mbytes/sec, Memcpy는 RAM에 읽은 다음 쓸 수 있기 때문에 10700의 절반. (편집 : v.oddou가 지적했듯이 이것은 단순한 근사치입니다).
반면에, 우리는 무한히 빠른 RAM과 사실적인 CPU를 가지고 있다고 상상해보십시오. 우리는 무엇을 달성 할 수 있습니까? 내 3GHz CPU를 예로 사용합시다. 32 비트 읽기와 32 비트 각 사이클을 작성할 수 있다면 3E9 * 4 = 전송할 수 있습니다. 12000 Mbytes/sec. 이것은 현대적인 CPU에 쉽게 도달 할 수있는 것 같습니다. 이미 CPU에서 실행되는 코드가 실제로 병목 현상이 아니라는 것을 알 수 있습니다. 이것이 현대 기계에 데이터 캐시가있는 이유 중 하나입니다.
데이터가 캐시됨을 알 때 Memcpy를 벤치마킹하여 CPU가 실제로 수행 할 수있는 일을 측정 할 수 있습니다. 이 작업을 정확하게하는 것은 어리석은 일입니다. 나는 임의의 숫자를 배열에 작성한 간단한 앱을 만들었고, 다른 배열에 memcpy를 만들고, 복사 된 데이터를 확인했습니다. Debugger의 코드를 통해 영리한 컴파일러가 사본을 제거하지 않았는지 확인했습니다. 배열의 크기를 변경하면 캐시 성능이 변경됩니다. 작은 배열은 캐시에 맞습니다. 큰 배열은 큰 배열이 적습니다. 다음 결과를 얻었습니다.
- 40 KBYTE 어레이 : 16000 MBYTES/SEC
- 400 KBYTE 어레이 : 11000 MBYTES/SEC
- 4000 KBYTE 어레이 : 3100 MBYTES/SEC
분명히 CPU는 16000이 이론적으로 계산 한 12000보다 많기 때문에 주기당 32 비트 이상을 읽고 쓸 수 있습니다. 이것은 CPU가 이미 생각했던 것보다 병목 현상이 훨씬 적다는 것을 의미합니다. Visual Studio 2005를 사용하여 표준 Memcpy 구현에 들어서서 컴퓨터에서 MOVQDA 명령을 사용한다는 것을 알 수 있습니다. 사이클 당 64 비트를 읽고 쓸 수 있다고 생각합니다.
올바른 코드 Hapalibashi 게시물은 내 컴퓨터에서 4200mbbytes/sec를 달성합니다. VS 2005 구현보다 약 40% 더 빠릅니다. 프리 페치 명령어를 사용하여 캐시 성능을 향상시키기 때문에 더 빠르다고 생각합니다.
요약하면 CPU에서 실행되는 코드는 병목 현상이 아니며 코드가 작은 개선 만 할 수 있다는 조정이 아닙니다.