随笔 - 59, 评论 - 580, 引用 - 50

导航

关于


微软提供的免费计数器

山不在高,有仙则名。水不在深,有龙则灵。斯是陋室,唯吾德馨。苔痕上阶绿,草色入帘青。谈笑有鸿儒,往来无白丁。可以调素琴,阅金经。无丝竹之乱耳,无案牍之劳形。南阳诸葛庐,西蜀子云亭。
子曰∶“何陋之有?”


My name is BEN.
This is my master's "doghouse". hehe...

标签

每月存档

最新留言

广告

【第1页/共2页,18条】
首页
前页
1

从一个气压计想到的

从一个气压计想到的事先声明,本文章与技术没有实际关系。不喜欢的跟我讲,我立马从首页撤下来!
不知道大家读过一个笑话没有:
有一个物理学的教授邀请了他的一位朋友——另外一名非常著名的物理学家,来帮忙评判他的一个学生的考卷,其中有一道题是这样的:你现在有一个气压计,如何用这个气压计来获得一栋楼的高度?他的学生是这么回答的:先度量气压计的高度以此作为单位高度,然后度量该楼一级楼梯台阶的高度,输出每一层楼有多少个台阶,一共有多少层楼,然后四者相乘即可得出高度……
这位教授实在觉得不知道怎么评判比较好,一方面觉得比较有创意,另外一方面跟气压方面的物理知识一点关系都没有。他的朋友建议让那个学生再来做一次这道题目,当然,是用完全不同的方法。如果他能够用比较技巧性的,通过复杂数学计算的物理学方法做出来就算他通过。
当那一个学生来到这两位面前,并了解了他们的意图之后,开始苦思冥想。当教授和他的朋友都以为他做不出来的时候,他说了一个令他们目瞪口呆的答案:
嗯,首先,用一根一米长的绳子绑住这个气压计,然后到楼顶上作单摆运动,计下周期T1,然后到楼底下做同样的实验,记录周期T2。根据单摆的公式,我们已知周期T,单摆摆长L,就可以求出重力加速度g。再通过万有引力定律,已知g,地球质量M,就可以求出离地心的半径R。T1算出R1,T2算出R2,R1-R2就是楼的高度。
看到两个人愣在那边,这个学生得意地说:我刚才想了那么久并不是想不出来,而是想出来太多的东西了,要仔细考虑一下那一个更符合你们的要求。当然,我知道你们想让我通过气压方面的知识来得到这个题目的答案,不过那样太没有意思了,谁都知道答案是什么样的。
教授就问了,那你还有什么样的答案呢?学生说:比如我可以用一根绳子绑着气压计然后从楼顶吊到楼底下,或者用气压计来做一个自由落体运动,再或者测出气压计的高度和它太阳下的影子高度,再测出大楼的投影长度。实在不行了,我可以把这个气压计送给楼底下看门的老头子,让他告诉我大楼的高度。

这是一个笑话,所以从这里想出来的大约都是胡诌,就随意胡诌一下吧:
1、根据气压方面的知识来计算大楼的高度,也许就是最为传统的软件工程方法,CMM等之类的东西。

2、如果真的以气压计作为单位测量台阶高度来计算大楼高度,也许跟手工作坊大生产比较象。

3、要是你一个人来做实验,比如用绳子吊着气压计到大楼底,那么也许会因为不小心碰到二楼阳台的晾衣杆,以为到大楼的了,结果数据出错。所以这个要两个人来做,一个在楼顶吊着,一个在楼底下看着。这么做的好处很多:除了可以防止上面那种人为失误之外,如果在楼顶上吊气压计的兄弟胳膊实在是酸了,可以让楼底下的哥儿们上来替换一下。当然,楼下的兄弟通过观察绳子的情况,也可以知道楼顶上的兄弟是否睡着了。看出来了吧,这就是结对编程的好处。

4、自由落体运动的方案看起来不错,如果一切都是比较接近理想状态的话。可是如果正好有一个往上吹或者往下吹的风呢?所以测试之前最好在大楼中间的地方来一个监测,看看到达正中点的时间是否正好是总时间的二分之根号二,如果不是的话可以断言测试失败,至少这个方案有问题。这个时候我们得想办法把风去掉(比如打电话给上帝),不过在我们真正遇到这个问题之前我们可以假设事情就是理想的。不过这么做最好有一个伙伴跟你一块来做,当然了,TDD最好是结对的。

5、用气压计收买看门老头确实是好办法,完全不需要动脑筋,我喜欢。其实这个我们更应该动这样的脑筋,其实每一个国家安全局以及间谍机构里面的人都是这么干的,而且通常很漂亮。什么?计算机界里面有没有?有,黑客啊!没有听说过后门吗?一般的后门都是小打小闹的,据传闻某国政府在某个操作系统里面可能安有后门,如果这是真的,那肯定是收买老头子方案的超大型翻版。所以不要相信任何关于绝对安全的鬼话,那根本是不现实的,除非全人类都没有任何欲望。

6、世界上还真有人用单摆的方法来得到大楼高度的吗?天啊!那多么烦人啊!如果让我来做这个实验我肯定会疯掉的!想一想为了让大楼顶端和大楼底端得到的T值的精确度足够,我们得测量多少个周期的单摆运动啊?估计我睡着了都不会得到满意的结果的。(呵呵!哈哈!)笑?谁在笑?还真别笑!我们以前就是这么干的,不要说直接写机器代码的那个黑暗时代了,就是C语言的时代我们也不见得好多少。那个时候所有人都在不停的重复的颠来倒去的翻来覆去的做一些实际上完全一样的工作,而且所有人做的东西几乎都是重复的,现在想想我宁愿去睡觉。我们就是这么走过来的,没有这些工作我们也没有如今的舒服。话又说回来了,人类确实是通过这个方法来得到有关重力加速度的,虽然这是几百年前的事情了。

7、用三角函数求大楼高度(就是那个测投影的方案),是一种以小见大的方式。当然了,这种方式需要评判答案的人有一定的想象力——如果气压计的高度和它影子的长度成一个比例的话,这栋大楼也应该一样。在你让对方接受这种方式计算出来的答案以前,我们必须先让对方想象一下上面所说的场景,并且接受这么一个其实无法直接证实的事情(就大楼和气压表两者而言)。我们做软件的时候也经常需要这样,首先弄一个原型出来让客户看看,如果客户接受的话就需要一个舌头能够打活结的人来说服客户:如果我们的文档一步一步按计划出来,那么最终产品就是你们想要的东西,跟你们看到的那个原型意思是一个样的。

8、如果你对于上面的一些方案感到可笑,是因为你觉得从实际角度看,每个地方都存在误差,甚至你觉得你无法通过这些实验来得到准确的大楼高度,那么也许你是一个程序员;
如果你对于上面的一些方案感到可笑,是因为你觉得过程方面完全被忽略了,比如缺乏每一步实验步骤的描述,那么也许你是一个设计师;
如果你对于上面的一些方案感到可笑,是因为你觉得这些方案本身的大方向就错了,比如所用到的物理定律有问题,那么也许你是一个架构师;
如果你对于上面的一些方案感到可笑,是因为你觉得这些方案根本没有考虑到一些最基本的问题,比如说他不赚钱,那么也许你是股东,搞不好就是董事长;
如果你对于上面的一些方案感到可笑,那么对不起,得分两种情况:
a、我就觉得该这样做,那个谁,过来给我把它做出来,那么也许你是CEO。
b、不知道你在说什么,那个谁,把这篇帖子给我删了,那么也许你是LHS(幽默感缺乏综合症)。

9、其实我最关心的是,有没有更加恶搞的解题方法? 不过必须和气压计有关,并且从方法上是不同的。比如说用气压计收买楼顶上的清洁工,那就没有意义了。
10、这篇文章昨天就写好了,没有Post出来,是因为受到情绪的影响。今天Post出来没有关系吧?

posted on 2004-10-31 23:35:00 by sumtec  评论(22) 阅读(4625)

静下心来看看周围都发生了些什么……

最近一直在忙于性能优化方面的调试工作,所以一直在研究这方面的问题。其实那些研究都是不着边际的事情,但是作为自己的知识储备,也算是一件有意义的事情。目前的工作算是“OK”了,不过我自己却不是很满意。在整个过程当中发现了不少的问题,首先是XML的速度,后来是FileStream的问题,接下来是Thread的互相影响问题。天啊!我真想喊出来,才解决了数据读取的问题又出现了Thread的问题——Ce里面的背景线程一旦运行,就会严重阻塞前台的线程。有没有谁知道CE里面(包括.NET CF里面)的线程机制是什么样的?是轮换式还是抢占式?优先级有没有作用?.NET CF里面的线程简直就是弱的不堪设想,除了能够让他运行起来,加上调一个优先级,就没有更多的功能了。不能够强制中断,不能够Suspend/Resume,也读不出来他的运行状态……真是要命,如果没有人能够给我答案的话只好自己慢慢找了。还好,目前的性能问题还可以含糊过去,比如让背景线程推迟30分钟之后再运行,但愿这样JGTM不会察觉出来他的存在。

很久没有仔细去看看外面周围都发生了些什么了,今天下午真的要好好的看看一些很久没有看到的Blog了。比如Neil CowburnAlex Yakhnin的,发现里面又有好多新的东西了,真是很喜欢他们的Blog,虽然更新速度并不快,大师每篇都值得一看。不像我这个Blog,充满了不可阅读的或者不值得阅读的废话。哎,废话少说,还是来看看最近他们都说了些什么吧:

