首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何提高memcpy的性能

如何提高memcpy的性能
EN

Stack Overflow用户
提问于 2010-11-23 20:33:33
回答 7查看 42.2K关注 0票数 52

摘要:

在真实的或测试的应用程序中,memcpy似乎无法在我的系统上传输超过2GB/秒。我能做些什么来获得更快的记忆到记忆的拷贝?

详细情况:

作为数据捕获应用程序的一部分(使用一些专用硬件),我需要将大约3GB/秒的临时缓冲区复制到主内存中。为了获取数据,我为硬件驱动程序提供了一系列缓冲区(每个2MB)。硬件DMA将数据发送到每个缓冲区,然后在每个缓冲区满时通知我的程序。我的程序清空缓冲区(memcpy到另一个较大的RAM块),并将处理过的缓冲区重发到要再次填充的卡上。我对memcpy移动数据的速度有问题。看来内存到内存的拷贝应该足够快,在我正在运行的硬件上支持3GB/秒。Lavalys EVEREST给了我一个9337 me /秒内存拷贝基准测试结果,但是我无法通过memcpy达到这些速度,甚至在一个简单的测试程序中也是如此。

我通过在缓冲区处理代码中添加/删除memcpy调用来隔离性能问题。没有memcpy,我可以运行完整的数据速率-大约3GB/秒。启用memcpy后,我仅限于550 to /秒(使用当前编译器)。

为了在我的系统上对memcpy进行基准测试,我编写了一个单独的测试程序,它只是在一些数据块上调用memcpy。(我发布了下面的代码)我已经在我正在使用的编译器/IDE (National )和Visual 2010中运行了这两种代码。虽然我目前没有使用Visual,但如果它能产生必要的性能,我愿意进行切换。然而,在盲目迁移之前,我想确保它能够解决我的内存性能问题。

Visual C++ 2010: 1900 MB/秒

NI CVI 2009: 550 MB/秒

虽然我并不奇怪CVI比Visual慢得多,但我对memcpy性能如此之低感到惊讶。虽然我不确定这是否可以直接比较,但这比珠穆朗玛峰基准带宽低得多。虽然我不需要这样的性能水平,但至少需要3GB/秒。当然,标准库的实现不会比珠穆朗玛峰使用的更糟糕!

如果有的话,在这种情况下,我能做些什么来使记忆更快?

硬件详细信息: AMD Magny Cours-4x八进制核心128 GB DDR3 Windows 2003企业X64

测试程序:

代码语言:javascript
运行
复制
#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;
}

编辑:如果您有额外的五分钟,并想要贡献,您能运行上述代码在您的机器上,并张贴您的时间作为评论?

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2010-11-24 17:28:34

在这种情况下,我找到了提高速度的方法。我编写了一个多线程版本的memcpy,将要在线程间复制的区域分割开来。下面是设置块大小的一些性能缩放数字,使用与上面相同的计时代码。我不知道性能,特别是对于这么小的块,会扩展到这么多线程。我怀疑这与这台机器上的大量内存控制器(16)有关。

代码语言:javascript
运行
复制
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代码。请注意,在这段代码中没有错误检查--这可能需要为您的应用程序添加。

代码语言:javascript
运行
复制
#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;
}
票数 36
EN

Stack Overflow用户

发布于 2010-11-23 22:51:38

需要注意的一件事是,您的进程(以及memcpy()的性能)受到操作系统任务调度的影响--很难说这在您的时间安排中有多大的影响,但是tit是很难控制的。设备DMA操作不受此限制,因为一旦CPU启动,它就不会在CPU上运行。但是,由于您的应用程序是一个实际的实时应用程序,如果您还没有使用Windows的进程/线程优先级设置,您可能需要尝试一下。请记住,您必须小心这一点,因为它会对其他进程(以及机器上的用户体验)产生真正的负面影响。

另外要记住的是,OS内存虚拟化可能会在这里产生影响--如果您要复制的内存页实际上没有物理内存页支持,那么memcpy()操作将错误地发送到操作系统上,从而使物理备份到位。您的DMA页很可能被锁定在物理内存中(因为它们必须用于DMA操作),因此memcpy()的源内存可能不是这方面的问题。您可能会考虑使用Win32 VirtualAlloc() API来确保您的memcpy()目标内存被提交(我认为VirtualAlloc()是正确的API,但我可能忘记了一个更好的API--我已经有一段时间没有做过这样的事情了)。

最后,看看是否可以使用Skizz解释的技术来完全避免memcpy() --如果资源允许的话,这是最好的选择。

票数 5
EN

Stack Overflow用户

发布于 2010-11-23 21:36:56

在获得所需的内存性能方面有一些障碍:

  1. 带宽--数据从内存移动到CPU并再次返回的速度是有限的。根据这篇维基百科文章的说法,266 the的DDR3内存的上限大约是17 get /s。现在,使用memcpy,您需要将这个值减半才能获得最大的传输速率,因为数据是读写的。从您的基准测试结果来看,您似乎没有在系统中运行最快的RAM。如果你能负担得起的话,升级主板/内存(而且它不会便宜,英国的Overclocker目前有3x4GB的PC16000,If 400)。
  2. often是一个先发制人的多任务操作系统,因此您的进程常常会被挂起,以允许其他进程查看并执行一些事情。这将打击你的缓存和拖延你的转移。在最坏的情况下,您的整个进程可以被缓存到磁盘!
  3. CPU -正在移动的数据还有很长的路要走: RAM、->、L2、缓存、->、L1、缓存、->、->、L1、->、L2、->、RAM。甚至可能有一个L3缓存。如果您想要涉及到CPU,您确实希望在复制L2的同时加载L1。不幸的是,现代CPU能够比加载L1缓存块所用的时间更快地通过L1缓存块。CPU有一个内存控制器,在这些情况下,您的流数据按顺序进入CPU,但是您仍然会遇到问题。

当然,更快的方法是不去做。捕获的数据可以在RAM中的任何地方写入,或者是在固定位置使用的缓冲区。如果你可以在任何地方写它,那么你根本不需要备忘录。如果它是固定的,你能处理数据到位,并使用双缓冲类型的系统吗?也就是说,开始捕获数据,当数据半满时,开始处理数据的前半部分。当缓冲区已满时,开始将捕获的数据写入开始并处理下半部分。这就要求算法能够比捕获卡生成的数据更快地处理数据。它还假设数据在处理后被丢弃。实际上,这是一个带有转换的memcpy,它是复制过程的一部分,因此您可以获得:

代码语言:javascript
运行
复制
load -> transform -> save
\--/                 \--/
 capture card        RAM
   buffer

而不是:

代码语言:javascript
运行
复制
load -> save -> load -> transform -> save
\-----------/
memcpy from
capture card
buffer to RAM

或者更快的内存!

编辑:另一个选择是处理数据源和PC之间的数据--你能在里面放一个DSP / FPGA吗?自定义硬件总是比通用CPU更快。

另一个想法:我已经有一段时间没有做任何高性能的图形处理了,但是您能把数据DMA到显卡中,然后再DMA吗?你甚至可以利用数据自动化系统来做一些处理。这将使CPU完全脱离内存传输循环。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4260602

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档