programing

memcpy 퍼포먼스 향상 방법

projobs 2022. 8. 13. 11:41
반응형

memcpy 퍼포먼스 향상 방법

요약:.

memcpy는 실제 어플리케이션 또는 테스트 어플리케이션에서 2GB/sec 이상의 전송을 할 수 없는 것 같습니다.메모리 간 복사를 고속화하려면 어떻게 해야 합니까?

상세:

데이터 캡처 애플리케이션의 일부로서(일부 전용 하드웨어를 사용하여) 임시 버퍼에서 메인 메모리로 약 3GB/sec를 복사해야 합니다.데이터를 수집하기 위해 하드웨어 드라이버에 일련의 버퍼(각 2MB)를 제공합니다.하드웨어는 데이터를 각 버퍼에 DMA하고 버퍼가 가득 차면 프로그램에 알립니다.내 프로그램은 버퍼를 비우고(다른 큰 RAM 블록에 메모리), 처리된 버퍼를 카드에 다시 붙여 다시 채웁니다.memcpy가 데이터를 빠르게 이동하는 데 문제가 있습니다.메모리 간 복사는 실행 중인 하드웨어에서 3GB/sec를 지원할 수 있을 정도로 빠릅니다.라발리스 에베레스트는 나에게 9337을 준다.MB/초의 메모리 카피 벤치마크 결과는 나왔지만, 간단한 테스트 프로그램에서도 memcpy에서는 그 속도에 전혀 근접할 수 없습니다.

버퍼 처리 코드 내의 memcpy 콜을 추가/삭제하여 퍼포먼스 문제를 특정했습니다.memcpy가 없어도 풀 데이터 레이트(약 3GB/초)를 실행할 수 있습니다.memcpy를 유효하게 하면, 약 550 Mb/s로 제한됩니다(현재 컴파일러 사용).

시스템에서 memcpy를 벤치마킹하기 위해 데이터 블록에서 memcpy를 호출하는 별도의 테스트 프로그램을 작성했습니다. (아래 코드를 올렸습니다) 사용 중인 컴파일러/IDE(National Instruments CVI)와 Visual Studio 2010 모두에서 이 프로그램을 실행하고 있습니다.현재 Visual Studio를 사용하고 있지 않지만, 필요한 퍼포먼스를 얻을 수 있다면 바꿀 의향이 있습니다.하지만 무작정 자리를 옮기기 전에 제 memcpy 퍼포먼스 문제를 확실히 해결하고 싶었습니다.

Visual C++ 2010 : 1900 MB/초

NI CVI 2009: 550 MB/초

CVI가 Visual Studio보다 크게 느린 것은 놀랄 일이 아니지만, memcpy 퍼포먼스가 이렇게 낮다는 것은 놀랍습니다.직접 비교할 수 있을지는 모르겠지만 에베레스트 벤치마크 대역폭보다 훨씬 낮습니다.그 정도의 퍼포먼스는 필요 없지만 최소 3GB/s는 필요합니다.물론 표준 도서관의 구현은 에베레스트가 사용하고 있는 것보다 더 나쁘지는 않을 것이다!

이 상황에서 memcpy를 고속화하기 위해 무엇을 할 수 있을까요?


하드웨어 상세: AMD Magny Cours - 4x 옥탈코어 128 GB DDR3 Windows Server 2003 Enterprise X64

테스트 프로그램:

#include <windows.h>
#include <stdio.h>

const size_t NUM_ELEMENTS = 2*1024 * 1024;
const size_t ITERATIONS = 10000;

int main (int argc, char *argv[])
{
    LARGE_INTEGER start, stop, frequency;

    QueryPerformanceFrequency(&frequency);

    unsigned short * src = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);
    unsigned short * dest = (unsigned short *) malloc(sizeof(unsigned short) * NUM_ELEMENTS);

    for(int ctr = 0; ctr < NUM_ELEMENTS; ctr++)
    {
        src[ctr] = rand();
    }

    QueryPerformanceCounter(&start);

    for(int iter = 0; iter < ITERATIONS; iter++)
        memcpy(dest, src, NUM_ELEMENTS * sizeof(unsigned short));

    QueryPerformanceCounter(&stop);

    __int64 duration = stop.QuadPart - start.QuadPart;

    double duration_d = (double)duration / (double) frequency.QuadPart;

    double bytes_sec = (ITERATIONS * (NUM_ELEMENTS/1024/1024) * sizeof(unsigned short)) / duration_d;

    printf("Duration: %.5lfs for %d iterations, %.3lfMB/sec\n", duration_d, ITERATIONS, bytes_sec);

    free(src);
    free(dest);

    getchar();

    return 0;
}