(所有内容未经我亲自验证,仅代表原作者观点)
Neil Cowburn说:
Ultra-Simple Application Logging
原来在.NET CF上面开发,如果想要Log一些东西是这么简单的。大概意思就是通过P/Invoke改变标准输入输出流的方向,然后就可以直接用Console里面的方法来输出字符串到一个特定的文件里面了,没错,就那么简单。

Pocket PC Magazine Best Software Awards 2004 -- WE WON!
opennetcf.org获得了PPC Mag 的2004年度最佳软件大奖。嗯,应该指的是里面的SmartDevice Framework吧?说句老实后,这个东西确实是一件神奇的东西,它能够快速的解决许多你认为非常头疼的事情(也许待会儿我要到那里面找找有没有增强型的Thread类),尽管里面有很多地方有Bug。Bug不是问题,因为这个东西完全免费和开源,只要你自己会调试,水平足够的人都可以自己去截取代码和修改。这个东西最大的问题是:太大了,也还不够大。一方面我希望他能够小巧一点,因为这么大的东西会影响加载速度和占用内存的空间,而我自己又想偷懒,不想老是自己截取代码来避免这个缺点。另外一方面我却希望他能够解决更多更多的问题,比如COM交互,或者DX之类的东西,哈哈,这个就真是勉为其难了。总结一下,SD Framework还是很好D……

Craig on .NET Compact Framework 2.0
Craig是谁?我不知道,他最近在一个内部培训的时候进行即席演讲,并录了下来。他说了一些他觉得.NET CF 2.0里面最让他喜欢的东西,不听的话你真是错过了很精彩的东西!比如说,.NET CF 2.0 里面将会有Generics,有Iterators,有Com Interop,有Sql CE 3.0,有DX,有SMS Queue,有直接操作Inbox的能力,有Data Encryption,有Location Based API,有Camera API,有……听得我是极度兴奋(还有性能改善忘了列出来了)。不过这个只是内部培训的时候录下来的,估计是为了训练演讲能力,提高培训能力的。(上一次有幸参加了MS给MVP做的类似培训,感觉棒极了。哈达不一定赞同,他水平太高了……)毕竟不是官方的东西,可信程度还是不高,不过也比我自己瞎吹要好多了。嗯,别的东西我不要,就要Generics、Sql ce.NET、DX,还有性能提升,就这些了,我还是不太贪心的。
这里还有另外一个录像,我还没有仔细看,大家也可以看看,是关于ASP.NET 2.0的(显著优点的)。
这个Post实际上是来自Carter Maslan的。

CFNunitBridge GotDotNet Workspace
嗯?CF也上NUnit了?嘿嘿……怀疑ing

Who would like to fly Virgin Galactic?
FT,还有可以飞到室女座星云的方法?Neil想,我可不想。那么大的星云(跟银河系一般大)要找个落脚点可不容易,目前人类在300光年的范围以内都尚未找到和地球环境相近的星球,更何况要在一个直径是万光年的星系里面去找,真是茫茫大海呀……

Meet the Visual Studio for Devices Team
关于Visual Studio for Devices Team里面的录像

Using POOM with .Net Compact Framework Whidbey Beta1
前面说了.NET CF 2.0 可以POOM,这里有一个例子可以去看看。

Show me your desktop!
看看人家多好,最小的也使17寸的液晶……羡慕有两个显示器,以后有钱了一定要买四个显示器,一个打开VS2kX玩玩,一个运行自己写出来的程序玩玩,一个摆着玩玩,还有一个摔着玩玩……

Catch-up Round-up
都是一些连接,值得一看。

Visual Studio For Devices Team Blog
VSD Team的Blog,如果你对如何改进VS有什么想法,可以给他们说说,也许会管用。


Alex Yakhnin 说:
Accessing Memory Mapped Files from CF
这个是关于在CF上面使用FileMapping技术的Post,值得研究。

Nota bene...
这个……这个……

Using cordbg for .NetCF
其实也是转自别人的Blog,好像Neil也转了。值得研究,这样我们也许有机会知道.NET CF里面到底搞了些什么小动作。

Great info on Managed App Startup and Retargeting
有点深度的内容,具体是什么还没有仔细看。好像说的是.NET CF里面EE启动Managed App的机制,不知道有没有看错。

XPathReader on .NetCF
在.NET CF上面XPath?上次仅仅是XML已经让我非常头疼了,还要XPath呢!不过文章里面说,有人提到现在的XML解析方法有问题(没有提到到底是DOM还是XmlReader),而值得庆幸的是Dare Obasanjo,一个MS的PM,发表了一个 XPathReader,据说除了提供了一些XPath的能力之外,还有效率方面的提高?该死的Alex说:
The next step?will be to test a comparison performance of this library vs XmlDocument in CF.
但是却没有写出来,难道非要我也研究一遍?待会儿给他email以下,如果效率有很大提高,那么这些天的工作算是白忙活了……(估计XML的解析效率很难提高的,毕竟是解析……不过话还是不要说死为好。)

Sending mail with attachments from .NetCF.
如果有人对于在.NET CF里面如何发送带有附件的邮件有困难的话,可以看看这个Post,也许对你有帮助。

OK,今天的大转载集合到此,还要看看Thread的问题呢……

posted on 2004-10-29 17:06:00 by sumtec  评论(16) 阅读(6612)

原来是 MarshalByRefObject 搞的鬼……

一个强制转换快还是ToString快,搞得我晕头转向的。以为OK了出结果了,却出现奇怪的情况,还好,总算是山穷水尽(复,李敖说的)疑无路,柳暗花明又一村。

一开始,我想大家都跟我猜想的一样,认为ToString比较慢,而且还有危险。结果一开始我的试验结果跟我的猜测比较吻合,ToString比强制转换要慢一个级别,不同的机子上面会有不同的结果,在10:1到6:1之间。但是后来有人说在Console下面不一样,问题就变得让我困惑起来了。事实上这个跟GDI,跟Form本身,跟DoEvents函数统统没有关系,因为我没有动用到无关的东西,也没有把非必要的部分给测量出来。更让我感到有趣的是,即使是在Console下面,只要自己写的类继承自Form,测试结果就会变慢。如果取消继承关系,两者的速度就会相差无几。认为Form的初始化会比较复杂,会动用Native资源,会在释放对象的时候有效率问题,这些都是错的,因为我很明显没有不断的进行构造和释放,也没有把这部分的时间计算在内。那么到底是什么问题呢?昨天没有时间进行试验,今天一大早起来就是为了进行测试。
其实我一开始就在汇编底下注意到那个ToString之后的调用,总觉得很奇怪:为什么不用强制转换之后的那一个赋值调用呢?(就是下面这个)
00000112  mov         esi,eax
00000114  push        esi 
00000115  mov         ecx,edi
00000117  mov         edx,995358h
0000011c  call        71E7021D


所以我从一开始就怀疑性能损失在这个地方,但是因为暂时没有办法调式到内部(MS的调试器……),所以我只能够进行一些猜想:是不是跟测试所在的实例有关系呢?
用一句代码来举一个例:
s = o.ToString();
本来觉得性能应该仅仅取决于调用了什么方法(ToString还是强制转换),以及所调用对象本身的类型(跟o有关)。比如考虑到ToString是虚函数,也许会有关系。但是后来看到这一系列奇怪的结果之后,不得不怀疑这个测试还跟this有关系。

首先我就猜测,是不是跟继承的深度有关系?如果是这个问题就太荒谬了,但是Form类和自己写的类之间的差别之一就是继承深度,还是有嫌疑的理由。为了验证这个问题,写了两个类:

public class TestBase
{
   
protected const int testRound = 100000000;
   
private string sTestBase;
   
public void Test();
}
public class First : TestBase
{
   
private string sFirst;
   
public void FirstTest();
}

构造相应的实例并本别调用Test和FirstTest,函数内部的代码大致如下:

object o = "Hello! this is a test!";
long dt;
int i;

dt 
= DateTime.Now.Ticks;
for (i = 0; i < testRound; i++)
{
    sFirst 
= (string) o;
}

dt 
= DateTime.Now.Ticks - dt;
Console.WriteLine(dt.ToString(
"N"));

dt 
= DateTime.Now.Ticks;
for (i = 0; i < testRound; i++)
{
    sFirst 
= o.ToString();
}

dt 
= DateTime.Now.Ticks - dt;
Console.WriteLine(dt.ToString(
"N"));


这里写成两个函数主要是避免虚函数可能带来的影响,尽管不太可能,还是不要节外生枝比较好。测试的结果表明几乎没有任何差别,我还是不死心,把继承深度从1层扩大到3层,测试结果还是一样,因此排除了继承深度带来的影响。
然后我只好怀疑是否因为对象内部实现了某些接口造成性能上面的差异,因为Form内部确是有实现不少的接口,尽管也挺荒谬的。于是我又写了一个类:

