SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  263 随笔 :: 19 文章 :: 2993 评论 :: 22 引用

2011年8月19日 #

博主您好! 
首页是发表精品文章的地方。 
您的博文“QUIZ:一个有8个属性的匿名类大约会占多大的文件大小?”被移出首页,由此给您带来的麻烦,请谅解! 
首页文章要求:原创,排版整齐,文中有文字明确说明文章的主题,内容对程序员有帮助。 
下列类型的文章不允许发到首页: 

1) 转载;2) 只有代码;3) 简单的提问;4) 软件发布;5)人才招聘;6) 包含推广或广告内容;7)活动信息;8)关闭评论功能的随笔;9)不完整的内容。

 

既然抛砖引玉中的抛砖不能出现在首页,那我也不想引玉了。真遗憾,在博客园中用了这么长的时间,我终于有了当年从CSDN退出时的感觉:实在无法理喻这里的风气了。难道C#自己写的一个自定义分页控件就是一个很有价值的文章?我的意思并非这样的文章就不好,而是说,一个引起思考的短文居然和这个比都没有价值,我不知道这样的地方价值在哪里?或者说,这样的地方已经没有多少营养了,需要认真考虑另起炉灶了。

 

我想问一下,认为不值得放首页的那些个同学,你们有几个能回答出我说的这个问题?你们有几个自己真的做过这方面的研究?知道MetaData都包含什么?这个是一个简单的提问?

 

前阵子微博上还有人调侃,说:

 

其实我倒不是觉得不能写一些非技术的东西,或者制造一些话题。但是,现在真不是观众说了算,一些我觉得没啥看头的东西占据了首页,一些其实蛮有看头的东西却被大量水文瞬间冲掉。好,废话不多说,最后给出这个被强制移出首页的QUIZ的答案。

 

一个包含了8个属性的匿名类,会占用大约5K的文件存储空间。也就是说,如果你使用了10个这样的匿名类,你的文件大小就会导致你的文件增大大约50KB。对于一个桌面应用来说,50KB算不上什么,但对于一个SilverLight应用来说,这就不是一个小数目了。更可怕的是,如果我们不知道这个问题,使用了一个比如说42个属性的匿名类,就会导致你的文件增长大约46KB的大小。当然,这里有严重的水分,原因后面会简单提到。


在开始进行一些简单的分析之前,也许你需要自己去了解一些有关CLI的知识,比如什么是BlobHeap、UserStringHeap、StringHeap、TableHeap等,以及里面是用什么格式进行组装的。原本呢,我觉得这些大家只要用Google搜索一下CLI和MetaData就会出来了,可是我现在觉得,以博客园的平均水平而言,也许连这个能力都不具备。那么好吧,下面这个是链接,有能力的请自行阅读:

http://download.microsoft.com/download/d/c/1/dc1b219f-3b11-4a05-9da3-2d0f98b20917/partition%20ii%20metadata.doc

 

下面,我们来对这一个问题进行一下简单的剖析。为了让问题更明显和突出,我们对一个有42个属性的类进行分析。

 

首先,整个匿名类哪些部分会占用的比较多? 根据统计,在TableHeap中使用了5k,StringHeap中使用了7k,Blob使用了27k,UserString使用了1k,Body占用了近5k。需要说明的是,除了Body部分(IL代码)相对比较准确之外,其它的部分的统计是不准确的。这是因为根据规范,相同的内容很可能会自行排重而只记录一遍。而同一个内容在整个程序集中会多次被使用到,据一个例子:比如属性的访问器名称get_PageId,可能在多个类当中都有该属性;此外还有其它很多原因可能会导致重复统计。根据我的估算,可能需要打个3折,即便如此也得占用大约14k的空间。为了便于讨论,我们这里就先忽略这些重复的统计。


途中的Blog占用空间非常大,至于为什么,这是我尚未解开的部分。也许是因为:

