赵翔鹏的Blog - Xiangpeng's Thinkpad

关注形式化方法,Windows性能分析,还有各种跟programming相关或不相关的事儿

Windows里跟performance相关的一些概念

1、kernel里的Cache Manager

提供了在ReadFile、WriteFile时的cache,包括read-ahead和write-behind机制。原理是把application本来应该让file system服务的I/O request截走,转给cache manager自己拥有的一小块memory。

Cache manager就像一个应用程序,它只管虚拟内存,真正的物理内存管理都由memory manager做。所以cache那一部分memory也可能被page out。

只针对普通的file,dll、exe文件不会read-ahead。更精确的说,凡是用memory-mapped file方式读取的文件,都不会被read-ahead。

2、Prefetch:

在启动一个Process时,总要load一些dll,常用的几个icon等。如果等用到了再去读就比较慢,而且硬盘磁头会来回跑路。prefetch会根据上次process运行的历史记录,按照它们在磁盘上的顺序预读这些dll。

因此,一个程序第一次运行时可能硬盘要转很久,但以后再运行就快了。Prefetch对Boot也有类似的优化,会自动defrag启动时用到的文件。

可能的问题:上次打开的文件这次未必用到。

3、SuperFetch

为了解决“after-lunch effect”引入的。为啥午饭回来之后outlook很迟钝,硬盘在狂转?因为在idle的时候杀毒软件开始扫描磁盘,占用大量物理内存,outlook不幸被page out了。等杀毒软件运行完释放了内存,LRU算法会把杀毒软件留在内存的standby list里,而outlook的页面却不会从pagefile里回来。

类似Prefetch,SuperFetch记录内存的历史使用情况。它考虑了时间信息,比如“每天下午2:00要用Outlook”,这样就预先把需要的东西读到内存里。而且也会考虑把常用/重要的东西(比如输密码解锁那段code)尽量留在内存里。

只要发现你的空闲内存比较大(比如刚有一个大程序退出),它就会一点一点地吃掉你的空闲内存,把它认为需要用的东西放在内存里。所以很多人误以为SuperFetch浪费内存。

4、ReadyBoost:

用优盘当虚拟内存。hard fault发生时,从优盘读比硬盘稍微能快一点点。

5、ReadyBoot:

在boot的时候,预读一些硬盘数据到内存里。这个跟prefetch很像但是更智能。我现在还不是很清楚这个东西怎么工作的。还需要翻一翻宝书Windows Internals 5。。。

Update: 今天翻了宝书,发现宝书上只用了1页描述ReadyBoot……郁闷,还是不清楚。这也意味着即使我通过别的方法把它搞清楚,也不能把结果贴出来了。

Sleepless in Seattle

上午睡到12:00,下午和晚上去逛了Seattle downtwon,于是回来就睡不着,郁闷中。。。写写游记,希望能早点困。

时间有限,去的地方不多。华盛顿大学是个不错的景区。要知道美国的“大城市”跟中国的城市一样也是千篇一律,无非就那么同样的几家品牌店,一个个数字编码的street和avenue。所以呢,华大的“古”建筑看起来有内涵多了。在被雨水浸湿的宽阔广场上,除了镶嵌彩色玻璃、带有尖顶和拱门的欧式建筑,有野鸭散步其间的草坪,鲜红的爬墙虎,在地上映出长长倒影的诡异砖墙高塔,还有若干跟我们一样不打伞的各色美女帅哥——btw,真正的西雅图人是不打伞的。向海边的方向望去,华盛顿铜像在大团氤氲的云雾中显得有点孤单。几棵很有些年头的樱花树散布在落叶缤纷的草坪上;虽然樱花是肯定见不着的,但也让我们这些东方人感到一点点的亲切。

雨下了又停,停了又下。这里的天气预报不好做啊!开车5分钟后从小雨变成大雨,再开5分钟又从大雨瞬间转晴天。继续走吧。

跟旧金山一样,这里有25-30度左右的恐怖斜坡,一直从山上通到海边。上到一个小山顶后,可以看得很远。市中心就那么一小片高楼,哪个都跑不出视野。Space needle,西雅图的标志建筑很不幸的早就不是最高建筑,不过等夜晚塔上亮起纯白的灯光后,看起来很美,好像一个巨大卧室里的一盏精致落地灯……反正我挺喜欢就是了。

