Sends for the fellow dear visitors:welcome to dongpad!


 Welcome to DongPad!

 msn


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

感谢作者的分享,原文参见:c#和clr的15个细节!



评论: 0 | 引用: 0 | 查看次数: 333 | 返回顶部
发表评论
昵 称:    
验证码:   
 
内 容:
选 项:  
 
虽然发表评论不用注册,但是为了保护您的发言权,建议您注册帐号.
字数限制 500 字 | UBB代码 开启 | [img]标签 开启