1、BlobHeap包含了太多的东西,比如一个函数的签名,标签(Attribute)所使用的具体参数等;

2、使用的场景也比较多,比如TableHeap中的MethodRef、Method、Param等等,几乎各个Table都可能有指针指向Blob。甚至连MethodBody当中的某一句IL,比如call System.Linq.Quearyable.Where'1 ... 等,都可能会在Blob里面加一些东西;

3、从Reflector看到的情况无法解释这一部分异常增大的原因,甚至不排除这个工具本身哪里有Bug导致统计数据出现了错误。

但无论如何,也不是重复统计所能解释的,关于这部分后面会给出两个图进行说明。


抛开奇怪的Blob部分,还有一些很容易发现的问题。比如,为什么一个匿名类需要使用1k的UserStringHeap呢?UserStringHeap中记录的是你代码当中的字符串常量,比如说下面这么一段代码:

void Main()
{
  Console.WriteLine(
"Hello world!");

 

这么一个语句,当中的"Hello word!"就是要进入UserStringHeap的,大约会占用23个字节。可是我们的匿名类里面,怎么会有字符串常量呢?原来,编译器在生成匿名类的时候,为了便于你调试,会在类的前面打上一个DebuggerDisplayAttribute标签,比如:(为避免泄露些什么,字段名称已经修改,字符串中的...表示后面还有好长好长……)

 

DebuggerDisplay(@"\{ PageId = {PageId}, ReadId = {ReadId}, RefId = {RefId}, Guid = {Guid}, TypeId = {TypeId}, CategoryId = {CategoryId}, Title = {Title}, Mode = {Mode}, Setting = {Setting}, Tags = {Tags} ... }", Type="<Anonymous Type>")]

 

 

打上这一个标签的好处是,当你进行断点调试的时候,你可以看看这个匿名类里面的属性值都是什么。可正是这个标签,导致了UserString的占用。由于不同匿名类中,属性名称可能会不一样,就算一样,顺序也可能不一样,因此这串字符串也就不太可能完全相同。于是,你用的匿名类越多,这种无谓的占用就会越多。幸好,这个问题只会出现在Debug的编译结果中,对于Release发布则没有这个标签。

 

接下来,也许你会奇怪,对于一个42个属性的匿名类,所使用的StringHeap会达到7k,好吧,这是我的工具重复统计导致了过分的放大。但是,仔细看一下匿名类你就会发现:

1、一个有着N个属性的匿名类实际上是一个有N个泛型参数的泛型类。假设有一个属性是PageId,则:

2、属性名称叫做PageId;(7个字节,注:C字符串格式最后有一个字符0)

3、属性的访问器叫做get_PageId;(11个字节)

4、属性所对应的成员叫做<PageId>i__Field;(17个字节)

5、属性的泛型参数名称叫做<PageId>j__TPar;(16个字节)

6、匿名类名称为AnonymousType#`N,数字#表示第#个匿名类,数字N表示有N个属性。那么对于有着8个属性的匿名类1,长度就是17个字节,对于有着42个属性的匿名类2,长度就是18个字节;

7、假设我们属性名称的平均长度就正好是6个,那么42个属性的匿名类就至少占据了2k有多。

 

从上面这个部分,我们就可以发现,假如我们把几乎不会影响一般运行,甚至对反射也没有太大影响的成员名和泛型参数名优化一下,变成平均4个字节(甚至是0字节),那么也可以减少超过一半以上的空间占用。对于使用匿名类较多的某个dll来说,光是这部分可能就可以优化掉大约10k左右的大小。

 

另一个让人吃惊的地方,是MethodBody占用非常大,大5k之多,平均一个属性有大约100多个字节。要知道,一个如下的属性:

