[翻译]GDI on Windows Mobile--托管代码和本地代码的性能比较

GDI on Windows Mobile---托管代码和本地代码的性能比较

摘要
在论坛或者邮件组里经常会有朋友提到,要想提高图形应用程序的性能必须要使用本地代码(C/C++)来开发。我们也经常听说本地代码在三维图形的操作上要比托管代码快得多。然而,事实并非完全如此,本文会通过多个平台上的实验数据来探讨托管代码和本地代码在图形操作上的性能问题。
Keywords
GDI, C++, C#, Windows Mobile, .NET Compact Framwork, performance


原文由Windows Embedded MVP Chris Tacke发表在:http://community.opennetcf.com/articles/cf/archive/2008/05/19/native-vs-managed-code-gdi-performance.aspx

本地代码测试
我们先来看看本地代码测试程序。代码很简单,仅仅是为了测试。这段程序绘制了一个矩形位图域并创建了相应的DC(device context),然后重复10000次以下两个操作:一是不断的用灰色填充矩形域,二是不断在矩形域中间绘制绿色的椭圆。这里椭圆的长轴短轴都是在不断变化的,这可以避免显示驱动缓存对我们的测试造成的影响。主要代码如下:

...

screenDC = GetWindowDC(NULL);

bufferDC = CreateCompatibleDC(screenDC);

oldPen = (HPEN)SelectObject(bufferDC, CreatePen(PS_NULL, 0, NULL));