到海岸边(有车就是好啊!),1st avenue上有些有趣的地方,比如第一家星巴克,flying fish market之类的,到处都人满为患。我必须承认,虽然被提醒过,flying fish还是比我想像的更囧……真正happy的还是crab pot餐馆,虽然等了45分钟才有位子(哈,当然不会傻到坐在门口等了,顺便就逛了逛附近的老城区,在黑乎乎的灯光下见到黑人兄弟若干,晃来晃去似乎很拽的样子)。

继续说crab pot!海鲜吃到饱不说,关键是吃的方法。没有碗也没有盘,一大堆螃蟹、虾、clam,mussel、香肠、煮土豆和嫩玉米被直接倒在垫了张大纸的桌上,然后每人拿木槌在小木板上狂敲一通~这比文明人的吃法有特别的趣味,超太有两层房,之前楼下住着她的儿子儿媳,不过,他们去南京工作了,楼上则是她一个人照看两个孙女的地方。本分工作外,爽的……嗯,破坏一下吃饭的气氛,我忍不住想,每个木板上,都有多少个关节被敲碎呢……已经接近尾声,猫捕获了它的猎物,眼下正挑逗。屋里的人将衣服挂在窗外,巷子中砖瓦紧凑地接着淅淅沥沥的水滴。“滴答相请看图

饭后去朋友家里小坐,11:00回来。Acknowledge一下热情的朋友,要是自己瞎摸,怎能玩得high到现在都睡不着啊~:)

http://picasaweb.google.com/zhao.xiangpeng/Seattle

几个马路上常见的单词

yield,让路。to slow down or stop in order to let another vehicle pass。一般在并线的地方出现。

多线程编程里面会用到yield,是线程主动让出CPU,让下一个线程执行。

detour,绕行。

微软研究院有个项目叫detours,是用来hack Windows API的。系统本来要调用Windows API的code,现在却要转到我自定义的代码上。很贴切。

顺便感叹一下,老美是先学了这些词再去理解计算机的术语,咱们正好反过来。

LOW SHOULDER ,道路两旁低。类似的还有soft shoulder。

limosine,豪华轿车。在租车的地方看到的标志。

ped xing,这个很容易猜到……pedestrian crossing.

back to USA

又一个秋天,重逢的是蓝天白云,碧海绿树的美丽景色。

跟上次来美国的独自折腾相比,这次条件好得太多。不用转机,11个小时就到。旅店、租车都由公司预先搞定。有人同行不担心找不到路,去超市买东西也不再担心东西太多自行车载不动了。

以前不开车也没在意,到停车场一瞧,好像所有的车都比中国的大一圈。我们租的是辆凯美瑞,感觉比国内的凯美瑞就大一些。更不用说各种夸张的SUV了,国内的SUV真的只能算mini-SUV。

明天还可以休息一天,去downtown逛一下,后天去上班啦~

为什么坚持写博客的人很少

为什么我很久不写blog了?一个原因是觉得自己在OS,Performance这个领域懂得太少太少,自己看来很新奇的东西其实都是些基础知识。不过现在想想懂得少又如何?就算粗浅的见解不能让读者感兴趣,至少还可以督促自己的学习。

工作很忙是一个原因,但决不是关键的原因。虽然每日的工作时间比在学校里多了,但玩的时间并没减少多少……写到这里我想我应该少花时间打台球……当然最近也有点打够了……-_-

还有,之前在某同学blog看到的一帖,让我很感叹。博客没人看的时候,作者会希望有更多人看。等看的人多了,写东西的心情就不一样了,反而难下笔了。说得太对了。所以要做好博客的定位。心情帖就灌到xiaonei,msn签名这种地方好了。(话说twitter不就相当于把msn签名收集起来放在网页上再允许留言吗……)

最后,坚持做任何一件事都很难。(比如每天按时睡觉就很难 :P )所以才叫坚持。好在朋友的一个提醒,一个鼓励,都会给人很多动力。谢谢提醒我应该继续写博客的朋友们。:)

由无数细节组成的系统

计算机科学总是讲abstraction。这是源自数学的思维。formal methods可以说把这个发挥到了极致。但实际系统并不是这样。不要说构建Windows这样的庞然大物,就只是要写一个实用的小软件,就会遇到无数的细节。performance,security,scalability,debugging……除了functionality之外,有无数的事情要考虑。

传统的formal methods注重functionality也没错,因为在某些情形下(比如算法领域)functionality的正确性已经够难了。但这却不是软件工程中的主要问题。