public class Second : First, ICloneable, IComparable, IFormattable, IServiceProvider, IDisposable, IConvertible, ICustomFormatter, IAsyncResult, IAppDomainSetup, IDbCommand, IFeatureSupport, IFileReaderService, IContainerControl, IDataAdapter, IButtonControl, ICommandExecutor, IDbDataParameter, IColumnMapping, IMessageFilter, ITableMapping, IWin32Window, IWindowTarget, ITableMappingCollection
{
    
private string sSecond;
    
public void SecondTest();
    
// 接口实现就不贴上来了
}

不要怪我狠心,我开始的时候添加了三个接口,观测到几十毫秒的差异,于是就一路添加上去。最后发现差别不会超过100毫秒,并且很可能是误差造成的。现在添加了二十多个接口都没有问题,那么到底问题在哪里呢?
再看就剩下Form的父类没有研究了,难道还真的是构造函数里面有些损耗性能的东西?那就太郁闷了,也非常荒谬。没办法,猜测不解决问题,还是来测试一下吧。看看怎么个测试法呢?想了想觉得还是从最上面的父类开始找问题,也就是说设计一个从MarshalByRefObject派生的类:

public class Fourth : MarshalByRefObject
{
    
private string sFourth;
    
public void FourthTest();
}

不测不知道,一测下一跳,问题就是由MarshalByRefObject引起的,试验结果跟前面的10:1非常吻合。后来为了挖掘更多的数据,我做了更多的相关测试,例如测试函数是实例上的,但是sFourth则是静态的结果会怎么样?如果函数是静态的,但是全局变量是实例上的呢?两者都是静态的呢?所以还写了更多的函数来测试:

public void TestStatic();
static public void StaticTest(Fourth fourth);
static public void StaticTestStatic(Fourth fourth);

这个测试我在.NET 1.1(DEBUG)、.NET 2.0(DEBUG/RELEASE)下面分别测试,测试结果数据如下:

说明 .NET 1.1 DEBUG .NET 2.0 DEBUG .NET 2.0 RELEASE
TestBase : object
string = (string) o
string = o.ToString()

First : TestBase
string = (string) o
string = o.ToString()

Second : First, Interfaces
string = (string) o
string = o.ToString()

Third : First
string = (string) o
string = o.ToString()

Fourth : MarshalByRef
string = (string) o
string = o.ToString()
string = MyClass.GetString()
string = this.GetString()
string = Fourth.GetString()

Fourth
static string sStatic;
public void TestStatic()
{
sStatic = this.someThing
}

Fourth
private string sFourth;
static public void StaticTest(obj)
{
sFourth = obj.someThing
}

Fourth
static string sStatic;
static public void StaticTestStatic()
{
sStatic = Fourth.SomeThing
}

---End of Test---
TestBase
10,625,000.00
11,250,000.00

First
10,468,750.00
11,093,750.00

Second
10,781,250.00
10,937,500.00

Third
10,468,750.00
11,093,750.00

Fourth
10,625,000.00
109,062,500.00
110,937,500.00
120,781,250.00
109,062,500.00

Fourth
10,468,750.00
11,093,750.00
11,093,750.00
24,843,750.00
10,156,250.00

Fourth
106,718,750.00
108,125,000.00
109,687,500.00
119,218,750.00
106,875,000.00

Fourth
10,312,500.00
11,093,750.00
11,093,750.00
24,843,750.00
10,156,250.00

DONE
TestBase
10,781,250.00
15,312,500.00

First
10,312,500.00
15,781,250.00

Second
11,250,000.00
15,468,750.00

Third
10,312,500.00
14,531,250.00

Fourth
9,687,500.00
125,625,000.00
120,312,500.00
131,093,750.00
118,281,250.00

Fourth
8,593,750.00
12,656,250.00
12,968,750.00
27,343,750.00
12,187,500.00

Fourth
117,343,750.00
118,906,250.00
119,687,500.00
131,406,250.00
121,562,500.00

Fourth
8,593,750.00
12,656,250.00
12,968,750.00
28,125,000.00
12,031,250.00

DONE
TestBase
8,125,000.00
12,968,750.00

First
7,656,250.00
12,968,750.00

Second
8,281,250.00
15,468,750.00

Third
7,187,500.00
13,437,500.00

Fourth
7,812,500.00
12,812,500.00
5,156,250.00
25,625,000.00
6,093,750.00

Fourth
12,812,500.00
15,156,250.00
6,093,750.00
24,687,500.00
6,250,000.00

Fourth
113,750,000.00
115,156,250.00
109,062,500.00
126,718,750.00
109,062,500.00

Fourth
6,875,000.00
12,031,250.00
5,156,250.00
22,968,750.00
5,156,250.00

DONE

(先给大家说一下,大家可以注意一下.NET 2.0 RELEASE的数据,相比较起来是一个比较奇特的数据。)

我一开始万万没有想到,a = b 这样的简单赋值语句还会跟a这个变量声明的位置有关系(都是全局变量的情况下),或者说跟a所在的类有关系。以为不就是把b赋给a嘛,汇编里面一句mov或者lea就搞定了,如何获得b才是影响性能的关键。结果发现.NET下面并不是这么简单的事情,至少会有MarshalByRefObject这个特例。以前也听说过MarshalByRefObject会非常的影响性能,但是文章里面没有提及,自己也没有考究过。心里面想,这个MarshalByRefObject跟我有什么关系?现在想想好象还是有一点点关系的,比如你在Form上面有一个全局变量(包括你放在窗口上面的所有控件都是本地变量),你对他进行赋值就会有性能损耗。然而值得庆幸的是,我们很多时候都不会对一个全局变量不停的进行复制操作,比如我们总不可能对一个Button myButton这个全局变量循环赋值100000000次吧?顶多我们可能对Hashtable ht这个全局变量不停的添加内容,还好Hashtable等集合类型都不是从MarshalByRefObject里面继承出来的,如果是那样的话真是要疯掉了……

这个发现也许有用,也许没有用,至少在VS2k5里面已经没有什么太大意义了。但是在.NET CF里面就不好说了,虽然我从直观上认为不可能做出太多优化,并且那里面的MarshalByRefObject里面没有任何内容,不知道.NET CF还会不会对其进行特殊对待呢?

posted on 2004-10-29 13:48:00 by sumtec  评论(20) 阅读(7240)

一切都不能够想当然D

刚刚发完上一篇Post,在cnblogs上面msolap就提出了Console下面结果相反的情况,继而有人用实验证实了这一结果。我立刻稍微实验了一下,发现果然如此,更进一步的发现了一些不可思议的事情。

事实上我在另一台机器上面做了一个完全相同的Console实验(一台普通的P4 1.8G 768MB Ram),在VS2k3里面的Debug模式就得到了反转的结果——ToString比强制转换要快,虽然快的并不多,大概也就几十毫秒的时间。经过相当长的时间的困惑之后(真XX的不可思议),终于发现了问题的关键所在(可能而已,不敢确定,仍需实验)。

在这里给所有愿意自己亲手做实验的朋友们一个提示:
首先不要用static的方式,请在一个实例里面进行实验,例如:
private class Tester
{
            
private string testString;
            
private const int testCount = 100000000;
            
public void TestConsole()
            
{
                  
//  test it!
            }

}

先别急,光是这样的话,还是跟原来一样的,我们接着改造:请把Tester改造成从Form继承!没错,就这么做,看看结果如何?

做完了实验请你说说你有什么想法?我是有挺多的想法的,不过现在没有时间,改天再聊。

posted on 2004-10-28 18:24:00 by sumtec  评论(16) 阅读(4996)

两种方式谁快谁慢——我的实践AND发现了与此无关的意外惊喜

首先先说一句不好意思,昨天的Post里面忘了给大家介绍一个基本的前提:那个object o 里面的数据我可以确保它是string类型,也只可能是string类型。这一个研究并不是完全没有意义的,虽然在我实验之前确实也认为应该是强制转换的速度快,但是没有实际的比较问题还是比较难以说明的。也许有人就会问了,你凭什么认为object o里面的就是string呢?不好意思,这是我限定的条件,并且是有实际被应用情形的:
考虑现在我有一个private的ArrayList用于保存用户在TextBox里面输入的字符串,很明显TextBox.Text里面就是返回一个string类型的对象,并且也不可能是null(或者说我通过在初始化的时候给它赋一个string.Empty来避免出现null的情况)。现在我需要将这些保存在那个ArrayList里面的字符串通过BinaryWriter保存到一个自定义格式的二进制文件里面,当然也可能有其他的计算需要我把ArrayList里面的字符串数据进行计算或者处理,这里为了简便随便说了一个也许不是很完整也不是很合理的的例子,但是我相信大家应该明白我的意思了。Hmm,也许还需要另外一个限定,就是没有Generic的支持,比如说这个东西需要在.NET Compact Framework里面实现,而.NET CF即使是到了2.0的版本也不会有Generic的支持。

但是上一次Ninputer说了:
假如o里面原来就是String类型,那么循环100000000的结果是
(string)o 为610ms
o.ToString() 为670ms
这种差距几乎没有意义。

假如o里面是除string之外的其他类型,那么o根本不能直接cast成String。


而同时也有人说,强制转换比ToString要快。那么实际情况是如何呢?请大家先来看看我的代码:
        private string testString;
        
private const int testRound = 100000000;
        
private void button1_Click(object sender, System.EventArgs e)
        
