原文:使用C#进行系统编程
虽然对于系统编程(System programming)的定义很模糊,不过可以将其描述为在比特、字节、指令,或CPU周期层面所进行的思考。系统编程这个概念也暗含了对性能和可靠性的需求。Microsoft技术总监Joe Duffy在 QCon New York 活动中介绍了使用C#进行系统编程的战略,同时他还谈到了这其中的一些陷阱以及缓解方法。
Joe的演讲中很多内容来自一个名为Midori的研究项目。该项目意在使用C#从零开始打造一个操作系统,这也让我们对编译器的结构和有关高性能代码的新战略有了全新认识。
使用托管语言(Managed language)构建操作系统使得我们能够在内存层面上运用C#中的安全功能。这样做可以避免由于缓冲区溢出或格式字符串(Format string)弱点而针对内存进行的代码注入攻击,因为此时可以由运行时负责边界检查(Bound checking)和类型安全(Type safety)。
代码的生成
代码可以通过预先(Ahead of time,Aot)或即时(Just in time,Jit)的方式编译。Jit的优势在于编译速度更快,但Aot可以获得更好的机器代码,因为编译器可以对代码执行更多优化。
原生语言编译器实现的很多优化原本是托管语言所不具备的。一般原因通常在于,通过Jit编译器实现这些优化通常可能需要极大的运算量或过于复杂。正是这些问题导致C#在紧密、高效的底层代码生成方面口碑不佳。最近通过RyuJit实现了下列这些优化:
- Inlining(使用所调用函数的主体取代函数调用点)
- Flowgraph和Loop分析
- 静态单一指派(SSA)和全局值编号
- 通用子表达式消元(Common subexpression elimination)
- 复制/常量传播(Copy/constant propagation)
- 无用代码删除
- 范围分析
- 非虚拟化(Devirtualization)
- 循环不变量代码提升(Loop invariant code hoisting)
- SIMD和向量化
- 通用共享(Generic sharing)
- 堆栈分配(正在开发中)
垃圾回收
.NET中的垃圾回收已发展到第三代。一些数据程序分析师需要将自己过半的时间用于垃圾回收,而无法用在更有价值的工作中。
改善性能的方法之一是使用Struct,Struct可改善下列领域的性能问题:
- 降低GC压力,因为Struct是针对堆栈分配的。
- 提高内存局部性(Memory locality),改善缓存命中率。
- 整体减少对内存的使用,避免32-64位应用程序中对象的8-16字节开销。
关于Struct有个问题需要注意:在复制超出某一大小的Struct时可能导致Memcpy。为了优化性能,应确保Struct尽可能小,不要超过32/64字节。
C# 7的一些功能使得通过Struct进行底层优化的过程变得更容易。C# 7的元数组(Tuple)是Struct,而非老版本中的System.Tuple<>,后者现已成为对象。引用返回则是Struct的另一个特性,可以无需复制直接通过函数返回Struct。
错误处理
可恢复的错误总会存在例外,然而很多错误都是不可恢复的。诸如无效转换、栈溢出以及空引用等错误实际上属于Bug。但I/O故障和验证错误是可以预见并从中恢复的。
这种错误恢复催生了Fail fast策略。 Fail fast 是一种包含在.NET中的机制,这种机制下诸如StackOverflow等例外可能会绕过异常处理程序导致进程崩溃。该策略使得查找此类错误的过程变得更简单,因为此时例外已经无法被过度的通用异常处理程序所处理。Midori团队发现他们的可恢复错误(例外)与Bug(Fail fast)已经达到了1:10的比例。
详细信息请参阅Joe的博客,他在博客上写了 很多有关Midori的文章 。他的 演讲演示文稿 也已经发布到网上。
查看英文原文: Systems Programming in C#