我们能做的,就是尽量挣扎着把细节包装起来。比如,工程师做出一个个框架,分出若干个层次。框架可以帮助我们,但框架并不能解决一切问题,因为好的程序员必须了解各种细节。最近读一个基于ATL/WTL的真正的product code,发现这一年来看的n本书都没白看。比如,不懂C++内存布局,不懂各种calling convention,compiler的差异,就不能理解COM的设计(btw,强烈推荐《COM本质论》的第一章)。

researcher也自有办法。因为很多细节是正交的。在TASE'09上听到Zhong Shao做的invited talk,分不同的aspect去验证汇编代码的不同性质,这个是正确的思路,而且我觉得可以做更多的东西出来。以前做business process的security验证,这个也可以归到这个思路,不过high level的东西比较虚,做起来虽然不难但并不容易很深入。而Shao对汇编和机器本身建模,比如对interrupt handling建模,这个就很有新意,且竞争者较少——很多数学系出身做formal methods的人,都不太懂系统底层。

是的,跟C++和汇编相比,写Java很容易。不过事情一旦很容易,于工业上看,就不值钱了;于学术上看,就没啥好研究的了。所以,重视各种细节吧。

品味红酒

话说偶今天小资了一把,在公司尝试了各种红酒,从Sauvignon Blanc,Chardonnay,到Cabernet Sauvignon,Shiraz……最后是 Bordeaux的Margaux,好酒很香绵,只可惜没有美食相配。喝到微醺就下班了。快乐的星期五~

红酒的红色来自葡萄的皮。在制作之初葡萄皮就与果肉分离,在发酵的过程中添加一段时间又要取出。复杂吧。白葡萄酒为什么是白色?因为没有加葡萄皮。

制作一瓶好酒,要天时地利人和。先说天时,不同年份的酒特性相差很多,据说2005年的法像样的玩意,我就常去逛,这是个人的职业习惯。巷子里叶子葱翠,老屋子年久失修,多是低矮红砖房,好些房子里还用着原国红酒就可以长期存放,而之前00-04年的酒都不太适合放太久。再说地利,法像样的玩意,我就常去逛,这是个人的职业习惯。巷子里叶子葱翠,老屋子年久失修,多是低矮红砖房,好些房子里还用着原国地图上一小块地方就有无数的酒庄无数的葡萄品种,极品产地的酒可以卖到>6w人民币一瓶,极品产地的极品酒庄的极品酒就更不用说了。最后,采摘葡萄的时机和酿酒师的技术都很重要。

品酒师说100块以内的红酒就不要买……我承认我很土,虽然国产赤霞珠很烂,但总算买得起……btw,据说双井的家乐福周末经常有免费品酒活动,可以去蹭,都是进口的好酒噢。

说到国产红酒,原来赤霞珠和解百纳是同一种葡萄Cabernet Sauvignon (号称超级容易养)。如今被当作两种,完全是中国人的发明。

p.s. 品酒师是偶们公司的一位architect,太有才了;再顺便说一下今天尝的酒都是他自掏腰包带来的,太葱白了~

最后提个思考题:做葡萄酒的葡萄一般是什么味的?

C#和CLR的15个细节

这两周一直在training,一共听了4个课程,相当的累啊。

