无常

记录无常工作上的点点滴滴
posts - 106, comments - 344, trackbacks - 6, articles - 1

托管代码和非托管代码效率的对比。

Posted on 2006-12-07 10:14 无常 阅读(6473) 评论(69)  编辑 收藏 所属分类: dot net

 一直以来只知道托管代码的效率要比非托管代码低,至于低多少也没有可参考的数据。今天在csdn看到的英特尔多核平台编程优化大赛的广告,把里面的代码下载回来,分别用非托管c/托管cpp/c#做了个简略的性能测试,不比不知道,一比吓了一跳。且看数据说话。

 

第一步:原始代码如:

/* compute the potential energy of a collection of */
/* particles interacting via pairwise potential    */
 
#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<math.h>
#include 
<windows.h>
#include 
<time.h>
 
#define NPARTS 1000
#define NITER 201
#define DIMS 3
 
int rand( void );
int computePot(void);
void initPositions(void);
void updatePositions(void);
 
double r[DIMS][NPARTS];
double pot;
double distx, disty, distz, dist;

int main() {
   
int i;
   clock_t start, stop;

   initPositions();
   updatePositions();
 
   start
=clock();
   
for( i=0; i<NITER; i++ ) {
      pot 
= 0.0;
      computePot();
      
if (i%10 == 0) printf("%5d: Potential: %10.3f\n", i, pot);
      updatePositions();
   }

   stop
=clock();
   printf (
"Seconds = %10.9f\n",(double)(stop-start)/ CLOCKS_PER_SEC);
   
int e;
   scanf(
"%d",&e);
}

 
 
void initPositions() {
   
int i, j;
 
   
for( i=0; i<DIMS; i++ )
      
for( j=0; j<NPARTS; j++ ) 
         r[i][j] 
= 0.5 + ( (double) rand() / (double) RAND_MAX );
}

 
 
void updatePositions() {
   
int i, j;
 
   
for( i=0; i<DIMS; i++ )
      
for( j=0; j<NPARTS; j++ )
         r[i][j] 
-= 0.5 + ( (double) rand() / (double) RAND_MAX );
}

 
 
int computePot() {
   
int i, j;

   
for( i=0; i<NPARTS; i++ ) {
      
for( j=0; j<i-1; j++ ) {
        distx 
= pow( (r[0][j] - r[0][i]), 2 );
        disty 
= pow( (r[1][j] - r[1][i]), 2 );
        distz 
= pow( (r[2][j] - r[2][i]), 2 );
        dist 
= sqrt( distx + disty + distz );
        pot 
+= 1.0 / dist;
      }

   }

   
return 0;
}


执行结果如下:

执行时间4.609s。

 

 第二步:托管

新建一个 C++ CLR Console Aplication,命名为mcpp。打开mcpp.cpp文件,将原始代码粘贴进来即可(代码太长这里就不贴出来了,可以在全贴下面的下载全部源码)。

执行结果如下:

执行时间:15.1720s。

 

第二步:c#

笔者将原始代码翻译成CS代码,如下:

using System;
using System.Collections.Generic;
using System.Text;


