如何看待 .NET Native,真能达到 C++ 的性能、C# 的生产效率吗?

Microsoft .NET Native FAQ Getting Started with .NET Native .NET Native 会给 Windows Store App、Windows Phone App 多大帮助? .NET Native 会不会跨平台支持 Linux、OS X 等系统? 相关问题:如何评价微软推出的 .NET Native? (知乎的搜索……)
关注者
816
被浏览
83687
我对.NET Native抱着很大的信心,相信它能让许多C#应用接近用C++写的Windows Store Application的性能。“接近”可能是90%、95%性能,但彻底“达到”100%的C/C++性能还是不太现实。

我觉得这个问题可以参考一下我回答的一个关于Java的静态优化的问题:逃逸分析为何不能在编译期进行? - RednaxelaFX 的回答
.NET其实也有类似的问题,不过严重程度没有Java那么糟糕。
Java的动态加载是在Class文件级别,所以安全的做静态分析和优化的边界也就在Class文件内;
.NET的动态加载是在Assembly级别,而一个Assembly已经包含了许多类型,虽然静态分析和优化的边界在Assembly内但已经可以做很多实用的优化。

而进一步拓展优化的边界就需要“放弃”一部分动态加载的灵活性,换来更大的可静态分析的范围以便进行更彻底的优化。

在.NET Native之前,.NET已经有一个AOT编译器——NGen,用于在目标机器上把MSIL编译到native code并缓存在GAC里。那.NET Native是不是只是NGen旧瓶装新酒、换汤不换药呢?

从概念上说,完全不是。

NGen的目的主要是通过事先编译(AOT)避免(减少)在运行时JIT编译,提升启动速度和后续运行的性能稳定性,但不是提升程序的平均或最高性能。这使得它的设计和实现有所取舍:
  • NGen工作在Assembly单位上,每个MSIL assembly编译出一个对应的native code assembly。这样就会遇到前面提到的“优化边界”问题——跨越Assembly的方法调用全部无法优化。
  • NGen其实是把CLR里的JIT编译器拿来当作AOT编译器用,在客户机器上做安装时编译,并没有充分利用静态编译做优化可以付出的时间和空间代价,因而最终生成的代码的优化程度最高也不会比CLR运行时用JIT编译出来的好。这是个工程取舍。(不过比较新的NGen有profile模式而且可以依据profile信息把热的代码块跟冷的代码块分别集中起来,这比JIT的效果好)
  • NGen大概是出于灵活性(动态加载的灵活性和打补丁的灵活性)的考虑,生成的代码是PIC(position-independent code),比起能直接把目标地址嵌入代码中的JIT来说会损失一点性能。

.NET Native的目的则是提升程序性能和简化部署,使得它跟NGen采取了不同的取舍:
  • .NET Native工作在一个完整的应用的层面,其中所有的Assembly,包括.NET标准库的Assembly,都会引入进来,一股脑塞进.NET Native的分析、优化、链接程序,最终生成一个打包好的可执行程序(附带程序所需的元数据)。这就突破了Assembly的优化边界,可以真正在静态编译时做全程序分析与优化(whole program analysis),最后得到比较优化的代码。这跟我前面提到的Java程序的静态分析那个回答的后半部分说的一致(Excelsior JET的模型)。
  • .NET Native不是在客户机器上编译,而是在Windows Store的服务器上编译,因而无需担心消耗客户机器的资源,可以放心大胆开足马力做编译优化。
  • 我还没仔细确认,但我猜.NET Native生成出来的代码不是PIC。
  • .NET Native用的编译器借用了Visual C++的后端(UTC)。虽然NGen借用的CLR的JIT在x64上的版本其实也是借用的UTC,但它们俩具体到底有多少异同我就不知道了。我的猜想(和希望)是.NET Native的使用方式能够容忍更长的编译时间,这样才能有效进行某些复杂度高的优化。
  • NGen编译得到的Assembly仍然需要系统上的CLR来运行,所以目标机器上事先必须安装.NET Framework;.NET Native则是把一个由CLR极度精简后的runtime——MRT_app.dll——打包到应用里,不需要目标机器上事先安装了.NET Framework,因而简化了部署。
(图片截取自Andrew Pardoe的演讲:.NET Native Deep Dive

总之.NET Native概念非常好,最终效果好不好就看M$同行们有多努力了哈哈哈 ^_^

===================================

@Thomson 大大在回复里问NGen生成的代码是PIC的引用资料,有这些:
CLR Inside Out: The Performance Benefits of NGen.
Improvements to NGen in .NET Framework 4
Surupa Biswas: CLR 4
Throughput of NGen-compiled code is lower than that of JIT-compiled code primarily for one reason: cross-assembly references. In JIT-compiled code, cross-assembly references can be implemented as direct calls or jumps since the exact addresses of these references are known at run time. For statically compiled code, however, cross-assembly references need to go through a jump slot that gets populated with the correct address at run time by executing a method pre-stub. The method pre-stub ensures, among other things, that the native images for assemblies referenced by that method are loaded into memory before the method is executed. The pre-stub only needs to be executed the first time the method is called; it is short-circuited out for subsequent calls. However, every time the method is called, cross-assembly references do need to go through a level of indirection. This is principally what accounted for the 5-10 percent drop in throughput for NGen-compiled code when compared to JIT-compiled code.
<- 这描述的就是一种PIC概念的实现。在较新版本的NGen中softbound dependencies仍然是这样实现的,而新的hardbound dependencies则试图把地址硬编码进来。