今天先把Jeffrey Richter的培训课:C# and CLR中讲到的一些东西整理一下:

  1. foreach的性能问题

    foreach(string s in rows) { foo(s); }的实现是:

  2. IEnumerator e = rows.GetEnumerator();
    try {
      string s;
      while (e.MoveNext()) {
        s = (String) e.Current;
        foo(s);
      }
    }
    finally {
      IDisposable d = e as IDisposable;
      if (d != null) d.Dispose();
    }
    

    每一步都调用了e.MoveNext()和e.Current两个方法;而大多数时候,完全有可能优化为一次调用。显然这对性能是有影响的。虽然foreach对于数组作了单独的优化(编译成for循环),但这还是值得注意的。

    那么,怎么做比较快?

    对于List等Collection,可以用ForEach(Action<T> action),FindAll(Predicate<T> match),ConvertAll<U>(Converter<T, U> converter)等方法。它们比较快,但不是所有实现IEnumerable的类都提供。

    LINQ追求compatiblity,而不是performance。因此LINQ的实现完全采用了foreach。值得注意。

  1. yield的实现原理

    实现一个支持IEnumerable的对象时,一般会用到yield关键字,这样foreach遍历这个对象时,可以做到lazy evaluation。例如:

    class MyCollection: IEnumerable<char> {
      private string s; ...
      public IEnumerable<char> GetEnumerator() {
        for (int i=0; i<s.Length; i++) {
          yield return s[i];
        }
      }
    }
    

    执行到yield时函数返回,下次调用时,接着上次运行的位置继续运行。这个continuation的效果是怎么做的呢?

    包含yield的函数都会被编译器做成一个状态机。每调一次,就接着上次的状态继续运行。简单有效啊。我一直以为要有什么特殊的办法呢。

  2. exception handling的实现决定了throw的performance较差。

    可以用Int32.TryParse代替try{Int32.Parse…}catch{…},稍快一点。类似地建议使用Dictionary.TryGetValue。

  3. .Net CLR执行引擎对应于MSCorWks.dll和MSCorEE.dll这两个文件。

  4. .Net 3.0, 3.5没有对CLR作任何修改。

    所有增加的东西(比如LINQ)都是syntactic sugar,只改了C#编译器而已。

  5. AppDomain

    如果把.Net虚拟机看成一个虚拟操作系统,AppDomain的概念则类似于操作系统中的进程。

    可以用代码创建一个AppDomain,然后动态加载/卸载assembly,还可以设置权限,相当于提供了一个沙箱。

    跨AppDomain的调用类似于RPC。

    调用某个AppDomain内部的obj.foo(x)时,.net会自动帮你做出一个proxy object,你所调用的obj其实是一个proxy object。传给foo的参数x会先被被marshal,以保证AppDomain被安全隔离。

    谁用AppDomain?SQL Server用这个技术实现managed存储过程。IIS会把不同的Web Application放在不同的AppDomain里,以实现动态装卸。

  6. 动态载入Assembly的陷阱

    Sytem.Reflection.Assembly.LoadFrom(pathName)并不会载入pathName所指定的dll,而是看看pathName那个dll的名字、版本,然后到系统默认位置去找。(陷阱啊)

  7. C#里用reflection创建一个新对象

    用Activator.CreateInstance。(奇怪的名字啊。)

  8. C#泛型之“where”

    可以用“where”来限定T的接口。例如

    static T min<T>(T arg1, T arg2) where T: IComparable<T> {…}

    不写where的话,就不能调arg1.CompareTo(arg2)。

    为啥不把T换成IComparable?一是为保证arg1, arg2一定是同一个类型,二是泛型的效率更高。(JIT会为不同类型的T各生成一份native code,从而避免了boxing)

    更多where的细节:

    * 要想调T t1 = new T(),必须声明where T: new()或者where T: struct

    * 要写T t2 = null,必须声明where T: class

    * T z = default(T)是一个特殊的用法,会把T的每个bit都置为0。

    * 假设定义了Foo<T>(T x, T y),则if (x==null) … 是可以通过的,虽然C#中value type的值不允许为null(例如int a=null是错的)。这是因为,此时的语义是一致的,反正if里面的操作不被执行就是了,所以编译器对这种特殊情况网开一面。

    * if (x==y)不行,除非写了where T: baseclass。(这里我也没理解为啥。。。>_<好像说是不知道应该用reference比较还是value比较?)

  9. 匿名函数的背后。。。

    在C# 2.0以后可以用匿名的delegate,如ThreadPool.QueueWorkItem(delegate (Object obj) { Console.WriteLine(obj); })

    但编译器的实现会带来一点点overhead,会生成一个小小的静态WaitCallback对象,可以用Reflector看生成的代码。(不要打开Reflector的optimization,否则就看不到了)

    如果是自己写的话,可以选择每次动态建立一个WaitCallback对象然后销毁。当然这样做性能可能差一些,但这里的idea是:编译器会自动做一些事,但不一定是你所希望的。在使用这些高级feature前,最好先搞清楚背后发生了什么。

    另一个细节:如果匿名函数中使用了外层函数的局部变量(即所谓的function closure),会导致创建额外的shared-state object,把用到的局部变量做成一个新对象传给匿名函数。

    上述描述同样适用于lambda函数。因为C#的lambda函数就是匿名函数,改了改语法而已。

  10. Nullable type

    虽然C#要求value type的值不能是null,但写数据库程序时经常遇到某个值是null的情况。为此,C#2.0引入了Nullable type。例如,int? x = null。

    int? x其实就是一个缩写,等价于Nullable<int> x。Nullable是预定义的一个类,简单地对x作了封装。(因为增加了一个类,显然对性能稍微有点影响)

    这个小改动的实现其实很麻烦,需要修改CLR。为什么?因为原先的x是一个value type,现在则变成了一个object,看这个:

    void M(Object o) {
      if (o=null) {Bar();}
    }
    void F() {
      int? x = null;
      M(x);
    }
    

    如果CLR不专门做修正的话,上面的Bar()不会被执行。(思考题:想一想为什么~)

    另外,C#还引入了一个默认值运算符“??”,称为null-coalescing operator。

    一句话,x ?? value是 (x==null) ? value: x的简写。

  11. 属性(property)的简单声明

    public int x {get; private set;}是个很好用的句式。

    注意,public int x {get;}是错误的,不能通过编译。

  12. Extension method

    static class MyExtMethods {
      static public GetFirstLetter(this string s) {return s[0];}
    }
    

    然后就可以用string s = “hello”; char ch = s.GetFirstLetter()了。

    原理很简单,编译器把上面那句话翻译成MyExtMethods.GetFirstLetter(s)。LINQ就用到了这个技术。

  13. 匿名类型的背后。。。

    var o = new {name = “Xiangpeng”, id = 123 };

    在这背后是编译器生成的一个匿名类,包含了两个只读属性,形如public int id { get {return _id;} }为什么不做成可读写的呢?

    很微妙。匿名类自动生成了GetHashCode(),返回的是对所有属性的hash code做XOR的结果。如果允许修改属性值,那么Hash code的值就会变化;而这个可能会出问题~保险起见,只读吧。

  14. 每个thread占1M物理内存

    在Win32编程中thread的1M stack空间是Reserve的,直到真正用时才占用物理内存;而在.net中,这1M空间直接被commit。

    还好,可以在新建thread时指定stack size。不过这也比较危险,设小了怕不够。实际上,最好尽量避免创建thread——太多的thread要么导致CPU竞争和context switch,要么都block着浪费内存。建议是:能用ThreadPool就用ThreadPool。