namespace cs
{
    
class Program
    
{
        
private const int RAND_MAX = 0x7fff;
        
private const int NPARTS = 1000;
        
private const int NITER = 201;
        
private const int DIMS = 3;
        
private double pot;
        
private double distx, disty, distz, dist;
        
private Random random = new Random(Environment.TickCount);
        
private double[][] r = new double[DIMS][];

        
public void main()
        
{
            
int i;
            
int start, stop;

            
for (int ii = 0; ii < DIMS; ii++)
            
{
                r[ii] 
= new double[NPARTS];
            }


            initPositions();
            updatePositions();

            start 
= Environment.TickCount;
            
for (i = 0; i < NITER; i++)
            
{
                pot 
= 0.0;
                computePot();
                
if (i % 10 == 0)
                    Console.WriteLine(
"{0}: Potential: {1:##########.###}", i, pot);
                updatePositions();
            }

            stop 
= Environment.TickCount;
            Console.WriteLine(
"Seconds = {0:##########.#########}", (double)(stop - start)/1000);
        }


        
private void computePot()
        
{
            
int i, j;

            
for (i = 0; i < NPARTS; i++)
            
{
                
for (j = 0; j < i - 1; j++)
                
{
                    distx 
= Math.Pow((r[0][j] - r[0][i]), 2);
                    disty 
= Math.Pow((r[1][j] - r[1][i]), 2);
                    distz 
= Math.Pow((r[2][j] - r[2][i]), 2);
                    dist 
= Math.Sqrt(distx + disty + distz);
                    pot 
+= 1.0 / dist;
                }

            }

        }


        
private void updatePositions()
        
{
            
int i, j;

            
for (i = 0; i < DIMS; i++)
                
for (j = 0; j < NPARTS; j++)
                    r[i][j] 
-= 0.5 + ((double)random.Next(RAND_MAX)/(double)RAND_MAX);
        }


        
private void initPositions()
        
{
            
int i, j;
            
for (i = 0; i < DIMS; i++)
                
for (j = 0; j < NPARTS; j++)
                    r[i][j] 
= 0.5 + ((double)random.Next(RAND_MAX)/(double)RAND_MAX);
        }


        
static void Main(string[] args)
        
{
            Program p 
= new Program();
            p.main();
            Console.ReadLine();

        }

    }

}

执行结果如下:

 
执行时间:62.453s!
 
第四、数据比较
非托管C:  4.609s
托管cpp: 15.720s
托管c#:  62.453s
 PS机器配置:p4 3.0G双核,1G内存
 

全部代码下载

Feedback

#1楼    回复  引用    

2006-12-07 10:35 by GASSNAKE [未注册用户]
还真的是吓一跳

#2楼    回复  引用    

2006-12-07 11:09 by eee [未注册用户]
我在vista上运行的是62.619 看来机器差不多,呵呵

#3楼    回复  引用    

2006-12-07 11:20 by Boolean [未注册用户]
我觉得直接把代码对应翻译成C#可能会有些不公平。

同样的语句,不同的编译器产生的结果是有不同的。

当然,如此大的差距确实也很难解释成编译器的问题……

#4楼    回复  引用    

2006-12-07 11:40 by ddee [未注册用户]
嗯,的确,不能直接转换,
再者本来效率就不是托管的优势

#5楼    回复  引用    

2006-12-07 11:42 by ddee [未注册用户]
还可以在试试生成本地代码后的效率

#6楼    回复  引用    

2006-12-07 11:43 by lymph [未注册用户]
问题是C#的开发效率是C的N倍呢。

#7楼    回复  引用    

2006-12-07 11:44 by CCC[匿名] [未注册用户]
win32\win32.vcproj

文件有些什么内容??

#8楼 [楼主]   回复  引用  查看    

2006-12-07 11:54 by wuChang      
@Boolean
的确很难解释

@ddee 
@lymph
我并没有贬低托管代码和c#的意思,我也喜欢c#的语法和开发效率,转到.net平台以来一直也只和c#。

@CCC[匿名]
这个项目没用,删掉不影响

#9楼    回复  引用  查看    

2006-12-07 12:15 by muki      
我觉得有点好笑!不要见怪
因为我一直以来认为,无论用C#还是C写一些东西都是用来解决一些问题的(除非是一个数学家,才会如你所是,用来计算)否则就会关系到很多方面的,用得最多的估计是数据库方面的了,当你考虑到一个真正的应用的时候,就不是你的那些测试数据了,这个差距不会很大的。

#10楼    回复  引用    

2006-12-07 12:59 by jiangchuandong [未注册用户]
是Release方式生成的吗?

#11楼    回复  引用  查看    

2006-12-07 13:23 by 航天奇侠      

这个代码不太好吧,应该不要涉及IO比较公平。

#12楼    回复  引用    

2006-12-07 13:33 by A.Z[匿名] [未注册用户]
航天奇侠说的对,明显不公平,等会用dotTrace看看那里慢了。

#13楼    回复  引用  查看    

2006-12-07 13:41 by 航天奇侠      
我的速度c++ : Seconds = 5.844000000

配置: AMD 1.8G
内存: 1G
看来双核3G也没什么了不起嘛。