public int PageId
{
  
get
  {
     
return _pageId;
  }

对应的il也就如下三句:

ldarg.0
ldfld thisType._pageId;
ret

 

 共6个字节。如果是Debug编译,会多出额外3句以便于调试,也就在多4个字节而已。换而言之,有其它的函数在哪里捣鬼,捣鬼的那几个函数分别是:

Equals、GetHashCode、ToString以及构造函数.ctor。

 

 

如果我们用Reflector打开这个函数来看,Equals、GetHashCode、ToString以及构造函数都要访问到每一个属性。其中对于构造函数来说,这是几乎不可避免的,因为需要对匿名类的每一个属性进行赋值操作。但是为啥需要重写Equals等其他三个函数呢?这是因为这些对象可能会被用到Dictionary的Key中,此时就必须重写Equals、GetHashCode、ToString这三个函数,而这三个函数加起来就得占用大约4k的大小。准确说,所有匿名类的属性总数,决定了整个dll中该部分代码的大小。如果你这个dll中一共有10个匿名类,每个大约10个属性,那么光是代码部分,这三个函数就占用了大约10k的大小。不要忘了,除了代码之外,我们还需要因此写入一些元数据,以及为数不少的UserString。而实际上用到这三个函数的几率非常非常的小,完全可以通过实时动态Emit来完成,而不需要占用这么大的代码空间。当然了,进行动态生成的代码也会占用不少的空间,但如果你做的是一个很大的项目,比如你有很多的页面,每个页面用到不同的Dll,里面都有不少的匿名类,那么这点的优化成本很可能是值得的。而另一种方式也许更好,那就是假设该类不会被当作对比的Key来使用,而是当作一个普通的类,那你可以干脆裁减掉这三个方法(这种裁剪我还没有试验过,也许你需要自己进行尝试)。

 

最后,我们来看看Blob方面的一个奇怪的问题,那就是:匿名类的属性越多,则某一个属性需要用到的Blob就越大。比如说对于一个有42个属性的匿名类,其某个属性访问器所使用用到的Blob大小如下图所示:(包括方法签名,方法中调用某函数、使用某成员所产生的一个MemberRef记录所用到的方法签名等)

 

而一个只有8个属性的匿名类,其某个属性访问器所使用到的blob大小为:

 

目前我想到的合理解释是,由于需要返回该属性对应的字段,如下述IL代码:

ldfld !0 <>f_AnonymousType0'42<!<xxx>j_TPar, !<xxx>j_TPar, ...>::<PageId>i__Field 

这里需要产生一个针对该字段<PageId>i__Field的签名,而签名当中又带有当前类的各种泛型参数信息,因此造成了属性越多,占用Blob越厉害的结果。而至于这部分的Blob是否如我猜测,如是,是否会每个参数都要占用这么多还是其中一部分会被重复利用,都尚未可知。当然,也有可能我的程序有Bug造成的。关于此,我想我不会在博客园继续写了。

 

如果有兴趣的同学,可以去下载一个开源的项目,叫做Mono.Cecil,我的工具就是在这个项目的基础上去完成的。通过该项目的源代码,你可以很好的了解整个.NET文件的结构组成。当然,在你正式开始阅读这部分代码之前,最好先看看我前面提到的那篇文章,因为这个Mono.Cecil的项目里面,注释量基本是Zero。对于那些从来不喜欢Read the fuck code的同学,会是一种巨大的挑战。

 

最后,我宣布从博客园正式退役了,不玩了。有些东西抱怨太多就没意思了,每次发布都要头痛,到底是放首页呢还是放首页呢,还是放首页呢?然后要从一堆花花绿绿的各种我都不知从何下手的选项中,选一些我知道的不知道的东西,简直就是一场噩梦。

 

关键是,没错,我很不爽,你们把我觉得还比较有难度有挑战的小提问给挪出去了,你们不提倡思考了,开始喜欢浮躁喜欢造话题。那些相对来说没啥技术含量的东西,字数也不见得多出多少,不也照样放在首页吗?既然咱们的人生观价值观已然发生分歧了,那只好分道扬镳了。

 

我刚刚做了一个非常艰难的决定,那就是博客园,我不玩了。至于你们信不信,我反正是信了。走咯,回家了,拜拜了各位!

 

 

 

posted @ 2011-08-19 19:25 Sumtec 阅读(4105) 评论(107) 编辑

答案:It depends.

 

“你去屎吧!”

我猜一定会有人这么说。既然“

改天我也去发一篇“QUIZ: 园子一个用户发多少篇给个问题还看心情才给答案的文章才会让全园友崩溃,是什么原因”放首页来好了 ”这样的回复,上面那个想法也肯定必然的。好,那我先剧透一点:这个崩溃和里面的那一个字是什么有关,而且和你是什么职业也有关。

 

“靠,这怎么可能,你干脆说是人品问题好了!”好吧,如果你真这么想,那我也只好说,至于你信不信,我反正是信了。


其实我发这些个QUIZ的原因,是发现了一些有意思的问题,希望大家去实验一把。可是现在园子水平真的很不济,貌似愿意动手刨根究底的人并不多。这也是我在工作中发现的一种现象,很多人都“知其然不知其所以然”,并且心安理得。好吧,抱怨到此结束,下面来说说这个很奇妙的崩溃问题。

 

在我们的工作当中,发现了一个很奇怪的崩溃现象:(非常抱歉,没有任何截图,描述的也不是很清晰,因为这不是我的工作,也就没有第一手资料。)

1、某些人的机器永远会崩溃,无论是用什么浏览器都一样;而另一些人的机器则无论如何都复现不出来;

2、对于会崩溃的机器来说,则在某些个页面上打一开始就崩溃。尤其是IE7,会直接弹出一个红叉对话框,里面一串十六进制数字,显示类似Access denied之类的类似C++错误提示的东西。.NET不是应该托管安全的么,怎么会这样呢?

3、对于崩溃之前的那一瞬间,既没有看到占用内存大小有什么特别不正常,也没有看到CPU负荷有什么不正常。 


对于上面的这个疑惑,我们甚至做了个Dump,开始的时候以为是IE7里面的某个COM组件的错误,因为用WinDbg看出错时堆栈位于当IE窗口大小发生变化之后所经过的代码。可后来的发现证明完全不是这么一回事,因为有人做了一个实验,发现出现崩溃和同一个页面上有多少个SilverLight应用有关。前面这个条件是必要但非充分的,因为在同一台机器上,有的页面不崩溃,有的页面就会崩溃。差异就在于有前者只有1个SilverLight应用,而后者则同时加载并显示了4个应用。

 

可这解释不了为什么有的机器会崩溃,有的则不会。对于这个问题,我们做了不少的假设,比如说:系统内存大小,操作系统版本,浏览器版本,内存泄漏,没有正确的处理异常,各种线程安全问题,SilverLight核心版本不一致,开发版和普通用户版,等等,乃至中毒。随着前面那几个比较严肃的怀疑被一一排除,我们甚至笑谈到了人品的问题。因为开发的时候我们从来没有遇到过这个问题,只是在内部使用的时候,个别美工和设计师会出现这种尴尬的情况,而且没有一个开发人员能用他自己的开发环境复现这个问题。甚至还有人下载了Silverlight 5 Beta来试,发现临界值从4个变成了5个。这个结果让人哭笑不得:有变化了,但解释不了什么问题。

  

就在我们一筹莫展的时候,这位同事又做了一个简化实验:同时展示4个完全相同的SilverLight,里面就一个TextBlock,随便写了几个字。就这么简单的一个应用,如果一个页面显示4个,则会发生崩溃的机器仍然会发生崩溃。这时候我们就开始怀疑,这个问题真的跟我们写的代码,乃至.NET Framework中的托管部分没有任何关系了。进一步的实验发现,只要输入中文,或者使用了中文字体,就会崩溃。只要TextBlock中的是一个英文,并且没有指定字体,或者指定的字体是英文的,就不会崩溃。

 

现在问题指向就比较明显了:中文,或者中文字体就是元凶。到了这里,我们恍然大悟,怪不得会发生崩溃的机器,全都是美工和设计师的。这些美工和设计师因为工作需要,会安装各种你所见过或者从未见过的字体。这时候我们还非常惊讶的发现:

1、我们的Demo里面并没有使用任何特殊安装的字体;

2、即便我们把我们需要的字体打包到XAP里面,仍然不管用;

3、即便我们显示的是英文,但只要指定任意一种中文字体,也会崩溃;

4、对于安装了字体的用户,如果把注册表改了(也就是假装这些字体并未安装),崩溃的症状也就消失了。 

 

这时候我们发现,要解决这个问题,似乎就只能够告知用户,把某些有问题的字体给删掉,或者给他们一个禁用某类字体的脚本。但到底是哪一个,或者哪一些字体会出现问题呢?同事再次陷入了困境,因为实验表明:

1、某一些字体一定不会出问题,我们称为A组;

2、另一些字体则同时存在时,一定会出问题,我们成为B组;

3、将B组字体每三个分成1个小组,随便禁用任意1小组,就不会崩溃;

4、B组字体非常多,逐个实验很耗时间,但随即抽了几个发现,似乎禁用任意1个,也不会崩溃。

 

正当大家都在讨论,这时一个什么问题的时候。当当当当!我闪亮登场了,我说:那看起来不是某个字体有什么畸形的东西在里面导致了某种原因不明的错误,很可能是因为中文字体大小和英文字体大小的原因。尽管很奇怪,比如说崩溃之前内存也没有达到接近2G大小,不应该是OOM这种问题,而且我从来没有、将来也不会用到的字体,为啥要一股脑儿全部加载进来呢?但也不是不可能的事情,毕竟中文字体和英文字体确实有这个差异。在这个思路下面,我们做了这么两个实验:

 

实验一:在会发生崩溃的机器上面,把B组字体删除1个,使得同事显示4个应用不会发生崩溃,然后再加载第5个应用。结果毫不意外,崩溃了。

实验二:在之前我们认为不会崩溃的机器上面,加载N个应用,发现在大约加载到30个的时候,也发生崩溃了。注意,不是.NET报告的OOM异常,我们不可能靠自己写代码能解决此问题。


目前,我们的进展只到了这里,进一步的排查和问题的机掘可能会非常困难,因为已经远超托管范围内的世界了。这个问题最好是微软来解决,因为严格来说这时一个Bug。但是至少,这个QUIZ的答案有了:

 

如果包含中文,或者指定了任意一个中文字体,则一个页面包含1到30个左右的SilverLight应用就可能会导致莫名其妙的无法启动的崩溃。具体是多少还取决于你的职业,比如你是一个很无趣的程序员,这个数字可能是30甚至更大;但如果你是个天才美工,爱好装各种各样牛逼的中文字体,那么这个数字也许是1;而如果你在程序员和美工之间,这个数字就不定了,最常见的数字可能是3到5。哦,对了,还可能和SilverLight版本有关,比如说SL4的数字是4,那么SL5的数字就可能是5。

 

嗯,还有一个QUIZ,答案也敬请期待。 

 

posted @ 2011-08-19 15:54 Sumtec 阅读(1847) 评论(17) 编辑

一个很简单的SilverLight4应用,上面只有一个TextBlock,里面只有一个字。你猜,这一个SilverLight在同一个页面中创建多少个时,会出现莫名其妙的崩溃?这种崩溃和什么有关?

 

哈,知道的同学不要说话。同样,这个问题的答案也是要看我心情如何,心情好的时候就会上来写,嗯。

 

P.S.: @dudu

What is the god damned order of your priority in developing this website? Making money, or making it easy using for us? Why the sooooooo complex publishing panel still there after I complaint it almost one year?

posted @ 2011-08-19 10:03 Sumtec 阅读(479) 评论(2) 编辑