累死了,写了这么多……Jeffrey还有一个更牛的课:.net threading。里面的东西更多……要不要再总结一下呢。。。Thinking

时间管理啊……

为什么在长假和坐飞机的时候,能够快速地阅读生涩著作呢?长假,是彻底清空,有了接受新信息的空间,有了闲暇的时间。坐飞机或在机场,创造出一种深度阅读的“狭小空间”。但这种解读还是表象,或许这两者的真正共性是,它让我们离开了庞杂的信息,离开了哪些摆在四周的阅读糖果零食,我们都知道,糖果吃起来不错,但吃多了很难受,只吃糖果会更加不舒服。

这段话说的太好了。但为什么我就没有办法把主动网络断掉慢慢研究WRK呢。。。

用c#从Outtlook 2007里读取email信息

首先要添加对Microsoft.Office.Interop.Outlook的引用。

image

using Microsoft.Office.Interop.Outlook之后,操作Outlook的代码如下。

注意,代码中演示了如何取出特定文件夹,和取出inbox等特殊文件夹:

Application myApp = new ApplicationClass();
NameSpace mapiNameSpace = myApp.GetNamespace("MAPI");
MAPIFolder folder;

folder = myApp.Session.Folders["邮箱 - Xiangpeng Zhao"].Folders["Maillists"].Folders["Windows 7"];
//folder = mapiNameSpace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);

foreach (MailItem mail in folder.Items) {
    string subject = mail.Subject;
    string body = mail.Body; // this will trigger the security warning...
    string senderName = mail.SenderName;
    string senderEmail = mail.SenderEmailAddress; // security warning
}

如何跳过讨厌的Outtlook安全警告!

运行上面的代码,Outlook就会弹出一个对话框:“A program is trying to access e-mail address information stored in Outlook…”必须按下“Allow”才能访问email信息。怎么跳过这个对话框?

image

这个问题的解决方法比我想像得复杂。针对不同的场景,可能需要不同的方案。Outlook "Object Model Guard" Security Issues for Developers是一篇非常好的文章,介绍了各种方法。

因为我只想读取我自己机器上的email,所以就装了个免费的Outlook插件Advanced Security for Outlook,它可以自动替换Outlook的安全警告对话框,取而代之的是一个允许自定义信任程序的对话框:

image

这确实很方便。