편집: 5분 정도 시간이 남아서 기여하고 싶은 경우, 위의 코드를 머신에서 실행하여 시간을 코멘트로 투고해 주실 수 있습니까?

나는 이 상황에서 속도를 높일 방법을 찾았다.멀티 스레드 버전의 memcpy를 쓰고, 복사할 영역을 스레드 간에 분할했습니다.상기와 같은 타이밍 코드를 사용하여 설정된 블록 크기에 대한 몇 가지 성능 스케일링 수치를 보여 줍니다.특히 이렇게 작은 크기의 블록에서 성능이 이렇게 많은 스레드로 확장될 줄은 몰랐습니다.이 기계상의 메모리 컨트롤러(16)의 수가 많은 것과 관계가 있는 것이 아닐까 생각합니다.

Performance (10000x 4MB block memcpy):

 1 thread :  1826 MB/sec
 2 threads:  3118 MB/sec
 3 threads:  4121 MB/sec
 4 threads: 10020 MB/sec
 5 threads: 12848 MB/sec
 6 threads: 14340 MB/sec
 8 threads: 17892 MB/sec
10 threads: 21781 MB/sec
12 threads: 25721 MB/sec
14 threads: 25318 MB/sec
16 threads: 19965 MB/sec
24 threads: 13158 MB/sec
32 threads: 12497 MB/sec

3 스레드와 4 스레드 사이의 엄청난 성능 향상을 이해할 수 없습니다.왜 이렇게 뛰어내리죠?

아래에 기재한 memcpy code를 첨부하여 같은 문제가 발생할 수 있는 다른 코드를 첨부하였습니다.이 코드에는 에러 체크가 없습니다.어플리케이션용으로 추가해야 할 수도 있습니다.

#define NUM_CPY_THREADS 4

HANDLE hCopyThreads[NUM_CPY_THREADS] = {0};
HANDLE hCopyStartSemaphores[NUM_CPY_THREADS] = {0};
HANDLE hCopyStopSemaphores[NUM_CPY_THREADS] = {0};
typedef struct
{
    int ct;
    void * src, * dest;
    size_t size;
} mt_cpy_t;

mt_cpy_t mtParamters[NUM_CPY_THREADS] = {0};

DWORD WINAPI thread_copy_proc(LPVOID param)
{
    mt_cpy_t * p = (mt_cpy_t * ) param;

    while(1)
    {
        WaitForSingleObject(hCopyStartSemaphores[p->ct], INFINITE);
        memcpy(p->dest, p->src, p->size);
        ReleaseSemaphore(hCopyStopSemaphores[p->ct], 1, NULL);
    }

    return 0;
}

int startCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        hCopyStartSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        hCopyStopSemaphores[ctr] = CreateSemaphore(NULL, 0, 1, NULL);
        mtParamters[ctr].ct = ctr;
        hCopyThreads[ctr] = CreateThread(0, 0, thread_copy_proc, &mtParamters[ctr], 0, NULL); 
    }

    return 0;
}

void * mt_memcpy(void * dest, void * src, size_t bytes)
{
    //set up parameters
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        mtParamters[ctr].dest = (char *) dest + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].src = (char *) src + ctr * bytes / NUM_CPY_THREADS;
        mtParamters[ctr].size = (ctr + 1) * bytes / NUM_CPY_THREADS - ctr * bytes / NUM_CPY_THREADS;
    }

    //release semaphores to start computation
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
        ReleaseSemaphore(hCopyStartSemaphores[ctr], 1, NULL);

    //wait for all threads to finish
    WaitForMultipleObjects(NUM_CPY_THREADS, hCopyStopSemaphores, TRUE, INFINITE);

    return dest;
}

int stopCopyThreads()
{
    for(int ctr = 0; ctr < NUM_CPY_THREADS; ctr++)
    {
        TerminateThread(hCopyThreads[ctr], 0);
        CloseHandle(hCopyStartSemaphores[ctr]);
        CloseHandle(hCopyStopSemaphores[ctr]);
    }
    return 0;
}