oldBrush = (HBRUSH)SelectObject(bufferDC, CreateSolidBrush(#00ff00));

hBufferBmp = CreateCompatibleBitmap(screenDC, BOX_WIDTH, BOX_HEIGHT);

oldObject = SelectObject(bufferDC, hBufferBmp);

rect.top = 0;

rect.left = 0;

rect.right = BOX_WIDTH;

rect.bottom = BOX_HEIGHT;

for(i = 0 ; i < GDI_ITERATIONS_PER_REPORT ; i++)

{

width += xop;

height+= yop;

if((width >= BOX_WIDTH) || (width <= 0))

xop *= -1;

if((height >= BOX_HEIGHT) || (height <= 0))

yop *= -1;

r += rop;

g += gop;

b += bop;

if((r > 254) || (r< 1))

rop *= -1;

if((g > 254) || (g< 1))

gop *= -1;

if((b> 254) || (b< 1))

bop *= -1;

boxBrush = CreateSolidBrush(RGB(r, g, b));

FillRect(bufferDC, &rect, boxBrush);

DeleteObject(boxBrush);

Ellipse(bufferDC, width, height, BOX_WIDTH - width, BOX_HEIGHT - height);

Ellipse(bufferDC, height, width, BOX_WIDTH - height, BOX_HEIGHT - width);

BitBlt(screenDC, left, top, left + BOX_WIDTH, top + BOX_HEIGHT,

bufferDC, 0, 0, SRCCOPY);

}

...

运行效果如图:

测试结果是,在Windows Mobile 5.0的模拟器上,执行循环的速度为490次每秒(当然这跟你PC机的性能是有关的)。每一次操作包括了填充矩形和绘制两个椭圆的过程。在一台Windows Mobile 5.0 Dell Axim x51(PXA270处理器)的实际设备上,这个程序可以达到250次每秒的循环速度。

Compact Framework下的测试
厄,实际上这是一份和上面的代码很类似的C#代码:

 

...

Bitmap backBuffer = new Bitmap(BOX_WIDTH, BOX_HEIGHT);

Brush boxBrush;Brush ellipseBrush = new SolidBrush(Color.Green);

Graphics screenGraphics = f.CreateGraphics(); // Graphics.FromHdc(screenDC);

Graphics backGraphics = Graphics.FromImage(backBuffer);

for (i = 0; i < GDI_ITERATIONS_PER_REPORT; i++)

{

width += xop;

height += yop;

if ((width >= BOX_WIDTH) || (width <= 0))

xop *= -1;

if ((height >= BOX_HEIGHT) || (height <= 0))

yop *= -1;

r += rop;

g += gop;

b += bop;

if ((r > 254) || (r < 1))

rop *= -1;

if ((g > 254) || (g < 1))

gop *= -1;

if ((b > 254) || (b < 1))

bop *= -1;

boxBrush = new SolidBrush(Color.FromArgb(r, g, b));

backGraphics.FillRectangle(boxBrush, 0, 0, BOX_WIDTH, BOX_HEIGHT);

boxBrush.Dispose();

backGraphics.FillEllipse(ellipseBrush, width, height,

BOX_WIDTH - 2 * width, BOX_HEIGHT - 2 * height);

backGraphics.FillEllipse(ellipseBrush, height, width,

BOX_WIDTH - 2 * height, BOX_HEIGHT - 2 * width);

screenGraphics.DrawImage(backBuffer, 0, 0);

}

这次试验的结果是,在.NET CF 3.5+Windows Mobile 5模拟器的环境下,该程序中循环的速度为425次每秒,这比刚才的本地代码要慢15%,而在同样的Dell Axim设备上,该程序只有210次每秒的表现。比之前的本地代码要慢上19%。

桌面PC机上的测试
通常来说,托管代码和本地代码的效率应该5%左右,充其量有10%的差别,而以上结果(15%~20%)确实有些让人感到意外。考虑到有可能是.NET Compact Framework和我们通常用的桌面版本的Framework有所不同,接下来我们再来做一个桌面PC机上的测试。我们稍稍改动了一下代码,加了几条预编译指令,让之前的代码托管代码能在PC机上运行。结果是托管的程序循环速度为825次每秒,本地代码的程序循环速度则达到了6200次每秒。这比托管的程序快了625%!!

改进后的托管代码
为什么托管代码会如此之慢,设想那些托管方法如Graphics.FillRectangle只是通过P/invoke对Win32API FillRect的简单封装。那何不直接P/invoke来调用本地GDI代码呢,这样可以减少方法调用的次数。
改进后测试结果表明性能有所提高,在模拟器和Axim上分别达到了455和225次每秒,较之前提高了7%。这将托管代码和本地代码的差别缩小了一半。再回到桌面上,测试结果让人更加吃惊,托管代码也达到了每秒6150次循环。和之前的本地代码只差了1%。看来,托管代码并不总是比本地代码慢那么多。

总结
经过在不同设备,不同平台上的试验,证明了托管代码至少在基本的GDI操作方面确实比非托管代码要慢,但并不总是慢那么多。下表列举了一些实验数据:

 


Managed: Framework Calls

Managed: P/Invoke Calls

Native

Native is n% Faster (framework / P/invoke)

Desktop

 

 

 

 

2.66GHz Intel Core2 Duo

890

6150

6200

597% / 1%






Device

 

 

 

 

Windows Mobile 5.0 Emulator

425

455

490

15% / 8%

WinMo 5.0

Dell Axim x51

PXA270

210

225

250

19% / 11%

HTC TyTn*

Samsung 2442A

70

74

75

7% / 1%

HTC P6300*

Samsung 2442

73

76

77

6% / 1%

HTC Athena*

PXA270

118

119

128

9% / 8%

HTC TyTn II*

Qualcomm 7200

155

168

178

15% / 6%

HTC Sedna*

Qualcomm 7200

121

129

131

9% / 2%

HTC Charmer*

OMAP850

134

151

154

15% / 2%

* = Results from testing 1,000 iterations instead of 10,000

既然托管代码或多或少的要比非托管代码慢,那么是不是使用费托管代码就一定优越一些呢?我觉得并非如此,二者各有各的优势,各有各的适应场合。
考虑到托管代码快速开发的特性,日益强大的IDE和工具支持,托管代码无疑是经典企业级应用的不二选择。使用托管代码意味着以更低的成本更少的风险更快地占领市场。你会不喜欢吗?
类似的,对于非托管代码,有些情况,你只能使用它来开发,比如一个以太网卡驱动程序。但是一般的应用是否一定要用费托管代码,这就是值得商榷的了,就像刚刚那个例子,即使非托管代码的确很快,就算它能达到每秒30帧的刷新速度,但是问题是,真的需要那么快么?对人的眼睛来说,也许根本不用那么高的速度。这就是说,不同的应用还是得具体分析。如果你打算做一个模拟飞行的游戏,你的程序大多是跟绘图有关的,那么显然,这时候应当选择使用非托管代码。但是如果你只打算做一个纸牌游戏,或者一个文本编辑器呢?所以,纯粹的性能并不是决定你选择使用哪种方式开发的唯一因素。主要还是要看你怎么分析具体的需求,然后选择一条格式的途径

完整代码在这里下载

黄季冬<fox23>

 

posted on 2008-05-22 03:50  J.D Huang  阅读(3188)  评论(13编辑  收藏  举报