{
            
object o = "Hello! this is a test!";
            
int i;
            
long dt;

            dt 
= DateTime.Now.Ticks;
            
for (i = 0; i < testRound; i++)
            
{
                testString 
= (string)o;
            }

            dt 
= DateTime.Now.Ticks - dt;
            textBox1.Text 
= dt.ToString("#,###,###,###");
            Application.DoEvents();

            dt 
= DateTime.Now.Ticks;
            
for (i = 0; i < testRound; i++)
            
{
                testString 
= o.ToString();
            }

            dt 
= DateTime.Now.Ticks - dt;
            textBox2.Text 
= dt.ToString("#,###,###,###");
            Application.DoEvents();
        }


这个实验我分别在VS2k3和VS2k5里面实验过,并且分别用Debug和Release两种模式去测试,测试结果如下:
DEBUG: (强制转换:ToString)
@2k3
11,406,250 : 123,125,000

@2k5
10,468,750 : 123,906,250


RELEASE:
@2k3
12,500,000 : 118,593,750

@2k5
7,031,250  : 13,906,250


大家首先会发现如下结论:
1、强制转换的速度在0.7秒到1.3秒之间,而ToString则基本上处于10秒以上的级别(有特例)。所以我极度怀疑Ninputer的测试结果是错误的,因为他的两个结果都是在0.6秒左右,当然还有一个特例跟Ninputer的数据相对接近,但是请继续看下面的结论。

2、这里有一个唯一的特例,就是VS2k5的Release模式,大家可以看到ToString的速度从10秒级别急剧减少为1秒级别,这个就是我题目所说的意外惊喜。但是即使是这样,ToString仍然比强制转换慢了50%,同时该数据还是P4HT 2.8GHz 1G Ram的机器上面测得,如果要ToString达到Ninputer所说的数据,我估计得要P4HT 4GHz以上的CPU,或者P4HT双CPU才有机会。还有,Ninputer的数据仅仅相差10%左右,这个跟我的实测数据完全不吻合。因此我估计Ninputer的测试很可能是因为代码不合理,被JIT短路了(优化了),如果将转换结果保存在全局变量里面,也许就不是这个结果了。
      关于为什么VS2k5的ToString在Release模式下面会有这么大的性能提升,我想还需要另外做实验去证明,现在还不好胡乱发言(虽然已经有自己的想法了)。

3、大家如果更加仔细的去看强制转换的数据,就会发现VS2k5无论在哪一种情况下面,都会比VS2k3的数据要快那么一点点,大约10%到30%不等。关于这个我可以马上给大家一个解释,这个实际上是.NET Framework 1.x和2.0之间的问题,但是这需要观察JIT之后的Native Asm,难度有点高。请看片断:

.NET Framework 1.1:
                testString = (string)o;
00000063  mov         edx,dword ptr [ebp-0Ch]
00000066  mov         ecx,79BF9AF8h
0000006b  call        71E608BC
00000070  lea         edx,[ebx+000000ECh]
00000076  call        7204C590

.NET Framework 2.0:
                testString = (string)o;
0000007c  test        ebx,ebx
0000007e  je          0000009A
00000080  cmp         dword ptr [ebx],788ED668h
00000086  jne         0000008C
00000088  mov         eax,ebx
0000008a  jmp         00000098
0000008c  mov         edx,ebx
0000008e  mov         ecx,788ED668h
00000093  call        75621D76

00000098  jmp         0000009C
0000009a  mov         eax,ebx
0000009c  mov         esi,dword ptr [ebp-40h]
0000009f  lea         edx,[esi+00000104h]
000000a5  call        7561BBB0


在这里大家可以清楚地看到,.NET Framework 1.1 仅仅是简单的将强制转换交给一个函数来处理(红色部分),但是2.0则首先进行了一些简单的处理,例如先判断是否是null(test ebx, ebx),以及判断o是否就是恰好string类型而不是string的子类型(cmp dword ptr [ebx], 788ED668h)。当然了,string是一个sealed的类型所以明显不可能有子类型,如果我要求强制转换成Form呢?如果这两项都失败了,才会和1.0里面一样调用一个函数来判断。所调用的函数实际上是将ebx所引用的对象里面的类型向上查找其父类型,看看有没有string类型(788ED668h),可以预见这是一个相对较为耗时的操作。而在我们实际的应用里面,如果进行强制转换通常都是直接转化成他的实际类型的,因此这一小段代码能够较为明显的提高系统的性能。不过这里也可以看到,优化过程还是做的不够,应该将86到8a这三句简化成一句je 0000009A。

我们再来看看ToString的情况。由于ToString是一个virtual的函数,被string类型override了,因此必然需要查v-table来进行正确调用。通过查看.NET Framework 1.1 所JIT出来的代码,可以清楚地看到这一点:

                testString = o.ToString();
00000108  mov         edi,ebx
0000010a  mov         ecx,dword ptr [ebp-0Ch]
0000010d  mov         eax,dword ptr [ecx]
0000010f  call        dword ptr [eax+28h]
00000112  mov         esi,eax
00000114  push        esi 
00000115  mov         ecx,edi
00000117  mov         edx,995358h
0000011c  call        71E7021D



这里ebx指向object o的类型描述,ebp - 0Ch指向object o对象本身(其实这里的负偏移12个字节就是类型描述,两者实际上是完全紧挨着的),其第一个dword就是string类型本身的数据(不是实例的数据)。我并不是非常清楚里面的一些实现,通过观察,估计可能里面包括了本类型的一些描述,父类型和接口的描述,里面偏移0x28应该就是vtable的实质内容(估计),其第一个就是指向ToString实际函数地址的指针。后面一个调用估计应该是一个赋值调用,和ToString调用可能没有太大的关系。为了证实我的想法,我甚至跟踪到第一个调用内部,发现确实是到了string.ToString()里面(大家可以看Disassembly页的Address下拉框,里面现实的是System.String.ToString),其代码如下:
00000000  push        esi 
00000001  mov         esi,ecx
00000003  mov         eax,esi
00000005  pop         esi 
00000006  ret
 

分析到这里,我仍然不能够确定为什么ToString比强制转换性能相差10倍的原因。如果非要我估计一下,那么我会这么比较:
强制转换一共经历了:两个“比较+条件转移”,一个寄存器间赋值,一个索引器加偏移到寄存器赋值,一个普通调用(内部经历过程已经在前面分解了),一个赋值调用(内部无法分析)。
ToString一共经历了:5个寄存器间赋值,一个常数到寄存器赋值,两个间接(加偏移)地址到寄存器赋值,一个寄存器压栈,一个寄存器加偏移量间接地址调用(内部已经在前面分解了),以及一个赋值调用(内部无法分析)。

除去无法分析的部分,ToString比强制转换多出将近一倍的汇编指令,并且还包括比较耗时的间接地址赋值调用指令。光是这个差别,估计就应该有至少150%的性能损失。但是现在性能相差接近10倍,难道是仅仅因为那几句间接寻址的指令造成的?那就有点不可思议了。其实我有点怀疑ToString后面第二个调用内部有一些损失性能的指令(也许是多余的指令),如果我们看看.NET 2.0 Release版本的数据,也许是应该这么解释吧?有关这个问题需要做更多的研究……

嗯,今天的Post的正式内容到此结束。PS一下:
1、twodays在cnblogs里面的Post里面回复说:
“o.ToString() 实际上是调用了从object继承出来的ToString()虚函数,这一个虚函数在string类型里面被重写,代码如下:”

不对,因为你的o这会儿本来类型就是object,而不是string,所以他调用的实际上应该是object的ToString方法里面的内容,经过Reflector查看,里面的内容是:
return this.GetType().FullName;


这里我可以明确地告诉你,你错了(包括后面表示赞同的“小新0574”),至于为什么我会另外在一篇Post里面给出解释,因为这两个问题的难度级别相差太大了,在这里写也许很多朋友已经看得迷迷糊糊的了,继续看下去效果不好。

2、我记得有人在上一个Post里面问过as操作性能如何,我也做过试验了,性能没有差别,至少用肉眼观察数据结果是看不出来的。也许在Disassembly层会有细微差别,但是我没有兴趣再研究下去了。

3、有人说自己写的类里面ToString()可能会抛出Exception的问题,其实我是这么认为的。如果你在代码里面抛出Exception的话,那应该是一件相当遗憾的事情。因为ToString()没有任何参数,不可能因为参数问题引起异常。同时ToString函数的含义是,安全的返回关于该实例的信息,因此从感情上来说就不应该出现Exception的情况。很多时候我们就是利用ToString的一些特性来完成一些简单的事情,例如在ListBox里面加入一个不是String的对象,但显示的是用户能够理解的信息,这样用户方便选择,我们则方便编写代码。如果你的类会在ToString里面抛出异常,那么整个程序就会因此而不稳定。总之我认为在ToString() 里面抛出异常并不是这个函数存在的本意。

posted on 2004-10-28 14:32:00 by sumtec  评论(18) 阅读(4149)

两种方式谁快谁慢?

假设我有一个object类型的变量o,需要转化成string类型的变量s。那么请问下列方式当中的那一个方案更加有效率:

1、

= (string) o;

2、
= o.ToString();

相关提示:
1、(string) 强制转换对应一句IL语句castclass,在IL中大概表现为:
      L_0019: ldloc.0
      L_001a: castclass string
      L_001f: stfld string WindowsApplication1.Form1::testString

这样的形势,注意强制转换只增加L_001a这一句IL语句。