실행 시간 내에 완료되었는지 컴파일 시간 내에 완료해야 하는지 잘 모르겠습니다만, 대부분의 경우 CPU의 64비트에 비해 벡터 유닛은 128비트를 메모리에 쓸 수 있기 때문에 SSE 또는 유사한 확장을 활성화해야 합니다.

해라이 실장.

네, 그리고 송신원과 수신처가 모두 128비트로 맞춰져 있는지 확인해 주세요.송신원과 수신처가 각각 정렬되어 있지 않으면 memcpy()는 중대한 마법을 실행해야 합니다.:)

입니다.memcpy()는 OS의 에 영향을 은 타이밍에 어느 을 미치는지는수은 제어하기 .) OS는 이 에 영향을 줍니다이것은 타이밍에 얼마나 중요한 요소인지 말하기 어렵기 때문에 제어하기가 어렵습니다.디바이스의 DMA 동작은, 기동하면 CPU상에서 실행되지 않기 때문에, 이것에 해당하지 않습니다.다만, 사용하고 있는 애플리케이션은 실제의 리얼 타임 애플리케이션이기 때문에, Windows 의 프로세스/스레드 우선 순위 설정을 시험해 보는 것이 좋을지도 모릅니다.다른 프로세스(및 사용자 경험)에서 매우 부정적인 영향을 미칠 수 있으므로 주의해야 합니다.

을 미칠 수 의 메모리 물리 되지 않은 OS 가상화가 영향을 미칠 수 있습니다.복사처의 메모리 페이지가 실제로 물리 RAM 페이지에 의해 백업되지 않은 경우,memcpy()물리적인 백업을 확립하기 위해 OS에 장애가 발생합니다.는 물리 있을 이 높기 에), DMA 에의 는, 「」(DMA 의 조작을 위해서), 「」(DMA 의 조작을 위해서)입니다.memcpy()그런 점에서 문제가 되지 않을 것 같습니다. Win32 의 .VirtualAlloc()하게 memcpy()전념하고 있다(내 생각에)VirtualAlloc()이 API는 이 API에 적합하지만, 제가 잊고 있는 더 좋은 API가 있을지도 모릅니다.'API'를 참조해 주세요.

마지막으로, Skizz가 설명한 기술을 사용하여 Skizz를 회피할 수 있는지 확인합니다.memcpy()자원만 허락한다면 그게 최선의 선택입니다.

필요한 메모리 성능을 얻기 위해서는 몇 가지 장벽이 있습니다.

  1. 대역폭 - 데이터를 메모리에서 CPU로 이동하고 다시 되돌리는 속도에는 제한이 있습니다.이 Wikipedia 기사에 따르면 266MHz DDR3 RAM의 상한은 약 17GB/s입니다.이제 memcpy를 사용하여 데이터를 읽고 쓰기 시작한 후 최대 전송 속도를 얻으려면 이 값을 절반으로 줄여야 합니다.벤치마크 결과를 보면 시스템에서 가능한 한 빠른 RAM을 실행하고 있지 않은 것 같습니다.여유가 있다면 메인보드/RAM을 업그레이드하십시오(가격은 저렴하지 않습니다.현재 영국의 오버클럭은 4GB PC16000을 3대 탑재하고 있으며 가격은 400파운드입니다).

  2. OS - Windows는 프리엠프티브 멀티태스킹 OS이기 때문에 다른 프로세스를 조사하여 작업을 수행할 수 있도록 프로세스가 중단되는 경우가 많습니다.이로 인해 캐시가 클로버되어 전송이 정지됩니다.최악의 경우 전체 프로세스가 디스크에 캐시될 수 있습니다.

  3. CPU - 이동되는 데이터는 아직 멀었습니다.RAM -> L2 캐시 -> L1 캐시 -> CPU -> L1 -> L2 -> RAM.L3 캐시가 있는 경우도 있습니다.CPU를 사용할 경우 L1을 복사하면서 L2를 로드해야 합니다.유감스럽게도 최신 CPU는 L1 캐시 블록을 통해 L1을 로드하는 데 걸리는 시간보다 빠르게 실행될 수 있습니다.CPU에는 메모리 컨트롤러가 탑재되어 있어 데이터를 CPU로 순차적으로 스트리밍할 수 있지만 여전히 문제가 발생할 수 있습니다.

