快速初始化内存
许多计算密集型的应用都需要处理大量内存,这种应用中的内存初始化是一个常规操作,而内存和CPU内部的数据交换之间的速度瓶颈决定了内存初始化将会占用可观的时间。但因为应用程序初始化内存往往调用CRT的memset或者Windows API的ZeroMemory,很少有人在初始化方面进行优化。
另一方面,现在的应用硬件一般配置都比较好,大部分应用都运行在PII之上,但我们在使用诸如VC之类的编译环境时往往选择速度优化,并选择合适的处理器,然后寄希望于编译器给我们生成优化的结果,结果往往发现并不如意。
在我们的一个图像处理项目中,需要大量内存操作,而且多个线程同时运行,内存存取成为了各个模块的竞争资源,所以对内存存取优化成为项目的关键。在努力减少内存操作遍数的基础上,加快内存初始化成为我们的改进重点。
在用VC各种手段都没有太多改进后,我们把目光转向处理器特征。从Pentium系列开始,一方面Intel在不断提高CPU主频,同时也在针对多媒体等应用相继推出MMX/SSE/SSE2,增加了许多多位快速处理指令。在高层语言方面,Intel的C++ Compiler提供了针对不同处理器的最优化结果。但在一个成熟项目中贸然使用另外一种编译环境的风险较大,所以我们从Intel环境中抽取了memset的实现,重新组织了一个Lib,并在我们的项目中针对内存初始化进行了改动,并链接到抽取的lib库中。在内存初始化方面有了一个较大的提高。
下面我们用测试例子说明该过程。
一个例子
在测试程序中,分别调用微软C库的memset和intel版本的memset分别对100M内存进行60遍初始化,,为了模拟多线程环境,启动了两个线程同时进行内存初始化。测试时使用了Release版,为了方面查看包含了调试信息(调试信息无影响)。测试结果:
MSC 版本:12.453~12.547秒
Intel C版本:4.375~4.531秒
可见在大量内存操作时差别比较大。对内存存取密集型项目,因为内存存取往往是瓶颈,应该还可以提高整体处理性能。
下面是例子的代码:
// 本程序示例了使用微软CRT的memset和Intel优化的memset初始化内存的速度差异
// Lihw.
#include <stdio.h>
#include <windows.h>
#include <PROCESS.H>
extern "C"
void * __cdecl __intel_new_memset(void *, int, size_t);
#pragma comment(lib,"intelmem.lib")
#define SIZE 1024*1024*100
void threadfunc(void *dummy)
{
LPBYTE lpByte = (LPBYTE)dummy;//new BYTE[SIZE];
int j;
#define LoopTimes 60
DWORD dwStart, dwTime1,dwTime2;
//
//intel version
dwStart = GetTickCount();
for (j=0; j< LoopTimes; j++)
{
__intel_new_memset(lpByte,1,SIZE);
}
dwTime1 = GetTickCount() - dwStart;
//MS crt version
dwStart = GetTickCount();
for (j=0; j< LoopTimes; j++)
{
memset(lpByte,1,SIZE);
//ZeroMemory(lpByte,SIZE);
}
dwTime2 = GetTickCount() - dwStart;
//delete []lpByte;
printf("Intel=%dms MSC=%dms\n",dwTime1,dwTime2);
}
int main(int argc, char* argv[])
{
#define THREADS 2
HANDLE hThread[THREADS]; //array to hold thread handle
LPBYTE lpByte[THREADS]; //Array to hold thread-specific memory
int i;
//Count mem alloc time. Debug version is very long
DWORD dwStart = GetTickCount();
for (i=0; i<THREADS;i++)
{
lpByte[i] = new BYTE[SIZE];
}
printf("Alloc spend=%d\n",GetTickCount() - dwStart);
//Start thread
for (i=0; i<THREADS;i++)
hThread[i] = (HANDLE)_beginthread( threadfunc, 0, lpByte[i] );
//threadfunc(lpByte[i]);
WaitForMultipleObjects(THREADS,hThread,TRUE,INFINITE);
for (i=0; i<THREADS;i++)
delete []lpByte[i];
printf("Process Exec time=%dms\n",GetTickCount() - dwStart);
return 0;
}
让我们来看看究竟上什么造成这么大的差别。在Release版本的__intel_new_memset处和memset处设置断点,打开反汇编窗口:
Intel版本:
31: for (j=0; j< LoopTimes; j++)
32: {
33: __intel_new_memset(lpByte,1,SIZE);
00401017 push 6400000h
0040101C push 1
0040101E push ebx
0040101F call ___intel_new_memset (00401110)
00401024 add esp,0Ch
00401027 dec esi
00401028 jne threadfunc+17h (00401017)
34: }