2、o.ToString() 实际上是调用了从object继承出来的ToString()虚函数,这一个虚函数在string类型里面被重写,代码如下:
public override string ToString()
{
      return this;
}
如果效率有差别,差别是多大呢?答案在于实践当中。

posted on 2004-10-27 16:37:00 by sumtec  评论(12) 阅读(5579)

不明白为什么VS2k5编译for的时候……

今天在做一个实验,突然发现一个不寻常的地方:VS2k5 C# 在编译for循环的时候,IL代码跟以前的方式不一样:

      L_0029: ldloc.1
      L_002a: ldc.i4 100000000
      L_002f: clt
      L_0031: stloc.s flag1
      L_0033: ldloc.s flag1
      L_0035: brtrue.s L_0018

这个for循环的C#语句大致为
for (num1 = 0; num1 < 100000000; num1++)

上面IL里面所显示的正是比较num1是否小于100000000的片断,如果在VS2k3里面,则应该编译成:
      L_0029: ldloc.1
      L_002a: ldc.i4 100000000
      L_002f: blt.s L_0018

仔细再研究一下,发现这个变化只在Debug模式里面出现,也就是说这个变化并不是为了效率考虑的。但是原来的方式也能够比较容易的进行调试跟踪,实在搞不清楚为什么要用clt的方式。更加奇怪的是,在clt比较得出结果之后,还要弦弹出来保存到一个flag1的本地变量里面,然后再重新压栈。想了想,可能还是为了“更”方便的进行调适吧:比如说在IL级别进行调试的时候,可能看堆栈是不方便的,同时看两个变量的关系也是让人眼花的(n位数字比较,光是对齐数字就头痛了)。通过这样的IL变化,我们可以简单的看看flag1到底是什么结果。如果发现不对了,还可以在跳转之前把当前执行语句移到其他地方,或者修改转移条件,甚至不需要修改循环变量。这些在以前估计是不可能的,因为blt直接就判断并跳走了,就算可以修改转移条件,也必然影响循环变量i。如果考虑的更深入一点,我们可以简单的修改一条IL指令,使得flag恒为true或者false,强制控制流按照我们希望的方式去走,但是又不影响所有源代码里面的变量,这样子也许能够轻易的进行全覆盖的单元测试。不过这么强的能力目前好像还没有看到C#里面有相应的操作项,所以一切都只是猜测。真正的原因也许需要查找更多的资料,或者问问C#项目组的人员才知道了。

顺便说一下,最新版本的Reflector好像有点奇怪,在同一个函数里面IL结构完全一样的两个循环,前面一个会被翻译成while,后面一个会被翻译成for,也不知道是从什么时候开始的。因为这个是在看VS2k5 C#的Release模式编译出来的程序发现的,我一开始还以为这是VS2k5的优化成果呢……

posted on 2004-10-27 16:16:00 by sumtec  评论(7) 阅读(3304)

对GlobalHooks的进一步分析

前一段时间mvm写了一个记录键盘敲击情况的程序Post里面提到有两个特殊的全局钩子可以在.NET里面钩上。今天终于有幸把mvm的程序下载下来,然后偷偷的用Reflector去翻看了一下。哦,原来说的是SetWindowsHookEx这个东西,之前没有认真看Post……

然后我就查了一下这个函数,不查不要紧,查了之后发现这里面有一张表:
Hook Scope
WH_CALLWNDPROC Thread or global
WH_CALLWNDPROCRET Thread or global
WH_CBT Thread or global
WH_DEBUG Thread or global
WH_FOREGROUNDIDLE Thread or global
WH_GETMESSAGE Thread or global
WH_JOURNALPLAYBACK Global only
WH_JOURNALRECORD Global only
WH_KEYBOARD Thread or global
WH_KEYBOARD_LL Global only
WH_MOUSE Thread or global
WH_MOUSE_LL Global only
WH_MSGFILTER Thread or global
WH_SHELL Thread or global
WH_SYSMSGFILTER Global only

Thread/Global是什么回事我一下子也没反应过来,倒是另外两个东西把我吸引住了:WH_JOURNALPLAYBACK/WH_JOURNALRECORD。我就记得以前在什么地方看见过这个东西的,而且是在.NET里面!后来看了这两个钩子的解释,我才发现原来说的是所有输入的记录和回放,想起来好像是一个用来录制和播放“宏”的什么软件里面看到的。同时我在这两个钩子的Remark里面找到这么一段话:
Unlike most other global hook procedures, the JournalRecordProc and JournalPlaybackProc hook procedures are always called in the context of the thread that set the hook.

也就是说这两个钩子也可以被钩住,证明我之前的印象应该没有错,确实在什么地方看到一个录制和播放宏的.NET程序用到了这两个钩子。那么到底为什么这两个钩子就可以用了呢(包括mvm提到的那两个)?上面那句话说了,他们跟其他大部分的钩子不一样,他们是在设置钩子所在的上下文(也就应该是进程/线程)里面执行,而不是引发该调用的上下文(其他程序)。

想到这里就不免觉得奇怪,凭什么这四个钩子就对.NET这么亲切呢?为什么其他的全局钩子就不行?都是全局的呀!我想答案可能在LowLevelKeyboardProc的Remark里面:

This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

原来是发送消息给设置全局钩子的进程,怪不得对.NET的程序特别亲切了。不过在JournalRecordProcJournalPlaybackProc里面都没有说到是通过消息来传递的,更没有像上面提到的那样,需要有一个message loop。不过事实上我想无论如何这两个钩子都需要有消息循环,原因很简单,系统在用户按下:Ctrl-Break/Ctrl-Alt-Del/Ctrl-ESC三者当中任意一个的时候,就会给应用程序发送一个WM_CANCELJOURNAL的消息,也许因为这样默认客户程序必须有一个消息循环吧……(还是不敢确定)
但是问题又来了,为什么其他的钩子就不能够用同样的方法呢?基于安全或者性能的考虑?这个我暂时就不太清楚了。此外,我开始还以为WH_SYSMSGFILTER也是可以在.NET里面设置的,但是详细一看发现原来不是,看来.NET里面能够使用的全局钩子也就这四个了。

posted on 2004-10-26 15:14:00 by sumtec  评论(13) 阅读(3809)

C#本地变量声明趣味解析

我们先来看看展波举的例子:
http://blog.joycode.com/zhanbos/archive/2004/10/26/36605.aspx
在这个例子里面我们看到,编译器会检查scope问题,目的是防止错误使用本地变量。但是据我研究,这里面有“Bug”(注意双引号),那么会有什么有趣的“Bug”呢?我来给大家一个简单的例子:

        public void Test()
        
{
            
{
                
int a;
            }

            
{
                
int a;
            }

        }


