2012年4月27日星期五

喜剧了,D800完胜5DIII

D800一发布,3600万像素就相当震撼了,挑战了135传感器的像素面积极限。当时其实我蛮不爽的,因为我一直认为高感对于抓拍很重要。之前用过一次7D,被那个垃圾级别的高感给震撼了,昏暗的室内拍出来的照片基本上和明亮场景下的卡片机一个级别。这么小的像素面积,高感估计不会很好。以DXOMARK的过往记录,用D700的可用高感推算,如果技术没有改进的话,D800的可用高感估计只有1400左右。当时我就想:啊!废了!

接着5DIII发布了,像素没有质的提高,看来是要拼高感了。当时估计5DIII的高感表现怎么着也会比D800要强,于是相当郁闷。因为考虑到佳能的机器,只要是5D及以下,高感表现完全一样——800左右,这个数字已经维持了至少4年以上了!再加上人家告诉我,佳能低端机器没有色彩曲线,对焦如何烂,测光对焦如何不能联动之类的。未来要是想要双机双镜,佳能简直是……尽管镜头卡口规格有优势,可也经不起这么个糟蹋法。

可后来看的各种测评图片,喜剧效果就开始显现了。先是发现D800比D700的高感还要好,无论是同样的尺寸下面,还是同样的放大比例下面。然后又发现,D800和5DIII比,在ISO1600及以下全面优胜,3200及以上,只要D800有那一档,基本上和5DIII一样的表现。关键是,5DIII涂抹太他妈严重了。我记忆很深刻的是一张对比图,右上角有一个黑色的纸标牌,这个标牌其实不平整,有一些稍微亮一点的折痕。当时是四款机器对比:5DIII vs D800 vs 适马的某型号 vs 845D,其它三个都看到折痕,唯有5DIII是一抹黑。还有很多不那么明显的细节,也是被抹掉了。

再后来,dxomark出来了d800的测评,ISO居然2853!尼玛怪兽啊,怎么可能?这一项有史以来排第三(第一是怪兽级的D3s,第二是后继者D4,D4像素多出33%来,略降一点正常)。而且此机器居然Landscape得分排第一,动态范围高低意思就是亮部暗部的细节会非常好。更变态的是色彩位数,居然在135里面排第一,即使拿120一起比,也拍第三。可以说,这基本上是有史以来最强悍的135电子单反。