#14楼 [楼主]   回复  引用  查看    

2006-12-07 13:59 by wuChang      
@航天奇侠
单线程下单双核没多大差别。

#15楼    回复  引用  查看    

2006-12-07 14:07 by deerchao      
把Math.Pow()调用改成直接相乘.

C#同样是4秒多.

可见语言对效率的影响是微不足道的,最重要的是对语言的用法,呃...还是不得不承认,看来.net framework库的编写者对某些情况下的优化还是不够.

#16楼    回复  引用    

2006-12-07 14:34 by A.Z[匿名] [未注册用户]
@deerchao

结果相近,我开始质疑Math内由内核实现的方法了。

这段C#中可以做下一下改进,用二维数组的声明方式。而不是交错数组。

#17楼    回复  引用  查看    

2006-12-07 14:36 by freetofly      
根本不公平

只是装箱拆箱就要费好多资源
而且这里根本不用封装成一个类
干嘛非要封装后再实例化再去调用

而且.NET第一次编译肯定慢,编译后再重新运行一次,就会知道了

#18楼    回复  引用    

2006-12-07 14:41 by A.Z[匿名] [未注册用户]
using System;


static class Program
{
static int RAND_MAX = 0x7fff;
static int NPARTS = 1000;
static int NITER = 201;
static int DIMS = 3;
static double pot;
static double distx, disty, distz, dist;
static double[,] r = new double[DIMS, NPARTS];
static int CLOCKS_PER_SEC = 1000;
static Random random = new Random();
static void Main()
{
int i;
int start, stop;
initPositions();
updatePositions();
start = Environment.TickCount;
for (i = 0; i < NITER; i++)
{
pot = 0.0;
computePot();
if (i % 10 == 0) Console.WriteLine("{0}: Potential: {1:##########.###}", i, pot);
updatePositions();
}
stop = Environment.TickCount;
Console.WriteLine("Seconds = {0:##########.#########}", (double)(stop - start) / CLOCKS_PER_SEC);
Console.ReadLine();
}

static void initPositions()
{

for (int i = 0; i < DIMS; i++)
for (int j = 0; j < NPARTS; j++)
r[i,j] = 0.5 + ((double)random.Next(RAND_MAX) / (double)RAND_MAX);
}

static void updatePositions()
{

for (int i = 0; i < DIMS; i++)
for (int j = 0; j < NPARTS; j++)
r[i,j] -= 0.5 + ((double)random.Next(RAND_MAX) / (double)RAND_MAX);
}


static int computePot()
{
for (int i = 0; i < NPARTS; i++)
{
for (int j = 0; j < i - 1; j++)
{
distx = (r[0, j] - r[0, i]) * (r[0, j] - r[0, i]);
disty = (r[1, j] - r[1, i]) * (r[1, j] - r[1, i]);
distz = (r[2, j] - r[2, i]) * (r[2, j] - r[2, i]);

//distx = Math.Pow((r[0,j] - r[0,i]), 2.0);
//disty = Math.Pow((r[1,j] - r[1,i]), 2.0);
//distz = Math.Pow((r[2,j] - r[2,i]), 2.0);
dist = Math.Sqrt(distx + disty + distz);
pot += 1.0 / dist;
}
}
return 0;
}


}

#19楼 [楼主]   回复  引用  查看    

2006-12-07 15:21 by wuChang      
@freetofly
1、这里没涉及到装箱拆箱操作吧?
2、计时器是在创建了对象之后开始计的
3、“而且.NET第一次编译肯定慢,编译后再重新运行一次,就会知道了”?
好像winform没这种说法吧?

#20楼    回复  引用    

2006-12-07 15:21 by A.Z[匿名] [未注册用户]
90%以上的时间集中在computePot()里,大家快想办法优化

#21楼    回复  引用  查看    

2006-12-07 15:25 by lixiong      
非常有趣的问题
今天在公司内部讨论了一把,中间可以学到不少东西的。改天有时间整理出来

#22楼 [楼主]   回复  引用  查看    

2006-12-07 15:50 by wuChang      
将Math.Pow改用相乘后,c#执行时间在8.5s左右,代码如下:
将pow改用乘