一、实验背景
C++一直有一个被人诟病的问题,就是在大量分配(new&delete)小对象时,性能会有很大的下降。因此,Andrei Alexandrescu 的书中给出了一种快速的小对象分配解决方案。这种方案能比较好的解决小对象分配问题。而C#中,在“Writing Faster Managed Code: Know What Things Cost”一文中作者提到“The amortized cost of creating and later automatically reclaiming an object is sufficiently low that you can create many tens of millions of small objects per second”,于是我就设计了一个实验来比较C++/C#在小对象分配上的速度差异,同时我这个实验也比较了.Net Framework 1.1与.Net Framework 2.0的区别。
二、实验设计
我设计的实验主要验证小对象分配,而且一般情况下小对象分配必然和存储结合的,单纯的new和delete有可能会被编译器优化掉,或者因为系统中的cache原因会影响实验的可靠性。因此在代码中我使用了固定长度的数组作为存储这些小对象指针的地方,以模拟一个分配——储存的过程。
实验中使用的小对象从4字节到1024字节,一共9种不同的小对象大小。以观察不同大小的对象是否有被优化。
实验的计时使用了TickCount,虽然不太准确,但是用于对比已经足够了。
下面先给出C++代码,其中用到简单的template,我机器上的RAND_MAX=0x7fff
struct smalldata
... {
int d[nsize];
} ;
struct smallobj
... {
void* data;
} ;
FILE * f;
smallobj g_p[RAND_MAX];
template < int nsize >
void test()
... {
memset(g_p,0,sizeof(g_p));
DWORD c=0;
DWORD a;
DWORD bt=GetTickCount();
while (c++<10000000)
...{
a=rand();
if (g_p[a].data)...{
delete g_p[a].data;
g_p[a].data=0;
}
g_p[a].data=new smalldata<nsize>;
}
DWORD et=GetTickCount();
fprintf(f,"objsize=%d, time=%f ",nsize,(et-bt)/1000.0f);
}
int _tmain( int argc, _TCHAR * argv[])
... {
memset(g_p,0,sizeof(g_p));
f=fopen("a.txt","w+");
srand( (unsigned)time( NULL ) );
test<1>();
test<2>();
test<4>();
test<8>();
test<16>();
test<32>();
test<64>();
test<128>();
test<256>();
fclose(f);
return 0;
}
下面是C#代码,
... {
public static ArrayList arr;
public static Random rand;
public static void test(int nsize)
...{
int c=0;
int a;
int bt=Environment.TickCount;
while(c++<10000000)
...{
a=rand.Next(0x7fff);
arr[a]=new int[nsize];
}
int et=Environment.TickCount;
sw.WriteLine("objsize={0}, time={1}",nsize,(et-bt)/1000.0);
}
public static StreamWriter sw;
/**//// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
...{
//
// TODO: Add code to start application here
//
arr=new ArrayList(0x7fff);
for (int a=0;a<0x7fff;a++)
...{
arr.Add(null);
}
rand=new Random();
sw= new StreamWriter("a.txt");
test(1);
test(2);
test(4);
test(8);
test(16);
test(32);
test(64);
test(128);
test(256);
sw.Close();
}
}
三、实验结果
这里我就不给出我机器的具体配置,因为这是一个对比实验,具体数值会因为机器不同而有很大变化的。
首先是C++程序结果,第一列是实验对象的大小,后面是几次实验的时间(s)
大小(byte)
4 8.359 8.375 8.500 8.359 8.437
8 8.672 8.453 8.421 8.516 8.453
16 8.765 8.672 8.704 8.875 8.657
32 9.204 9.234 9.328 9.265 9.218
64 10.453 10.516 10.593 10.516 10.453
128 12.078 11.984 12.000 12.047 11.969
256 15.281 15.328 15.329 15.234 15.281
512 18.313 18.500 18.187 18.203 18.188
1024 24.094 24.141 24.047 23.828 23.797
然后是C#的结果,其中结果中前3列是.Net Framework 1.1编译的,后3列是Framework 2.0编译的
4 1.453 1.484 1.468 | 1.297 1.313 1.36
8 1.656 1.672 1.656 | 1.469 1.5 1.515
16 1.969 2.062 1.969 | 1.813 1.812 1.86
32 2.843 3.047 2.796 | 2.609 2.578 2.64
64 4.5 4.531 4.454 | 4.203 4.219 4.219
128 8.282 8.266 8.218 | 7.594 7.594 7.672
256 14.531 14.625 14.828 | 14.844 14.672 14.75
512 29.109 29.141 28.875 | 25.89 25.593 25.609
1024 51.594 51.875 51.11 | 45.281 45.813 45.391
可以看出,在256bytes以下的对象分配上,C#比C++有较大的优势,我觉得这个优势是来自于C#本身对小对象分配的优化以及其GC不是立即释放内存上,这就提供了某些被分配的内存不需要象C++那样先释放再分配,而是可以直接重用。
但是,在256bytes以上时,C#的速度却大大降低,在1024byte上更是只有C++一半的速度。同时我们看到,Framework 2.0在这方面有一点改善,虽然速度提高比例并不多。我觉得这其中有boxing和unboxing的原因,同时也有GC的原因。