摘要:
在真实的或测试的应用程序中,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
测试程序:
#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;
}
编辑:如果您有额外的五分钟,并想要贡献,您能运行上述代码在您的机器上,并张贴您的时间作为评论?
发布于 2010-11-24 17:28:34
在这种情况下,我找到了提高速度的方法。我编写了一个多线程版本的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代码。请注意,在这段代码中没有错误检查--这可能需要为您的应用程序添加。
#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;
}
发布于 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()
--如果资源允许的话,这是最好的选择。
发布于 2010-11-23 21:36:56
在获得所需的内存性能方面有一些障碍:
当然,更快的方法是不去做。捕获的数据可以在RAM中的任何地方写入,或者是在固定位置使用的缓冲区。如果你可以在任何地方写它,那么你根本不需要备忘录。如果它是固定的,你能处理数据到位,并使用双缓冲类型的系统吗?也就是说,开始捕获数据,当数据半满时,开始处理数据的前半部分。当缓冲区已满时,开始将捕获的数据写入开始并处理下半部分。这就要求算法能够比捕获卡生成的数据更快地处理数据。它还假设数据在处理后被丢弃。实际上,这是一个带有转换的memcpy,它是复制过程的一部分,因此您可以获得:
load -> transform -> save
\--/ \--/
capture card RAM
buffer
而不是:
load -> save -> load -> transform -> save
\-----------/
memcpy from
capture card
buffer to RAM
或者更快的内存!
编辑:另一个选择是处理数据源和PC之间的数据--你能在里面放一个DSP / FPGA吗?自定义硬件总是比通用CPU更快。
另一个想法:我已经有一段时间没有做任何高性能的图形处理了,但是您能把数据DMA到显卡中,然后再DMA吗?你甚至可以利用数据自动化系统来做一些处理。这将使CPU完全脱离内存传输循环。
https://stackoverflow.com/questions/4260602
复制相似问题