물론, 무언가를 하는 더 빠른 방법은 그것을 하지 않는 것이다.캡처된 데이터는 RAM 내의 임의의 위치에 쓸 수 있는지, 아니면 고정된 위치에서 사용되는 버퍼인지.아무데나 쓸 수 있다면 메모리는 전혀 필요 없습니다.수정이 되면 데이터를 제 위치에서 처리하여 더블 버퍼형 시스템을 사용할 수 있습니까?즉, 데이터 캡처를 시작하고 절반만 차면 데이터 전반의 처리를 시작합니다.버퍼가 가득 차면 캡처한 데이터를 시작 부분에 쓰기 시작하고 나머지 절반을 처리합니다.이를 위해서는 알고리즘이 캡처 카드가 생성하는 것보다 데이터를 더 빨리 처리할 수 있어야 합니다.또한 처리 후 데이터가 폐기되는 것으로 가정합니다.사실상 복사 프로세스의 일부로 변환된 memcpy이므로 다음과 같은 이점을 얻을 수 있습니다.

load -> transform -> save
\--/                 \--/
 capture card        RAM
   buffer

다음 대신:

load -> save -> load -> transform -> save
\-----------/
memcpy from
capture card
buffer to RAM

또는 RAM을 고속화할 수 있습니다.

편집: 또 하나의 옵션은 데이터 소스와 PC 간의 데이터 처리입니다.DSP/FPGA를 삽입할 수 있습니까?커스텀 하드웨어는 항상 범용 CPU보다 빠릅니다.

또 다른 생각:고성능 그래픽스 작업은 오랜만입니다만, 데이터를 그래픽 카드에 DMA하고 나서 다시 DMA해 주실 수 있습니까?CUDA를 사용하여 처리의 일부를 수행할 수도 있습니다.이것은 메모리 전송 루프의 종합적으로 CPU가 걸릴 것이다.

우선, 그런 기억은 16바이트 경계에 정렬해 있는지 확인해 보면 그렇지 않으면 처벌 거야.이것은 가장 중요한 것이다.

만약 당신이 표준 규격 해결책이 필요하지 못한다면, 것들 같은 일부 컴파일러 특정 확장을 사용하여에 의해를 발전시킬 때, 독자아 볼 수 있습니다.memcpy64(당신의 컴파일러 의사와는지 체크해 을 사용할 수 있습니다).사실은memcpy싱글 바이트 복사본을 다루지만 너가 이 제한하지 않는 시기이다 훨씬 빠르다 4또는 8바이트이면 이동할 수 있어야.

다시, 그것이 네가 인라인 어셈블리 코드를 쓸?

아마도 좀 어떻게 더 큰 메모리 영역 처리는지에 대해 더 많은 설명할 수 있을까?

응용 프로그램 속에서 간단한 것이 아니라, 복사하는 버퍼의 소유권을 통과할 수 있을까요?이것은 아주 문제가 제거 될 것이다.

는는사???? or or or or or or or or or?memcpy사만하 하는 ??????캡처한 데이터로부터 순차적으로 데이터 스트림을 구축하기 위해 더 큰 용량의 메모리를 사용하고 있는 것은 아닐까요?특히 한 번에 한 명의 캐릭터를 처리하는 경우에는 중간에서 만날 수 있습니다.예를 들어, 처리 코드를 '연속 메모리 영역'이 아닌 '버퍼 배열'로 표현되는 스트림을 수용하도록 조정할 수 있습니다.

SSE2 레지스터를 사용하여 memcpy를 보다 효율적으로 구현할 수 있습니다.VC2010 버전에서는 이미 이 기능이 있습니다.따라서 정렬된 메모리를 건네는 것이 더 중요합니다.

VC 2010 버전보다 더 잘할 수 있을지는 모르지만, 그 방법에 대한 이해가 필요합니다.

PS: 반전 호출로 사용자 모드 프로그램에 버퍼를 전달하여 복사를 방지할 수 있습니다.

를 것을 합니다.fast_memcpy 예상되는 하여 최신 한 저장 여부를 .또, 예상되는 사용 패턴도 고려해, 최신의 CPU에는, 쓰고 있는 데이터를 다시 읽어낼 필요가 있는지를 CPU에 통지할 수 있는 특별한 스토어 명령이 있습니다.다시 가 없음을 하면 ( 큰 수 .memcpy운용을 실시합니다.

언급URL : https://stackoverflow.com/questions/4260602/how-to-increase-performance-of-memcpy

반응형