即便是这样,我还是以为5DIII出来之后高感会让人吓一跳:毕竟5DIII的像素密度比D800低很多,就算高感没有强太多,也至少要和D800一个等级吧?结果让人失望,居然只有2293。好吧,这也是一个不错的数字,怎么也比5DII的1815好不少。可是……居然败给了像素密度比它还高将近一倍的D800,我这不知道到时候拿什么来宣传这个机器了。是说5DIII的比D800高感好呢,还是像素少呢?我仔细想了一下,比D800有利的地方恐怕只剩下:
  • 不追焦的情况下连拍数稍高一些;
  • 对焦点数稍微多一些;
  • 设想能够连续拍摄超过30分钟(好吧,如果有这么高的要求,估计HDMI直出更有诱惑力吧?;
  • 镜头潜力;
  • 广告和营销;
  • 也许会出现的价格优势,也许;
  • 也许产能会好一些,也许。
这些优势几乎和技术无关,基本上未来4年佳能的套路只剩下:
加大广告和营销的投入;
加大产能,确保在尼康断货期间确保足够的供货;
降价;
出一个升级版;
出一些个变态镜头。

还有吗?没有了。现在要不是大面积断货,估计都要忍不住入手D800了。





2012年4月15日星期日

SqlServer 2005如何改善datalength计算的性能问题


假设数据库中有一个表photo,其中包含2个key组成联合主键,分别名为userId和photoId,以及一个image类型的字段stream。此时有两种不同的思路,一种是在保存的时候进行计算,并存入第三个字段dataSize,另一种是每次都直接使用以下语句进行计算:
select sum(datalength(stream)) from photo where userId=@userid

第一种方法一般来说都挺不错的,但是有那么一个不那么完美的问题:很难确保数据一致性。假如某一次更新中的代码新添加了一种对stream进行修改的特殊代码,但很遗憾忘了修改dataSize字段。更新之后,程序不会表现出明显的异常,有可能会跑了很长一段时间之后才会发现。而发现之后,就需要专门的小程序来重新修复。并且修复之后,以我的经验,一定会有用户不认可发生的变化。也许有人会说,图片不都是只插入不更新的么?那我们就再来想另一个场景:这是一个类似Evernote的应用,用户在里面保存的文字数量也是限量的,同时也是需要修改的。有人还会说,修改stream的代码不应该都在一起吗,不应该修改的时候都会修改dataSize吗?问题是,应该的事情总有不发生的几率,而小概率事件则必然发生。

第二种方法则保证了每一次都是准确的,也不需要代码进行相应的计算和更新,于是就没有了前面的一个问题。这种方法在每个用户的数据量较少的时候问题不大,但是在数据量巨大的时候,会面临非常严重的性能问题。这是由Sql Server的数据存储结构所决定的。

在SqlServer当中,数据库每8K为1页(Page),8页为1片(Extent)。而一行数据中的定长数据通常都存在一个页当中,而不定长数据如varXXX一类,只要这一行数据没有超出8060字节,也会存在于当前页中。上述的数据成为行内数据(In_Row_Data)。而对于一行超出8060字节的情况下,不定长数据会存入超长数据页(Row_Overflow_Data)。而类似Image、text等类型的数据,则会保存在大对象数据页(Large_Object_Blob,简称LOB)当中。关于这些知识的解释,请参见此处的官方说明

可惜的是,上述说明中并没有详细解释超长数据和大对象是如何在行内数据中记录的。对此,我们可以通过下述方式进行分析。

首先,我们需要通过下属语句找出保存数据的某一页:

select first_page from
sys.system_internals_allocation_units as u inner join
sys.partitions as p on u.container_id = p.partition_id
where
p.object_id=object_id('photo') and p.index_id=1 and u.type=1

这一个语句会得到一个类似如下的数据:
0x876543210500
这个16进制数据实际上分为两个部分:0x21436587是页码,而0x0005则是文件号码。注意,这两部分都需要对原始数据进行一个颠倒,有点类似BigEndian和LittleEndian的颠倒过程。

得到这两个数据之后,我们进一步将页码部分转换为十进制558065031,将文件码也转换成十进制,然后输入如下的命令:
dbcc traceon(3604)
dbcc page(databasename, 5, 558065031, 3)
dbcc traceoff(3604)

这一个命令会在消息中输出具体的存储信息,我们在里面首先找到这么一行数据:


stream = [Textpointer] Slot 0 Column 5 Offset 0xab Length 16
                 

请记住这个偏移量0xab,然后在这一行往上找第一个Memory Dump,比如:


Memory Dump @0x0000000024A9C060

0000000000000000:   30008d00 653e0500 02000000 80000000 †0...e>..........
0000000000000010:   00000000 16640000 00ecf496 00749800 †.....d.......t..
0000000000000020:   00000000 00e52001 00000000 00000000 †...... .........
0000000000000030:   00ffffff ffffffff 60000000 00000000 †........`.......
0000000000000040:   00000000 0000f03f d0000000 00ecf496 †.......?........
0000000000000050:   00749800 00000000 00000000 0013f8fe †.t..............
0000000000000060:   f1000000 0000f03f 00000000 0000f03f †.......?.......?
0000000000000070:   cd000000 00030000 00000000 01000000 †................
0000000000000080:   f89e015d 4a49ab3f 00000000 002f0000 †...]JI.?...../..
0000000000000090:   009c0370 7b0600ab 00bb80cb 00cb00cb †...p{...........
00000000000000A0:   00ef0001 4f1a4ecb 4ecd7e00 00f2fef8 †....O.N.N.~.....
00000000000000B0:   13000010 00000005 00010020 20202020 †...........    
00000000000000C0:   20202020 20202020 20202031 64616130 †           1daa0
00000000000000D0:   6230332d 62313866 2d343462 302d6231 †b03-b18f-44b0-b1
00000000000000E0:   62302d35 64353739 38366164 623037††††b0-5d57986adb07


途中高亮部分就是刚才偏移量所指出的16个字节的数据。根据观察,后8个字节10000000 05000100 和堆数据的行定位数据结构(row locator)非常相似。而该结构的含义如下:
页码(4字节) 文件号(2字节) 槽号(2字节)

需要注意的是,这是Little Endian编码格式。经过转换,页码为16,文件号是5,槽号为1。然后我们再次执行dbcc page命令如下:

dbcc traceon(3604)
dbcc page(databasename, 5, 16, 3)
dbcc traceoff(3604)


我们在消息输出中,找到槽号(Slot)为1的位置,就可以看到如下的数据:


Blob row at: Page (5:16) Slot 1 Length: 4900 Type: 3 (DATA)

Blob Id:21960150089728

而上面的二进制大对象块(Blob)编号21960150089728的十六进制值为0x000013f 8fef20000,正好是上面高亮部分十六个字节的前8个字节。换句话说,这个字段的大小并非存在行内数据当中,而是存在于大对象页当中,也就是上面的黑体字所显示的地方。

因此,当我们执行类似select sum(datalength(stream)) from photo where userId=@userid 这样的语句时,数据库系统再怎么优化,也需要读取这些页,而不是只需要读取行内数据即可。由于大对象通常都很大,其数据密度是相当低的,通常一页当中只有3-4行对应的字段值。除此之外,大对象的保存顺序和聚集索引不同,和数据的增删改的顺序有关,而与聚集索引本身的顺序无关。于是,我们基本上可以根据如下的方式估算一次上述查询所需要读取的数据量:
行数= select count(*) from photo where userId=@userid
读取次数 = 行数
读取数据量 = 行数 * 8K

也就是说,当用户数据达到1000行时,读取的数据量可能会达到8M,而且还是随机读取。而用两块普通15krpm的SAS硬盘做镜像,随机读取能达到4MBps就不错了。因此每次查询将会消耗2秒的时间,其它的查询只要内存中没有缓存,都注定需要等待。

那么,是否只能够退化到添加一个静态字段,然后通过代码来更新的境地呢?其实还有另一种方案:添加计算字段并对其做索引。这种方案对于查询远多于修改的系统来说,是一个很不错的选择,方法如下:

首先,在数据库的属性中,修改算术终止已启用(ArithAbort)为是(True)。这一步非常关键,否则当你完成后面两个步骤之后,将会无法对数据进行更改。然后用下列语句添加一个计算列:

alter table photo
  add dataSize as (datalength(stream))

然后再通过下面的语句添加一个索引:

CREATE NONCLUSTERED INDEX [IX_photo_datasize] ON [dbo].[photo] 
(
[userId] ASC,
[photoId] ASC
)
INCLUDE (
[datasize]
) WITH (STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = ON, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON)

需要注意的是,上面的ONLINE一定要设置成ON,这样可以让系统不要锁柱整个表(仍然会短暂锁住,但几乎感觉不到)。而datasize一定要设置成包含列,因为这一列基本上不会用于检索,出现相同值的可能性也非常低。而是否应该添加其它的列(包括包含列),例如photoId,则需要根据根据实际情况分析处理。

这三个步骤完成之后,再执行查询select sum(datalength(stream)) from photo where userId=@userid,你会发现执行任务计划已经使用了IX_photo_datasize了!注意,你连sql查询语句都不需要修改,SqlServer会自动识别出datalength(stream)等同于datasize字段,而datasize字段存在于IX_photo_datasize索引。很先进吧!

看到这里,有人就会问了:为啥不直接添加一个持久化的计算字段呢?这是因为一旦设置为持久化,将会导致行内数据的结构发生变化——多了4个字节的数据嘛。于是,这将会强迫数据库系统对已有的数据进行修改。对于数据量较小的库来说还好,而对于数据量巨大的库来说,将会导致系统长时间锁定该表而无法正常提供服务。这在互联网应用上,通常来说会导致用户的强烈反应。当然,你也可以采取复制一个数据库,并且设定为只读的方式,让服务在只读状态下运行,于此同时则修整真实数据库,修整完之后再切换回来的办法。但是,为什么要采取一个这么复杂的步骤,来获得几乎相同的效果呢?

不过,需要注意的是,以下情况会导致索引失效:
  • 查询中的函数不是计算字段中函数的一部分。比如说,查询字段设置成了IsNull(datalength(stream), 0),而查询却为select datalength(stream) ……
  • 查询中包含了某种导致索引失效的情况,比如:
    select sum(streamsize) from
      (
      select row_number() OVER (order by userId, photoId) as rowno,
             datalength(stream) as streamsize
        from photo
        where userid = @userid
      ) t
    where rowno between (1, 1000)
对于第一种情况,你需要修改查询字段,而对于第二种情况,则必须修改sql语句,比如说改成如下所示:
select sum(datalength(stream)) from photo as p inner join
  (
  select row_number() OVER (order by userId, photoId) as rowno,
         userId, photoId
    from photo
    where userid = @userid
  ) as t on p.userId = t.userId, p.photoId = t.photoId
where rowno between (1, 1000)

上述的方法有如下几个巨大的好处:
  • 即便前期规划出现问题导致数据库结构不包含stream大小的静态列,也可以后期修改;
  • 修改时服务不需要下线维护,同时也不会导致明显的服务阻塞感;
  • 几乎不需要修改任何程序,即便需要修改,程序修改和数据库修改也没有严格的先后顺序,在同时修改完成之前,性能几乎没有明显的下降,服务连贯性非常好;
  • 数据一致性非常好,不会受到程序设计、更新等人为失误的影响;
  • 性能提升非常明显,由于数据密集度很高,甚至比静态字段效果还要好。

当然了,硬币的另一面是:
  • 你需要修改数据库的默认行为(ArithAbort要改成True,一般影响不大);
  • 对于select * from photo where ...这样的查询,在执行计划中将会比原来多出一个计算datalength(stream)的步骤。不过由于stream本来就必须从文件中读取出来,因而计算datalength基本上不会增加可感觉到的运算时间;
  • 存储空间和写入次数都比静态字段稍多一些。其实是多出2倍以上(假如索引去掉PhotoId列的话),但是因为每一行的数据量其实很小,因此通常可以忽略不计。比如对于1亿行的数据的表使用上述方法,可能会多出至少400MB的数据量。而对于有着1亿行数据的表来说,本身的数据量就不会少于2.4GB(4字节userId+4字节PhotoId+16字节stream的row locator),这还没有算上stream部分占用的空间。因此,基本上可以忽视其空间上的影响。
怎样,这个方案不错吧?

2012年4月10日星期二

坚决捧庇

2012年4月10日,在神奇的大地上发生了一件里程碑一般的事件:西南王被正式立案调查,原因是其家属涉嫌谋杀一个英国公民。有意思的是,也是用屁眼想也知道会发生的是,当晚开始,报纸电视媒体铺天盖地的出现了“坚决捧庇中央的决定”。我改了一下字眼,捧庇这个词在报纸媒体上对应的是拥护。拥,其实搞不好就是捧,捧臭脚的捧;护,搞不好就是庇,包庇的庇。

“这不是挺好的嘛?该处理的就处理,没有包庇啊,难道不应该拥护吗?”搞笑的是,3月30日至4月3日还来了个轰轰烈烈的微博禁止评论,集中处理谣言的行动,甚至据传抓了很多人。奇怪了,都传什么谣言呢?现在不正在变成中央文件了么?感情原来是“朕还没发话呢,尔等则能泄露天机”!跟曹操似的,杨修,该杀。

这种堵塞大众口耳的自欺欺人行为,实在是一种猪头作为。其后果自然是有能力的人可以通过强力行动,将其违法行为遮蔽、消除,并迫害知情人使其无法透露真相。除此之外,还可以通过各种渠道,强行发布美化、神化其形象地位的谣言。这不是捧臭脚是什么?这不是包庇又是什么?

有意思的是,3月9日还信誓旦旦声称对其亲属的诋毁是谣言云云,时隔一个月零一天就被公布亲属涉嫌谋杀外国人。这是多么的讽刺啊!而这个家人谋杀藏尸的说法,在之前的微博上就已经被流传。我最早好像是在杨海鹏在上海的微博中看到的,当时没有提外国人,昨日听说后感觉甚是突然。而3月底4月初清洗微博,我也曾经想过那位杨先生是否已经被抓。前几天一翻微博,发现过的好好的,还说了“我只不过提前十来天说了中央要说的话,又不是谣言,怎么会不安全呢?”诸如此类的。现在看来,果真如此。

而现在这个消息的官方,又可以琢磨出许多味来:

  • 首先是薄熙来的狂妄自大,不学无术,自作聪明,最后玩火自焚。之所以这么说,是因为这显然是一个国际事件,这么大的事情,居然以为可以瞒天过海。以为可以漫天过海就算了,居然还以为有永远的朋友(估计自己洗别人脑洗惯了,自己都相信永远的忠诚了)。以为有永远的朋友就算了,居然在已然发生背叛的时候,还以为这事情能继续隐瞒下去。以为可以隐瞒下去就算了,居然忘了王立军去过美领馆。如果没有忘记,那就是居然以为美英会不知道这事,会以为不会给施加压力,会以为全部中央高层还有他的靠山都是他的伙计会为他抵挡乃至当炮灰。
  • 其次也知道了为什么王立军能够笃信去美领馆一定能成。这是因为单单就这一件事情,就足以让整个高层不敢隐瞒真相。否则最终英国会不干,世界会哗然,多年以来包庇腐朽权力的公开秘密,就彻底变成公开的知识了。而公开则导致“立功表现”,同时成为“国际新闻”之后,最终开脱死罪的可能性就相当的高了。
  • 最后,如果现在复盘事件发生开始的那一刻——王立军投奔美领馆,也许渝牌装甲车奔袭包围真是一个下下策。或者立刻软禁王立军家属,同时发短信告知“家属在我处,一切安好,你为我做贡献力保你无事。若有事,共进退”,会是更好的选择。而实际上事件的进展实际上是进入了囚徒困境:薄熙来先弃子搏自己的高升,而王立军则弃身份搏自己不被最严肃的方式处理掉。最终结果就是两者都没有好果子吃。

实际上现在的结果基本上是必然发生的,其必然性可能可以追溯到薄一波的父母那一代,至少可以在薄熙来那一代找到原因。一波同志没有教导好自己的小孩,居然发生儿子毫无底线批斗父亲,甚至到重庆后仍然重复着当年那些显然的错误。而这种教育缺失的问题,很可能是一波的父母就没有重视。而这种对后代不负责,乃至赞扬后背不负责任的精神,在某次对瓜瓜的采访中也可以看到。具体已经不记得了:大致是说瓜瓜很叛逆,父母觉得很可爱很特别之类。年代久远,有错勿怪。

而新闻内容更有意思:薄谷开来(薄熙来妻子)及其子曾经和被害人关系良好,后……。薄谷开来和张晓军(薄家勤务人员)有重大作案嫌疑。咦,为啥后面就没有“其子”的关系了呢?不好说,现在不知道。也许随着案情进展会变得更加有趣,因为现在已经彻底进入囚徒困境了:谁坦白谁从宽。而薄的倒台进程已经进入了不可逆的阶段,完全没有任何保其家的意义,外围的猢狲及其靠山,也没有任何必要进行保护,兴许踩上两脚才有利。于是张晓军还会爆出些什么,就真不好说了。至于其子,我胡乱猜测一下,兴许是有目的的保护,有人做出了极大的牺牲。(比如说,大包大揽。)

一个“唱红大黑”的演员最终倒在了“黑贪”之下,比当年“潜伏”的演员是逃犯还有具有戏剧性。

下面是一些链接:
http://www.iask.ca/news/china/2012/0410/128738.html
http://www.aboluowang.com/news/2012/0325/%E5%8F%97%E5%91%A8%E6%B0%B8%E5%BA%B7%E5%8A%9B%E6%8C%BA%E8%96%84%E7%86%99%E6%9D%A5%E4%BA%A2%E5%A5%8B%E9%80%BC%E5%AE%AB%E2%80%9C%E5%BC%95%E7%81%AB%E7%83%A7%E8%BA%AB%E2%80%9D14%E5%9B%BE-147838.html
http://www.aboluowang.com/news/2012/0215/%E7%8E%8B%E7%AB%8B%E5%86%9B%E5%AE%B6%E5%B1%9E%E5%9C%A8%E9%87%8D%E5%BA%86%E3%80%81%E5%8C%97%E4%BA%AC%E8%A6%81%E4%BA%BA%E5%9B%9E%E5%A4%8D%E6%98%AF%E2%80%9C%E6%97%A0%E5%8F%AF%E5%A5%89%E5%91%8A%E2%80%9D-144981.html
http://opinion.dwnews.com/news/2011-06-13/57801786.html
http://www.aboluowang.com/news/data/2012/0411/article_149041.html
http://beyondfirewall.blogspot.com/2012/03/319_22.html
http://www.wenxuecity.com/news/2012/04/08/1714907.html

2012年4月9日星期一

.NET应用非托管内存异常的简单排查

我们知道.NET应用如果出现问题,在Windows操作系统上面可以使用Windbg来查找问题所在。通常来说,.NET应用的问题会出现在托管部分。此时,只要我们的WinDbg在加载完CrashDump,或者附加上进程之后,就可以使用以下命令开始查找问题:
>.loadby sos clr
或者
>.loadby sos mscorwks
或者
>.loadby sos coreclr

其中第一个是.NET 4.0版本所使用的命令,而之前的其它桌面版需要使用第二个,最后一个是SilverLight 3/4所使用的版本。然后,我们就可以使用!eeheap/!dumpheap/!dumpobj之类的命令来进行内存问题的排查。

但是,有的时候问题并非在托管部分出问题。比如说,当我们输入:
>!eeheap -gc
之后显示:

……
------------------------------
GC Heap Size:    Size: 0xa0ec288 (168739464) bytes.

而这个Dump显然有大几百兆甚至1GB以上,此时就需要考虑非托管部分的问题了。首先,你需要输入:
>!address -summary
这一个命令可以给出地址空间的使用情况,这时候可能你会发现地址空间被字体占用光了,或者如这里所显示的那样,可能被非托管代码所占用了:

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    382      7fe`61d74000 (   7.994 Tb)           99.92%
                        2825        1`8c91f000 (   6.196 Gb)  95.75%    0.08%
Image                                  1986        0`103e2000 ( 259.883 Mb)   3.92%    0.00%
Stack                                   174        0`014bd000 (  20.738 Mb)   0.31%    0.00%
TEB                                      58        0`00074000 ( 464.000 kb)   0.01%    0.00%
NlsTables                                 1        0`00033000 ( 204.000 kb)   0.00%    0.00%
ActivationContextData                     5        0`0000d000 (  52.000 kb)   0.00%    0.00%
CsrSharedMemory                           1        0`00009000 (  36.000 kb)   0.00%    0.00%
PEB                                       1        0`00001000 (   4.000 kb)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                            2290        1`8affa000 (   6.172 Gb)  95.37%    0.08%
MEM_IMAGE                              2716        0`11529000 ( 277.160 Mb)   4.18%    0.00%
MEM_MAPPED                               45        0`01d59000 (  29.348 Mb)   0.44%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                382      7fe`61d74000 (   7.994 Tb)           99.92%
MEM_RESERVE                            1685        1`4709a000 (   5.110 Gb)  78.96%    0.06%
MEM_COMMIT                             3366        0`571e2000 (   1.361 Gb)  21.04%    0.02%

--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE                         1513        0`4427a000 (   1.065 Gb)  16.46%    0.01%
PAGE_EXECUTE_READ                       342        0`0d3e9000 ( 211.910 Mb)   3.20%    0.00%
PAGE_READONLY                           906        0`03837000 (  56.215 Mb)   0.85%    0.00%
PAGE_WRITECOPY                          306        0`01978000 (  25.469 Mb)   0.38%    0.00%
PAGE_EXECUTE_READWRITE                  147        0`006b8000 (   6.719 Mb)   0.10%    0.00%
PAGE_EXECUTE_WRITECOPY                   74        0`0020a000 (   2.039 Mb)   0.03%    0.00%
PAGE_READWRITE|PAGE_GUARD                77        0`0010a000 (   1.039 Mb)   0.02%    0.00%
PAGE_EXECUTE                              1        0`00004000 (  16.000 kb)   0.00%    0.00%

--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                      5`16fea000      63d`67e66000 (   6.240 Tb)
                           0`80be1000        0`1f40f000 ( 500.059 Mb)
Image                                   644`23535000        0`0125a000 (  18.352 Mb)
Stack                                     0`00a40000        0`0007c000 ( 496.000 kb)
TEB                                     7ff`ffe82000        0`00002000 (   8.000 kb)
NlsTables                               7ff`fffa0000        0`00033000 ( 204.000 kb)
ActivationContextData                     0`000b0000        0`00005000 (  20.000 kb)
CsrSharedMemory                           0`7efe0000        0`00009000 (  36.000 kb)
PEB                                     7ff`fffdb000        0`00001000 (   4.000 kb)

如果怀疑是非托管堆上面的内存泄漏(或者过度占用)问题,则需要继续进一步使用下面的方法来进行排查:
>!heap -stat
该命令将会输出当前存在的堆都有哪些,分别都有多大。比如,你可能会看到:

_HEAP 0000000009040000
     Segments            0000000b
         Reserved  bytes 000000003ff80000
         Committed bytes 000000002f5cb000
     VirtAllocBlocks     00000000
         VirtAlloc bytes 0000000000000000
_HEAP 00000000000c0000
     Segments            00000004
         Reserved  bytes 0000000000800000
         Committed bytes 0000000000706000
     VirtAllocBlocks     00000001
         VirtAlloc bytes 0000000004de0000
……
这样的输出,比如上面黄色高亮的部分,就显示已经提交的内存有794MB之多,显然不是托管堆169MB所能承载的。此时,你就需要进一步进行分析了。

等会儿,刚才的命令是否还有类似这样的输出呢:

*************************************************************************
***                                                                   ***
***                                                                   ***
***    Your debugger is not using the correct symbols                 ***
***                                                                   ***
***    In order for this command to work properly, your symbol path   ***
***    must point to .pdb files that have full type information.      ***
***                                                                   ***
***    Certain .pdb files (such as the public OS symbols) do not      ***
***    contain the required information.  Contact the group that      ***
***    provided you with these symbols if you need this command to    ***
***    work.                                                          ***
***                                                                   ***
***    Type referenced: ntdll!_PEB                                    ***
***                                                                   ***
*************************************************************************
嗯,这时因为WinDbg还没有正确的配置上符号文件的路径。请点击File菜单中的Symbol File Path,并且在弹出框中填写如下路径:
SRV*d:\symbols*http://msdl.microsoft.com/download/symbols
并选择上Reload(或者关掉重开WinDbg),否则你将无法使用Google到的很多命令,比如:
>!heap -a 9040000
(会显示上面那一大段内容,外加“Invalid type information”这样的提示。)

如果你实在不想使用微软的远程Symbol服务器,或者因为网络原因无法使用,也可以在!heap命令后首先添加-p来试试看。比如说,将上面的命令改成:
>!heap -p -a 9040000

具体可以使用那些命令,还是建议大家使用!heap -?来查看。同时,上述临时方法输出的信息格式,与前一条命令的输出格式稍有不同,但基本不影响使用。

在使用上述命令之前,建议先针对较大的堆使用如下的命令进行分析:
>!heap -stat -h 9040000 -grp A

>!heap -stat -h 9040000 -grp B
>!heap -stat -h 9040000 -grp S


这几个命令按照不同的分类方法,对这一个堆进行分析,分别输出:
单次分配最大的几个分配;
哪几个大小的块的分配次数最多;
哪几个大小的块的总分配数量最多。

当你能确定某种大小的分配,其数量或者大小比较不正常,可以用以下命令进行查看:
>!heap -flt s 8000
这就可以打印所有大小为32K的分配情况,包括地址,输出可能类似如下:

……
        0000000009042cd0: 02010 . 07c20 [01] - busy (7c10)
        000000000904a8f0: 07c20 . 07c20 [01] - busy (7c10)

        0000000009061d50: 07c20 . 07c20 [01] - busy (7c10)
        0000000009069970: 07c20 . 07c20 [01] - busy (7c10)
        0000000009071590: 07c20 . 07c20 [01] - busy (7c10)
        00000000090791b0: 07c20 . 07c20 [01] - busy (7c10)
        0000000009080dd0: 07c20 . 07c20 [01] - busy (7c10)
        00000000090889f0: 07c20 . 07c20 [01] - busy (7c10)

……


上述的输出可能会很大,甚至超出了WinDbg命令缓冲区的大小。因此,你可以通过下面的方式,将整个输出写入到一个日志文件当中:
>.logopen "c:\logfile\heap.log"
>!heap -a 9040000
>.logclose



针对怀疑有问题某一个堆上分配,可以使用如下命令查看其内容:
>db 90889f0
……
>d
……

如果这些内容是很容易辨识的文本内容,或者你对於这里面的二进制内容比较熟悉,就可以很容易知道大概会是些什么东西,可能是什么地方出现问题。但是,多数情况下是看不出来的,或者因为内容太多不可能注意筛查出有价值的内容。此时,我们可能需要查看这些堆上内存分配的调用栈情况。

要实现调用栈的分析,首先需要修改系统的一些设置,使得针对你当前这个应用的Dump会包含申请该堆的时候,调用堆栈的情况。也就是说,如果你不做如下的更改,将不知道这些内存是怎么分配出去的。修改的方法有三种:
1、运行命令"gflags.exe /i YourApplication.exe +ust",这个gflags是WinDbg安装附带的工具之一,就在WinDbg所在的目录中;
2、在开始菜单中,WinDbg所在的位置还有另一个应用,就叫做gflag。这时一个图形化界面。在Image File的页面中输入你要调试的应用程序名称,例如YourApplication.exe,并且按一下TAB键。接着在下面的Create user mode stack trace database上面打一个勾,然后确定即可;
3、或者,你可以直接修改注册表。在“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\”当中添加一个YourApplication.exe的结点,然后在其上添加一个名为GlobalFlag的双字类型的值,值等于0x00001000(4096)。
上面这三种方法可以参见这里以及这里

修改完之后,你需要重新在出现症状时做dump,因为之前dump的时候因为没有以上设置,是不会包含申请Heap时的用户空间堆栈情况的。而当你Dump完之后,记得用上述方法将之前的修改设置恢复原状。(我没有尝试过,不知道对于高负荷的线上系统,是否会造成很大的影响,最好谨慎一些。)

针对新做的dump,你需要再次使用上述方法,找到有问题的堆,再次执行如下的命令:
>!heap -p -a 90889f0

此时的输出可能就会如下所示:
    address 000a6ca0 found in
    _HEAP @ a0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        000a6ca0 0b2b 0000  [00]   000a6cb8    05940 - (busy)
        Trace: 2156ac
        7704dab4 ntdll!RtlAllocateHeap+0x0000021d
        75c59b12 USP10!UspAllocCache+0x0000002b
        75c62381 USP10!AllocSizeCache+0x00000048
        75c61c74 USP10!FindOrCreateSizeCacheWithoutRealizationID+0x00000124
        75c61bc0 USP10!FindOrCreateSizeCacheUsingRealizationID+0x00000070
        75c59a97 USP10!UpdateCache+0x0000002b
        75c59a61 USP10!ScriptCheckCache+0x0000005c
        75c59d04 USP10!ScriptStringAnalyse+0x0000012a
        7711140f LPK!LpkStringAnalyse+0x00000114
        7711159e LPK!LpkCharsetDraw+0x00000302
        77111488 LPK!LpkDrawTextEx+0x00000044
        76a4beb3 USER32!DT_DrawStr+0x0000013a
        76a4be45 USER32!DT_DrawJustifiedLine+0x0000005f
        76a49d68 USER32!AddEllipsisAndDrawLine+0x00000186
        76a4bc31 USER32!DrawTextExWorker+0x000001b1
        76a4bedc USER32!DrawTextExW+0x0000001e
        746051d8 uxtheme!CTextDraw::GetTextExtent+0x000000be
        7460515a uxtheme!GetThemeTextExtent+0x00000065
        74611ed4 uxtheme!CThemeMenuBar::MeasureItem+0x00000124
        746119c1 uxtheme!CThemeMenu::OnMeasureItem+0x0000003f
        74611978 uxtheme!CThemeWnd::_PreDefWindowProc+0x00000117
        74601ea5 uxtheme!_ThemeDefWindowProc+0x00000090
        74601f61 uxtheme!ThemeDefWindowProcW+0x00000018
        76a4a09e USER32!DefWindowProcW+0x00000068
        931406 notepad!NPWndProc+0x00000084
        76a51a10 USER32!InternalCallWinProc+0x00000023
        76a51ae8 USER32!UserCallWinProcCheckWow+0x0000014b
        76a51c03 USER32!DispatchClientMessage+0x000000da
        76a3bc24 USER32!__fnINOUTLPUAHMEASUREMENUITEM+0x00000027
        77040e6e ntdll!KiUserCallbackDispatcher+0x0000002e
        76a51d87 USER32!RealDefWindowProcW+0x00000047
        74601f2f uxtheme!_ThemeDefWindowProc+0x000001b8


有了这些信息,就基本可以确定这些内存是通过什么样的途径分配出去的了。至于说为什么没有被释放,或者如何才能释放,只能自行查看自己的代码了。







2012年4月6日星期五

2011年IT界工资摘要

这里有人给出了一个关于亚太地区五国(澳纽港新日)的IT界调查与分析,纯英文。我简单的摘要和再分析如下:(澳大利亚以悉尼为样本进行分析,毕竟主要工作机会在悉尼。)

  • 亚太地区中,澳大利亚、新西兰以及香港的IT环境前景相对较好,其中香港最好,而新加坡的呈中性,日本变差;
  • 日本、香港和澳大利亚的IT工资排列前三,而新加坡和新西兰的工资水平与前者有一定差距;
  • 除了日本以外,其余四国的工资都是整体向上,日本加薪的少,减薪的多;
  • 无论哪个地区,减薪的首要原因都是失业(以及换的工作比原来更低端)所致,但对于日本来说,经济状况导致减薪占比最高,而新西兰和新加坡在“其它”一项的选项显著增高。由于不知道“其它”代表什么,无法分析,个人猜测可能是转行或者自己决定休息一下等。日本选择其它的非常少;
  • 对于加薪来说,几乎所有国家的主要原因都和“我表现很好”有关,尤其是新西兰。但这里值得注意的是,对于澳大利亚来说,升职导致加薪是首要原因。新西兰几乎不会同时涨工资和奖金,日本则涨奖金的比例显著高于其它国家(也就是说每年收入浮动机会更高);
  • 认为收入合理与不合理的,在所有国家几乎都差不太多,除了新加坡以外。新加坡有半数人认为自己的薪资不合理(这一点很意外,我原以为是日本,因为薪资下滑最多。也许是期望越大,失望越大);
  • 在认为收入满意和不满意的问题上,多数人都认为满意,除了日本以外。日本认为不满意的人数高于满意的,这点比较合理,说明日本收入下滑严重。有趣的是,新加坡认为不满意的比例和日本一样高,只不过因为非常不满意和不确定的部分较少,导致满意的略高于不满意的(我听说有不少澳大利亚的PR甚至入籍的人士,近年来选择去新加坡。看来是不知道听说了什么样的传说过去后,发现可能生活成本照样不低,而收入却没有提高甚至下降。);
  • 认为通过跳槽来提升自己在薪资上陶家花间的能力,在所有国家中都受到大多数人的认同。其中比例最高的是香港,最低的是新西兰;(说明新西兰人的生活真的好安逸,这是他们的价值观,后面还会看到)
  • 对于认为薪资得到提高的最重要原因这个问题,所有国家的大多数人都认为首先是个人的能力表现和态度。其次则是达成一个正面的结果,除了香港之外(他们认为排第三重要)。而第三则是公司的成功,除了香港之外(他们认为第二重要)。这也解释了为什么香港人那么注重跳槽。换句话说,工作的结果如何不重要,如果想对于这个公司是否是一个大公司来说。日本也有类似的趋势,但没那么严重;
  • 在什么理由可以让你接受较低工资的问题上,各国之间的差异比较大(列出的原因按照比例进行排序):
    • 澳大利亚人注重生活质量:在家办公、弹性工作时间、地点较近,以及更多的假期;(如果你享受生活,可以考虑这里,因为你的这种想法在当地很主流,不会受到质疑。)
    • 新西兰人也很注重生活质量:弹性工作时间、更多假期、地点较近,以及在家办公;(可见新西兰的堵车问题没有澳大利亚那么严重,他们共喜欢有更多的自由时间,更多的享受生活。如果你享受生活,同时钱多钱少无所谓,新西兰真是天堂。)
    • 香港人比日本人更努力赚钱:有更好的职业发展(一半人)、在家办公、更多假期以及企业文化;(即便那么注重职业发展,在家办公和更多假期看来还是人类的共同追求。同时不要看有半数人看重职业发展,就以为香港人不懂得生活,那就错了。他们还是很会享受生活的,不然满记这种甜品店怎么生存,更不要说出名了。只是他们的买房压力比北京上海更大,其压力对于香港IT界初级职员来说,估计基本可以等价于北京一个普通文职人员的压力。)
    • 新加坡工作生活较为平衡,同时注重创新或者激情: 弹性工作时间 、在家办公、更好的职业发展、有机会为一个有魄力的领导工作;
    • 日本人也比较平均,但更注重稳定:在家办公、弹性工作时间、企业文化以及更多假期;(很惊讶,因为职业发展在日本排在第六,在工作地点之后。似乎对他们来说,职业发展是顺其自然不可强求的事情。)
  • 几乎所有国家的大多数人都会每年和老板商讨一次薪水,除了新加坡之外,他们近半数人从不讨论薪水。也就是说,如果你想要做打工皇帝,去新加坡可能不是一个好的选择。因为当你一讨论“哎呀老板,去年就没有给我加薪,今年是不是给我加点”,就会遭鄙视,因为你这绝对不是主流的想法。而新西兰则每年讨论加薪的比例最高,达到65%。换句话说,这才是当个打工族的天堂(也是享受生活的天堂)。另一个值得注意的是,除了每年讨论薪资占大头之外,从不讨论占二头,两年及以上讨论的几乎属于小众。所以,要么你属于鼓起勇气年年问工资的,要么就属于从不过问的;
  • 对于所在公司会有所增长的信心问题上,大多数人都认为有点信心,所有国家无一例外。香港和日本在非常有信心这个选项上的比例,显著较其他国家为少。尤其是日本,完全没信心的比例接近三分之一。有点信心和非常有信心加起来最高的新西兰,占87%,其次是新加坡的85%。(这个结果很令人意外,我的解读是,新西兰人心态真好,新加坡真心认为自己要创新了。)
  • 东家对于明年因为优秀雇员跳槽导致效能下降的问题,总体上还是有点担心的。大多数国家的分布呈现纺锤形,多数是有点担心,很担心和完全不担心的较少。但澳大利亚真是个特例,两端的比例较其他国家显著增加。可能是调查方法的问题,也可能是其国民有“非黑即白”的思维倾向,个人倾向前者。
  • 对于明年打算雇人,雇什么级别的人这个问题上,各国表现差异很大(按比例从高至低):
    • 澳大利亚:职员、高级工、入门学徒,以及不雇人。特点:不雇人比例是这五国最高的;(12年看来不容易啊!)
    • 新西兰:职员、高级工、入门学徒,以及管理人员。特点:对初级工的需求比例异常高,近一半;
    • 香港:职员、高级工、管理人员,以及不雇人。特点:前三者比例基本均匀,综合为五国最高(102%,因为可以多选);
    • 新加坡:职员、高级宫、执行官,以及管理人员。特点:执行官这一级别的比例显著高于其他国家,想做CXO的可以去那边试试;
    • 日本:管理人员、高级工、职员,以及执行官。特点:唯一一个对管理人员需求比例高于其它的,也是唯一一个职员需求比例不是最高的(第三)。(我个人解读为,此地喜欢按部就班,注重管理。)
  • 对于每周工作时间的问题上,各国还是有一定的差异的,但可以简单分为这么两组:
    • 大洋洲国家:新西兰和澳大利亚都显著的集中在45小时以下。尤其是新西兰,非常规范,显著集中在每天工作8小时这个法定认知上,其超时工作比例最低。澳大利亚也不赖,虽说超时比例稍高,但是工作时间在38小时以内的比例也是各国当中最高的;
    • 东南亚国家:香港,新加坡以及日本,在这方面的表现不太理想。39-45小时,以及46-55小时这两组的比例基本相同,甚至有15%-19%的比例是每周工作超过55小时的;
  • 对于加班压力的表述上,所有国家的大部分人都认为有时候会有,其中新西兰比例最高(我真晕了,应该解读为Kiwi对此问题比较敏感)。而日本人的比例分布较为平均,总加班以及一直加班的比例也是最高的,香港紧随其后。同时日本的从不加班的比例也是最高的,与澳大利亚并列。而香港和新加坡生成从不加班的比例最低。偶尔加班、很少加班以及从不加班的比例加起来,则数新西兰最高,澳大利亚紧随其后。(结论,如果你不喜欢加班,希望有更多的私人时间享受生活,请考虑新西兰。当然,新西兰钱少,如果你在意这个,可以退而求其次选择澳大利亚。)
  • 对于“你认为什么样的培训对你的职业生涯最有帮助”这个问题,所有国家都认为最主要是“领导力和管理技能”,但在排第二的问题上面就有一些分歧了:
    • 澳大利亚:项目管理技能;(新西兰紧随)
    • 新西兰:技术和软件训练;(澳大利亚紧随。这两个国家似乎都不认为沟通是个问题,很可能还是亚洲国家人际关系过于复杂造成的差异。)
    • 香港:沟通技巧;(日本紧随,然后新加坡。为什么?为啥亚周国家都认为沟通最重要?还是说这些国家天生不会沟通,或者太注重人际关系?)
    • 新加坡:项目管理(技术和软件培训、沟通技巧紧随其后);
    • 日本:比较平均的分布在技术和软件培训、项目管理,以及沟通技巧上。其中沟通技巧比例是除香港外第二低的(香港重视技术吗?心存疑虑);
  • 关于调查对象的性别构成,都是男性占绝大多数;
  • 关于调查对象的年龄构成,青春饭啊:主要集中在25-45岁这一个区段,往后减少较明显。其中日本46-55岁之间比例显著偏高,25-35岁之间的比例显著偏低,个人怀疑可能是抽样方法问题造成的;
  • 关于工作经验的问题,抽样结构显示主要集中在6-19年的区段,日本的部分仍然看到20年以上比例显著增加。总之,看到这样的比例很欣慰,说明国外IT的黄金时间显然比国内长;
  • 关于是否拥有相关专业学历,大部分国家对此的重视程度只能说Soso,除了新加坡,后者3/4的人拥有相关学历;
  • 关于是否长期全职问题,所有国家大部分都是的,但澳大利亚在临时/合同工的比例上是显著高于其他国家的,占了1/3。兼职工作也是澳大利亚比例最高;
  • 关于行业细分:
    • 澳大利亚、新西兰、新加坡以及日本主要是IT软件技术业,其次是金融业;
    • 香港金融业占了近2/3;

另外,由于该分析主要针对澳大利亚的分析,还有一些各大城市的具体职业细分薪资情况。这里做一些简单的摘要:

  • 悉尼收入最高,墨尔本次之,剩余的差别不太大;
  • 实际上超过5年的.NET开发人员,各地差异都已经不太大,可以认为是各地生活成本的差异。但是5年内的.NET开发人员,差异非常大;
  • .NET开发和Java开发的收入几乎无差异;
  • 做开发做到15年可能基本到头,后面的收入增长空间很有限。可以考虑转岗,比如当个Manager。不过对于华人来说,语言文化关这个坎很难。当然,拿到10万每年以上,已经在当地很有高度了;
  • 如果这个调查是真实的,如果你去悉尼,就算你经验很少,能力soso别太懒,按照现在的汇率,税前年收入30W人民币,那是撒撒碎啦。
话说澳大利亚税还是比较重的,不过和中国的比起来,个人猜测可能并不会高太多。尤其是税高了,痛苦指数却可能会下降。比如你只要一想,我再也不会喝到三聚氰胺奶,或者皮革奶,或者地沟油,顿时你就幸福了。同时你也会知道你的税都用来干什么,尤其大部分不是用在查不出来有多少的三公消费上面,顿时你就又感到幸福了。还有,当你自己开个小公司的时候,只要交3位数的钱就开了,也不会有什么人来强制你购买什么税控机,请吃饭什么的,顿时你就又感到幸福了。

当然了,那个地方都有不幸福的东西,看你怎么想了。尤其要想想,哪些是你可以努力避免的(比如住一个好点的区,少遭抢),哪些是你压根无法避免的(比如呼吸点PM2.5很高的空气)。





2012年4月2日星期一

昨天大吃后随感

昨天老婆突然想吃香喝辣,于是忽悠了一群前狼烟同学小聚。愚人节忽悠人有时有意思,都不知道该不该相信好。

席间大家谈到一个近期的出名事件:大陆微博被禁止评论了。这个事情我倒是知道的,之前就在推上看到类似的信息,再加上有人给我转了个东西,结果我只想评论而不得。因为不太关心这件事情,所以自然也不知道后续还抓了很多“造谣”的人,据说有1000多。这个数字我倒是觉得稍微有点吃惊——抓这么多人能增加GDP呢,还是能够填平社保的账面窟窿呢?

其实除了稍稍有点吃惊之外,我已不太在意。中国政府就像一个满脸长满了G点的妓女,别人随便扫一眼她脸上任何地方,她就高潮了。高潮就算了,还强迫所有路人甲乙丙丁全部脱光衣服给她检视,好强行给你提供为你好的服务。如果再变本加厉一点,估计真要开始满大街的强暴人了。

席间又聊到腾讯,原来挺憎恨该公司的同学居然说现在没那么憎恨了。记得以前他说过,他师兄原来给腾讯做过珊瑚版,刚开始腾讯还给予支持的,但过了两年就转手告人家侵犯版权。是不是陷害我就说不清楚了,但从这个公司的各种山寨来看(比如近期山寨人家的Draw something),真是没有什么底线。他说他之所以不那么憎恨腾讯,是因为从这个公司能够看出政府是什么样的,人民是什么样的。说的也是,国民本身的素质就不算高,政府的行为更是没有任何底线。有这样的人民和政府作后盾,又如何能够做事情有点底线呢?要是你有底线,别人没有底线,而政府有不惩罚没底线的甚至保护他,你如何玩得过人家?

聊到这个公司,就说到消费的问题。因为这朋友的朋友做微游戏,同样的游戏在新浪销售收入只有腾讯的1/10。原因在于用户群的差异:新浪都是上班的白领,偏向80以后;而腾讯的多是尚未工作的小毛孩,以及刚工作的90后。而白领通常的不愿意在这种无法增加其自身价值的事情上多花一分钱,倒是愿意在穷三代的单反上面投入一些。而90后则据称毕业后两极分化非常大,不是高富帅就是爱穷丑。前者不是有很多零花钱,就是挣来的钱也不需要买房买车之类的;而后者则挣得很少,少得可能一辈子都挣不来一套房,于是挣来的钱对他们来说也是没用的。于是这两者都不约而同的在腾讯的各种钻、服饰、游戏上面浪费钱,或者是到酒吧等地方消费。这种现象甚至很明显的在iphone上购买各种应用上也是如此。

也许有人解读为90后的消费观和我们有极大地差异,而我却宁愿认为中国正在形成一个垮掉的一代,并且没有任何自然人、法人乃至政府国家有意愿或者能力去改变。一个巨大的车轮沿着历史的轨迹飞速前进,碰到了现代文明这堵墙的后果,就是Off road——不上道。