在这个Test函数里面有两对大括号,标明两个互不相属的子范围。这里大家也许看的非常不习惯,因为没有人光秃秃的写这么两对大括号的。我跟大家说:没关系,编译器承认光秃秃的大括号的,这个也是标准C里面的规范之一,作用就是把大括号里面的所有东西认为是“一句话”,准确点讲是逻辑语句,同时内部是一个范围,约束范围内的本地变量不会往外传播。如果大家实在看不习惯了,可以自行加上诸如while(true)之类的前缀,就习惯了。
那么这段代码有什么Bug呢?没有,确实没有Bug,编译顺利通过。当然,显示了两个Warning,说a没有被用到,无伤大雅。我们首先来分析一下,编译器怎么给把这个给弄通过的呢?我们用Reflector来看一下(当然,因为没有切实的代码,所以只能够看IL,而不能够看C#):

.method public hidebysig instance void Test() cil managed
{
      
// Code Size: 2 byte(s)
      .maxstack 0
      .locals (
            int32 num1,
            int32 num2)
      L_0000: nop 
      L_0001: ret 
}


 


哦!原来编译器把内部的变量改名字了!或者说编译器把他们当作完全不同的两个变量来对待。同时我们在这里也可以看出来,实际上在IL里面时不区分范围的,只有本地变量着一个简单的概念。无论你在哪个范围,在什么时候开始声明,实际上都是在函数的一开始用一个.locals这样的伪语句来声明的。这么做是简单省事的办法,因为如果在用户源代码实际声明的地方才在栈上面开辟空间,那么最后函数退出的时候就不知道该释放多少栈空间了。当然这不是不可以解决的,但是那样的话增加了不必要的复杂度。如果我来设计.NET Framework,我也会通过高级语言的编译器来约束范围问题,而不是摆到IL里面去解决。(毕竟IL里面没有这样的功能不影响我们写程序)稍微引申一下,我们就知道,一个函数里面有多少个本地变量,取决于整个函数内部声明了多少本地变量,而与变量所在范围无关。在IL这一层里面暂时我们没有看到这样的优化工作,我们可以看看这样的代码最后被编译器编译成什么了(用Release模式编译):

        public int Test()
        
{
            
int b;
            b 
= new Random().Next(5);
            
if (b < 5)
            
{
                
int a = new Random().Next(5);
                Console.WriteLine(a);
                b 
= a;
            }

            
else
            
{
                
int a = new Random().Next(10);
                Console.WriteLine(a);
                b 
= a;
            }

            
return b;
        }


Reflector 反编译结果:

public int Test()
{
      
int num1 = new Random().Next(5);
      
if (num1 < 5)
      
{
            
int num2 = new Random().Next(5);
            Console.WriteLine(num2);
            
return num2;
      }

      
int num3 = new Random().Next(10);
      Console.WriteLine(num3);
      
return num3;
}


大家可以看到num1是b,num2和num3则是分别的两个a。事实上这两个a互相之间是没有任何冲突的,也就是说是完全可以重用的,编译原理里面也有一个变量重用的优化,但是这里看不到有这样的优化,我觉得比较吃惊。虽然说这也可以算是一种Bug(严格说来是也不是),但是我要说的“Bug”不是这个。

分析完上面这些基本知识,我就来劲了:

        public void Test()
        
{
            
{
                
int a;
            }

            
{
                
int a;
            }

            
int a;
        }


看,编译出来之后却出现了错误:
error CS0136: A local variable named 'a' cannot be declared in this scope because it would give a different meaning to 'a', which is already used in a 'child' scope to denote something else
哦,原来这个跟声明的顺序还没有关系,只要子范围里面有a了,那就不能够再定义这个变量了。这个难道跟IL里面所有变量都在函数开始部分声明有关系?看起来好像是这么一回事,但是实际上不是,因为C#的编译器完全可以像前面那样,把最后一个a当作另外一个变量。这到底是怎么回事呢?我们需要作本次探索的最后一个实验:

        public void Test()
        
{
            a 
= 2;
            
{
                
int a;
            }

            
{
                
int a;
            }

            
int a;
        }


这下可好,除了刚才那个错误之外,还多出来另外一个:
error CS0103: The name 'a' does not exist in the class or namespace 'ConsoleApplication1.Class2'
也就是说,编译器根本就没有把后面那个a当作从函数一开始的地方定义来看待。但是这两个错误合起来反而容易让我们产生这样的错觉和悖论:
因为前面两个a在范围外面就应该消失其影响力,那就不应该跟后面的a产生冲突。但现在既然你说了,第三个a的定义根前面那两个a的其中某一个定义相冲突了,那我就只能够认为后面这个a实际上在前两个a被定义出来之前就已经存在了,因为后面这个a处于外层范围,它不会在内层范围失去作用之前失效,这样还能够解释得通。可是这么解释我只能够认为外层的a应该在函数一开始的地方就生效了(老式的C编译器有一段时间确实是这样的),可是偏偏还来一个CS0103错误!解释不通,有“Bug”!

最后我来修正这个我一开始提出的说法,其实并没有Bug。得出有Bug的结论,那是从纯粹的语法角度看这个问题的,我也觉得应该容许在第三个a的定义出现,顶多只给出一个Warning。但是微软却给出了一个错误,我想这是从避免不必要的Bug的角度考虑,尽量保护开发人员避免不必要的烦恼。开发人员确实很有可能在定义了第三个a的时候忘记第一二个a已经失效了,同时也忘记了自己定义过第三个a,还以为自己用的是第一个或者第二个a里面的数据。不过对于这种解释,我还是有意见的:既然约束已经缩窄到这个地步了,那为什么要允许第二个a的定义呢?如果开发人员会忘记自己定义过第三个a,有什么理由认为不会把第二个a的定义给忘记了,以为自己在用第一个a呢?

本来上面所写的那些统统都是垃圾代码,我认为,在一个函数内部根本就不应该有相同的变量来迷惑自己。C#的编译器在这些问题方面确实有相当严谨的考虑,不过我还是觉得有一些“悖论”存在,如果能够更加严谨,我认为只会更好。

posted on 2004-10-26 13:13:00 by sumtec  评论(4) 阅读(2466)

给各方的一个预警信息

 今天我才知道我在博客堂的Blog被人污染了,有人在回复里面贴这种东西:

为了避免有人认为我是在这里对其宣传(这个罪名可是担当不起),因此这里截断了,只是为了显示证据。这样的回复一共有四个,每一个的用户名是一样的,但是里面的连接却都不一样,我已经全部删除了。不知道这个现象以前有没有发上过,反正我是没有遭遇过,一直以为只有那些很垃圾的论坛才会被这种东西骚扰,看来这种骚扰哪里都会存在。其他的诸位博客看来得小心你自己的blog里面也留有这样的回复而不自知,所以经常管理自己的blog还是必需的,至少从这个角度讲是必需的。

另外开心和dudu是否都要看看这个问题是否需要注意了,至少我觉得会有很多人不愿意看到自己的blog里面有这样的留言。如果说现在是小规模个别事件还好说,如果出现大规模同类事件是否会引起大量的投诉,甚至某些部门也出面了,那就比较麻烦了,还是提前准备比较好。其实出现这种现象也许也算是一件好事,说明网站出名了。但是马上我又想到另外一件事情了,你说会不会有DOS攻击之类的事情呢?这个就扯得比较远了。

其实我也担心是否有人给我结仇了,故意这么损我的……如果是这样我首先要反省,说话还是需要更加客气。不过贴这种东西的人是不是需要小心一点,惹毛了一堆developer(多数是精英)也不是什么好玩的事情。(想想对方还不一定认识字呢……)

posted on 2004-10-26 11:44:00 by sumtec  评论(11) 阅读(1630)

(IV).NET CF二进制文件操作+ SmartPhone开发思考

刚才对.NET CF内部的代码的分析得知,FileStream.Position对性能有严重的损耗,那么我们如何去解决的这个问题呢?

1、如果你可以自行计算当前位置,那是最好的了。比如for(int i = 0; i < fs.Length; i++),或者有一个count变量自行累加,然后通过while(count < fs.Length)来进行循环。
2、如果上面一个条件无法达成,那么可以考虑使用上文所示代码,用MemoryStream来进行间接读取。虽然需要从文件搬到内存,然后再进行读取操作,看起来绕了一圈,但是实际效率还是相当不错的。
3、如果文件超出内存能够承受的范围,那么只好自己编写一些更加复杂的代码了。比如一小段一小段的用MemoryStream来读取,或者自己写一个替代FileStream的类。

也许大家会困惑,难道还有情况不方便进行当前位置的计算吗?不方便的情况其实还是有的,最简单的比如BinaryReader.ReadString(),这个函数读取了多少个字节取决于编码的方式,以及该字符串的长度。由于它使用了一个7BitEncoding的方式来压缩字符串长度信息,因此整个字符串到底占用了多少文件空间还不是一件特别简单的事情。具体的代码可以自己看一下BinaryReader/BinaryWriter里面的Read/Write7BitEncodedInt函数,简单点讲就是,如果该字符串所占字节数n(注意不是字符串当中字符的个数)小于等于0x7f,则为1+n个字节;小于等于0x3fff,则为2+n;小于等于0x1fffff,则为3+n;小于等于0xfffffff,则为4+n;否则是5+n。另外一种比较罕见的情况是,你需要把这个FileStream交给一个你不认识的模块去处理,因此你无法得知读取了多少字节,也无法保证当前读取位置是否已经被强行修改了。关于如何处理这些问题,相信是见仁见智了。

写到这里,我就不断感叹,CE上面的资源是在是少得可怜(相对于桌面来说),尤其是SmartPhone。其实一个项目的开发过程,还是需要考虑到一些技术细节。也许每一个阶段考虑的范围不一样,例如最初的阶段我们首先需要解决领域模型的东西,这个时候我们不能够被技术细节所束缚,不能够因为我们希望使用.NET CF所以我们不解决某个需求等等。但是随着进度的推进,技术细节就开始逐渐地进入了我们的视野,我们绝对不能够视而不见的。举一个例子,如果老板说,我们要进行一个单片机开发,片上内存4-8k,最多的片外内存是64k,那么一切关于OO的东西几乎就是瞎掰。
当然,反过来的极端也是不可取的。如果在比较开始的阶段就过分的考虑技术细节也是不正确的,例如Xml可以为我们的开发工作提供方便,它的强大的描述能力和简单明了的格式,使得我们希望产生某些数据变得相对简单,那么为什么我们要舍去不用呢?一开始就专门开发一种工具,也许就会限制你的思路,因为可能你会觉得有效率问题,所以所有扩展性都是奢侈的部分。最后其实一个依赖于扩展性的项目却因为一开始设计的文件格式不支持,而无法完成开发,那这个代价就太大了。其实目前我遇到的情况是使用了Xml之后发现性能上面有非常严重的问题,但是相对来说还是比较庆幸当初使用XML作为数据的载体。因为开发过程确实比较轻松快捷,而且也拓宽了我们的思路,发现原来有些东西确实考虑的不周到,也搞清楚了一些原来毫无概念的东西。那么现在怎么办呢?技术问题相对于设计问题还是比较好解决的,比如说我们可以做一个简单的XML->特殊二进制格式的转化工具,就能够极大地提升性能。目前对于6000项数据的完整遍历,大概在19秒的级别,再加上一些其他的优化手段,目前能够达到3秒左右的级别,基本上属于可以接受的范围。(从197秒到3秒,提升幅度还是比较夸张的。)

写着写着就回忆起以前写自己的文件格式,现在还是念念不忘,后来逐渐认识到随着主流软硬件系统的性能提升,一些比较简单但是相对低效的文件格式比如Xml(不过我用Xml的机会非常少,几乎就是没有用过),其效率也慢慢变得可以接受,就不再愿意去做这种费时费力的事情了。没想到今天还是要开发一种特殊的二进制格式,感觉又回到了过去386之前的时代了……640k的物理限制,40MHz的CPU……其实现在在手机上用.NET CF开发也好不到哪里去,还是要不断的优化优化再优化。幸好以前在这些方面还是有一定的基础,不然真是手忙脚乱了。

p.s.: 会不会有人说,你的197秒怎么来的,在我这里才20秒不到的样子。呵呵,那我估计你是在P4 2.8(HT) 1G内存或者以上的机器上跑的模拟器吧?模拟器在大吞吐量方面的操作要比真实手机要快10倍左右,比如这种大量的文件读写操作。但是如果是普通的密集运算,两者就几乎不相上下了,甚至有可能手机还要快一点(比如启动的时候)。

今天就到这里吧,下次再写。(唉,明知道周末人看的人少,还那么努力不知道为什么……)

posted on 2004-10-23 04:59:00 by sumtec  评论(11) 阅读(2814)

今天晚上发飚了,来一段“浮动”窗口的代码

前一段时间看到博客堂上面的mvm用纯.NET写了一个有趣的程序,用来记录敲击键盘次数,当时就非常感兴趣。不过可惜这边的破烂蓝波速度质量奇差,当时网络时断时续无法下载,后来有机会下载了,却连接失效了。所以一直就没有机会看看原程序,家伙都准备好了——Refactor,既然mvm都说了可以disassemble……可惜啊可惜,后来就一直没有关注了。

今天晚上发飚,突然想起来mvm说我介绍的这个代码着色器有问题,说不能够正确处理空格,今天我突然想到原来是博客堂那边的.TEXT有点问题,如果拷贝上去的内容有空格,是不会自动转换成 的,同时也不接受空格,会出错,于是就让mvm以为是语法着色器的问题。于是我就想找mvm的那个Post,re他让他来博客源申请一个号,这样就可以用博客源的编辑器来编辑,编辑好了再转贴到博客堂就不会出问题了。

没想到那篇文章还没有找到,却又重新注意上那篇键盘计数器的Post,发现有40多个Reply,于是进去看看有什么新鲜说法。结果发现有人问floating窗口怎么做,嘿嘿,不就是floating嘛,拿手好戏……于是干脆忘了原来的想法,改变目标,把我的方法贡献出来给大家:

        private const int WM_NCHITTEST = 0x0084;
        
private readonly IntPtr HT_CAPTION = new IntPtr(2);
        
protected override void WndProc(ref Message m)
        
{
            
if (m.Msg == WM_NCHITTEST)
            
{
                m.Result 
= HT_CAPTION;
            }

            
else
            
{
                
base.WndProc(ref m);
            }

        }


是不是很简单呢?呵呵,实际上在WinUser.h里面还有好多关于HITTEST的返回值的定义,如果有兴趣的话可以做一个不规则的窗口出来,其实还是相当容易的。至少在要求整个窗口任何部分点击之后都能够移动窗口,代码就比mvm提供的方法要“简单”多了。(呵呵,mvm的方法其实也挺简单的,至少不需要知道什么消息机制、消息代码等比较啰嗦的东西,以前VB3的时候我就是这么干的。)当然这里涉及了一些CLR以外的知识,可以说并不是非常纯正的CLR解决方案。不过这里也没有使用到任何.NET Framework以外的东西,甚至没有P/Invoke,所以还是相当pure的。有兴趣的话还可以试一下值为-1的Transparent,为0的NoWhere,为3的SysMenu,为20的Close等等,写出来的窗口绝对有着“莫名其妙”的行为,好玩着呢。(不怕死的还可以试一下-2 —— Error,我怕,没试过)

P.S.: WinUser.h可以在你的Vs.net安装目录下面的VC7\PlatformSDK\Include里面找到,如果你的VC安装全了的话。比如说:
C:\Program Files\Microsoft Visual Studio 2003\Vc7\PlatformSDK\Include\WinUser.h

P.S.S.: 今天晚上继续发飚,也许还会有第5、第6篇文章……好戏不是天天有,但今天连台。

posted on 2004-10-23 03:14:00 by sumtec  评论(11) 阅读(6424)

(III)继续探讨.NET CF下大规模数据读取和保存

不知道大家有没有对我上面写的那个程序在.NET CF上面跑一遍?怎么样?感觉如何?
大家肯定完全没有耐心完成整个过程,大概看到第一个输出之后,就基本上“死机”了。等了五六分钟时在不耐烦了,断点调试一看,fs.Position才到xxKB到xxxKB之间,基本上绝望了,这就是我当时的感觉。但是千万不要放弃,请把while语句直接读FileStream的那一段去掉,重新来一遍。这一次怎么样呢?奇怪吧,绕了一个大弯,申请了一块巨大的内存,然后再用MemoryStream来读,速度远比FileStream快很多。我猜大家刚才还在注意那个BinaryReader,觉得那是一个累赘,一个可能的效率瓶颈。其实问题不在那个BinaryReader身上,相信大家已经知道问题在FileStream的Position属性上面了。用Reflector一看,果然问题多多:FileStream上面的Position计算非常麻烦,尤其是如果你一遍写一遍读的话问题会更加严重(不过本来问题已经够严重了,估计你不会觉得会有什么差别,麻木了)。

public override long get_Position()
{
      
if (this._handle == HostNative.INVALID_HANDLE_VALUE)
      
{
            __Error.FileNotOpen();
      }

      
if (!this.CanSeek)
      
{
            __Error.SeekNotSupported();
      }

      
this.VerifyOSHandlePosition();
      
return (this._pos + ((this._readPos - this._readLen) + this._writePos));
}

首先需要经过两个判断,虽然没有多大的性能损失,但是我还是觉得不雅观。要命的就在那个VerifyOSHandlePosition里面:

private void VerifyOSHandlePosition()
{
      
if (this.CanSeek)
      
{
            
long num1 = this._pos;
            
long num2 = this.SeekCore((long0, SeekOrigin.Current);
            
if (num2 != num1)
            
{
                  
this._readPos = 0;
                  
this._readLen = 0;
            }

      }

}

到这里层次还嫌不够深,还要调用一个SeekCore。我真是佩服写这段代码的人,也许这是一个无可奈何的事情,但是这么写我还是觉得比较让人感到难受得——明明是要得到当前的位置,却不得不首先调用一下Seek(0, SeekOrigin.Current)把位置“重新定位到当前位置”。也许是因为操作系统会缓冲,或者是操作系统会跑飞?这个十分有趣的写法给我非常深刻的印象,有点像看到了脑白金的广告一样。让我们继续跟踪下去:

private long SeekCore(long offset, SeekOrigin origin)
{
      
int num1;
      
int num2 = PAL.File_Seek(this._handle, (int) offset, (int) origin, out num1);
      
if (num2 != 0)
      
{
            __Error.WinIOError(num2);
      }

      
this._pos = num1;
      
return (long) num1;
}

终于看到PAL的东西了……不是仙剑奇侠传的PAL,确切是什么意思我也不知道,金山词霸说是太平洋航空图书馆,我猜可能是什么抽象层的意思,反正我知道PAL里面都是执行引擎里面的Native方法的P/Invoke。也就是说每次我们想得到当前的Position之前,首先要通过三次调用进入执行引擎,才能够得到确切位置。这就是造成IO效率的罪魁祸首。

待续……

posted on 2004-10-23 01:24:00 by sumtec  评论(3) 阅读(1601)

继续:.NET CF下面“大”规模数据的读取和保存

上一个Post讲到,在.NET CF下面直接用XmlDocument来读取比较大的Xml文件是非常缓慢的,但是我们也只是知道了一个绝对值,没有参照物,还是不知道到底怎么样。其实利用上一个Post的代码,读取同样格式和内容的Xml文件,当Item数量在接近6000的时候,仅仅是遍历一次所有的内容,仍然将会花费50秒左右的时间。注意这里仅仅是遍历,没有其他多余的语句,例如将数据用某种类型结构保存在内存当中等等。当然,这比197秒要好多了,但是这仍然不能够满足我们的需求。在跟进一步的提高速度之前,还是先尝试粗略分析一下性能的瓶颈在哪里。

用Reflector分析一下Xml相关的各个类,我们会发现XmlDocument实际上是利用XmlReader来解析Xml文件的,同时还会同时构造每一个节点,比如XmlElement或者XmlAttribute。很明显XmlDocument肯定不会比XmlReader更加有效率,至少XmlDocument还需要构造一系列的对象,并且还是递归的。如果我们能够减少节点的层次,或者尽量封闭节点(使之不具备拥有子节点的资格,例如把XmlElement的数据挪到XmlAttribute里面),那么XmlDocument的效率会有一定层次的提高。如果说数据量不是非常大(大概100左右),不希望用复杂的方式来完成本来应该很简单的事情,那么这个方法也值得尝试。
<Root>
  
<Item id = "1" name = "hello" something = "xxx" extension = "yyy" abc = "efg">Text</Item>
  
<Item id = "2" name = "world" something = "zzz" type = "reference" extension = "123">China</Item>
  ……
</Root>
不过我们也可以预见这个提高的程度是相当有限的,甚至不如分割文件所带来的效率。除了可能不雅观这个不充足的理由之外,如果我们需要对something用一个Attribute来额外描述的时候,这个方法就部分的失效了。当我看了一遍Xml里面的相关代码后,并没有完全搞清楚里面的机制,但是有一点是肯定的——代码复杂,从XmlDocument到XmlReader都是复杂的代码,几乎没有一目了然的东西。不过在这个复杂的背后,其实有着一定的效率优化,这在后面的实验结果里面可以看出来,但是我却暂时无法从.NET CF里面的代码直接分析出来,到底是哪个部分的性能优化带来了这些性能提升。

有的时候我们也需不愿意,或者无法进行文件分割,比如说所有的数据都必须要加载了才能够计算,又或者我们不能够冒分割文件所带来的一些技术风险,那么也许我们需要考虑用自己的文件格式来保存了,比如说二进制(也许文本方式也是一个不错的选择,我说的是纯文本)。如果您同意了我这个假设成立,那么我们可以开始着手研究一下有关文件读取方面的问题。不要以为这是一个简单的问题,我之前也以为这是一个很简单的问题,而最后我却发现这个问题要看硬件平台如何。我一年前在PC上面做应用,感觉文件读取和内存读取效率相差不大。当时我就猜测由于PDA的文件在内存当中,因此不会像桌面PC一样,文件的读取效率和内存的读取效率相差上千倍,顶多相差十倍。而当时的工作经验告诉我似乎结果就是这样的,因为我在PDA上面读取文件几乎就没有遇到什么性能瓶颈。然而这一次在SmartPhone上面的情况却完全不一样,让我大吃一惊。如果有机会大家可以在SmartPhone上面运行一下如下的代码片断,保证有一个深刻的体会:

        void Test()
        
{
            
// Create an 1MB file for testing, make the buffer size of filestream object to 640k
            FileStream fs = new FileStream(
                
"Test.Binary",
                FileMode.Create,
                FileAccess.ReadWrite,
                FileShare.None,
                
655360
                );
            
byte[] buff = new byte[1048576];
            fs.Write(buff, 
0, buff.Length);

            
long ticks;

            
// Reset the position
            fs.Position = 0;
            BinaryReader br 
= new BinaryReader(fs);
            
// Test reading every byte from BinaryReader using for statement:
            ticks = DateTime.Now.Ticks;
            
for(i = 0; i < buff.Length; i++)
            
{
                br.ReadByte();
            }

            ticks 
= DateTime.Now.Ticks - ticks;
            MessageBox.Show(ticks.ToString());

            
// Reset the position and test reading every byte using while statement:
            fs.Position = 0;
            ticks 
= DateTime.Now.Ticks;
            
while (fs.Position != fs.Length)
            
{
                br.ReadByte();
            }

            ticks 
= DateTime.Now.Ticks - ticks;
            MessageBox.Show(ticks.ToString());

            
// Reset the position and test reading via MemoryStream
            
// using while statement:
            fs.Position = 0;
            ticks 
= DateTime.Now.Ticks;
            
// we define another buffer to test mem alloc time all together
            byte[] buffRead = new byte[buff.Length];
            fs.Read(buffRead, 
0, buffRead.Length);
            MemoryStream ms 
= new MemoryStream(buffRead);
            br 
= new BinaryReader(ms);
            
while (ms.Position != ms.Length)
            
{
                br.ReadByte();
            }

            ticks 
= DateTime.Now.Ticks - ticks;
            MessageBox.Show(ticks.ToString());
            

            fs.Close();
            
        }


待续……

posted on 2004-10-23 00:38:00 by sumtec  评论(0) 阅读(2115)

在.NET CF下保存和读取XML文件数据不是一般的慢……

测试机器规格:
CPU:           TI OMAP 200MHz
FreeMem:   大约 7.5M
WinCE ver: 4.20(build 0)
.NET CF:    1.0

对一个格式大致为(保护公司机密,所以名称和结果不是很真实)

<Root>
  
<Item attr = "something">
     
<ID>1</ID>
     
<Name>abcdef</Name>
     
<Something ref = "alpha" type = "beta">abcdefg</Something>
     
<Other type = "abc">1</Other>
     
<Extension type = "linkSame">Parent</Extension>
     
<Parent xxx = "yyy">3</Parent>
  
</Item>
  
</Root>

的XML文件用XmlDocument直接进行读取,我的实测结果是:

当Item数量为150个左右的时候,读取所有信息需要时间大约为6-9秒。当数量为300左右时(此时文件大小大约为110k),时间大约需要11-14秒。当数量暴增到接近6000的时候(大小约为2M),时间需要大约200秒。在桌面下,我们可以轻松的使用XML来进行非结果化数据的存储,但是到了CE平台下面,我们就不能够继续这么干了,至少大量数据这么做是不行的。当然,XML也从来不是为了存储和检索大量数据而设计的,大家主要还是利用XML相对来说非常强大和灵活的描述能力来对一些非结构化的信息进行描述。虽然说6000项数据相对于大型数据库动辄上百万的级别来说还是芝麻绿豆,并且在桌面上也完全能够胜任(大概1.1秒),但对于SmartPhone这种资源较为有限的机器来说就是一个大规模级别的物体了。而非常不幸的是,.NET CF 1.0在这方面的设计也比较糟糕(据传说在2.0会有较大的改进),这两个条件的共同作用造就了上面那个非常糟糕的测试结果。这仅仅是把数据读进内存当中,还没有进行任何的处理,如果让用户去等待这么长的时间去读数据,估计用户肯定得把手机扔掉。

不过这不代表着没有任何改善的余地,比如说,我们可以把文件内容分割。前提条件是实际上不需要所有的内容,并且内容能够有效地分割(包括容易管理)。这个方法的好处是,仍然可以用XML来进行存储。有好处当然也有坏处:需要将内容分割,意味着你需要管理这些分割的部分,比如在程序需要的时候必须能够调度到内存当中,同时检索的难度也加大了(可能需要一个额外的索引文件或者机制来完成)。

在进行这样的分割之前,最好考虑一下是否有其他的解决方案,比如说XmlReader。说起来好像有点复杂,似乎要自己手动处理Xml文件,其实并没有那么复杂,比如下面的这个函数可以用来定位某一个指定的element:
        // Seek a specified element node.
        
// true: The specified element was found.
        
// false: reached the end of file
        bool SeekElement(XmlReader reader, string name)
        
{
            
while(reader.Read())
            
{
                
if (reader.NodeType == XmlNodeType.Element && reader.Name == name)
                
{
                    
return true;
                }

            }

            
return false;
        }


当然了,相对于一句doc.Load(path)来说,还是会复杂不少。此外xml文件内容的结果复杂度也需要简化,这样才便于手动处理。好在我们也不指望在手机上做什么特别复杂的应用,比如说什么内容排版之类的,一般来说数据也不是很复杂。如果你同意我的说法,那么也许可以参考一下我写的代码:

        void Travel(XmlReader reader)
        
{

            
while (SeekElement(reader, "Item"))
            
{
                
if (reader.IsEmptyElement == false)
                
{
                    FillAttributes(reader);
                    
while (SeekElementWithEndCheck(reader, "Item") )
                    
{
                        FillAttribute(reader);
                        
if (reader.IsEmptyElement == false)
                        
{
                            
if (SeekTextWithEndCheck(reader, property))
                            
{
                                text 
= reader.Value;
                                MoveToEnd(property);
                            }
 // end if Text found
                        }
 // end if Item is not empty
                    }
 // end while find elements under "Item" element
                }
 // end if element "Item" is empty
            }
 // end while seeking all "Item" elements
        }


        
// Seek a specified element node.
        
// true: The specified element was found.
        
// false: reached the end of file
        bool SeekElement(XmlReader reader, string name)
        
{
            
while(reader.Read())
            
{
                
if (reader.NodeType == XmlNodeType.Element && reader.Name == name)
                
{
                    
return true;
                }

            }

            
return false;
        }


        
// Seek an element with checking the end of a specified element node
        
// true: an element found(the name of element can be retrieve from reader.Name, etc.)
        
// false: reached the end of file, or reached the *end element* node ""
        bool SeekElementWithEndCheck(XmlReader reader, string name)
        
{
            
while(reader.Read())
            
{
                
switch(reader.NodeType)
                
{
                    
case XmlNodeType.EndElement:
                        
if (reader.Name == name)
                        
{
                            
return false;
                        }

                        
break;
                    
case XmlNodeType.Element:
                        
return true;
                }

            }

            
return false;
        }


        
// Seek text within the bound of element *name*
        
// true: text found (in reader.Value)
        
// false: reached the end of file, or reached the  node.
        bool SeekTextWithEndCheck(XmlReader reader, string name)
        
{
            
while(reader.Read())
            
{
                
switch(reader.NodeType)
                
{
                    
case XmlNodeType.EndElement:
                        
if (reader.Name == name)
                        
{
                            
return false;
                        }

                        
break;
                    
case XmlNodeType.Text:
                        
return true;
                }

            }

            
return false;
        }


        
// travel all the attributes
        void FillAttribute(XmlReader reader)
        
{
            
if (reader.MoveToFirstAttribute())
            
{
                
do
                
{
                    
// fill your code here
                    Console.WriteLine(reader.Name + " : \t" + reader.Value);
                }
while(reader.MoveToNextAttribute());
            }

        }



待续……

posted on 2004-10-22 23:10:00 by sumtec  评论(7) 阅读(2596)

【第1页/共2页,18条】
首页
前页
1

Powered by: Joycode.MVC引擎 0.5.2.0