我们很高兴发布.NET 6候选版本1。这是两个 "上线 "候选版本中的第一个,在生产中得到支持。在过去的一个多月里,团队一直专注于质量改进,以解决新功能中的功能或性能问题或现有功能中的退步问题。
你可以下载Linux、macOS和Windows的.NET 6候选发布版本1:
请参阅.NET MAUI和ASP.NET Core的帖子,以了解更多关于客户端和Web应用场景的新内容的细节。
我们正处于周期的有趣部分,我们在生产中支持新版本。我们真心地鼓励它。在上一篇文章中,我建议人们给我们发邮件,dotnet@microsoft.com,询问如何处理这个问题的指导。一批企业联系了我们,希望探讨他们应该怎么做。这个提议仍然是开放的。我们希望能有两三打早期采用者,并且很高兴能帮助你完成这个过程。这是很直接的。
.NET 6 RC1已经过测试,并支持Visual Studio 2022预览版4。Visual Studio 2022使你能够利用为.NET 6开发的Visual Studio工具,如在.NET MAUI中开发,C#应用程序的Hot Reload,WebForms的新Web Live Preview,以及IDE体验中的其他性能改进。
对.NET 6 RC1的支持即将在Visual Studio 2022 for Mac Preview 1中出现,该预览版目前可作为私人预览。
查看新的对话帖子,了解工程师之间关于最新.NET功能的深入讨论。
帖子的其余部分致力于介绍.NET 6中的基础功能。在每个版本中,我们都会承担一些需要多年才能完成的项目,而且(根据定义)在一段时间内不会提供其全部价值。鉴于这些功能还没有完全实现,你会注意到这篇文章偏向于我们在.NET 7及以后的版本中可能会对这些功能做的事情。
源码构建
源码构建是一个场景,也是我们与红帽公司合作的基础设施,从发货前的.NET Core 1.0开始就一直在进行。几年后,我们已经非常接近于提供一个完全自动化的版本。对于红帽企业Linux(RHEL)的.NET用户来说,这种能力是个大问题。红帽公司告诉我们,.NET已经发展成为他们生态系统中的一个重要的开发者平台。
很明显,.NET的源代码可以被构建到二进制文件中。开发人员在克隆了dotnet org的repo之后,每天都会这样做。这并不是真正的问题所在。
Linux发行版的黄金标准是使用编译器和工具链来构建开放源代码,这些都是发行版存档的一部分。这适用于.NET运行时(用C++编写),但不适用于任何用C#编写的代码。对于C#代码,我们使用双通道构建机制来满足发行版的要求。这有点复杂,但理解这个流程很重要。
Red Hat使用微软构建的.NET SDK(#1)来构建.NET SDK的源代码,以产生SDK的纯开放源代码(#2)。之后,同样的SDK源代码被再次使用这个新构建的SDK(#2)来产生一个可证明的开源SDK(#3)。这个最终的SDK(#3)会被提供给RHEL用户。之后,红帽可以使用这个相同的SDK(#3)来构建新的.NET版本,不再需要使用微软的SDK来构建每月的更新。
这个过程可能会让人感到惊讶和困惑。开源发行版需要由开源工具来构建。这种模式确保不需要微软构建的SDK,无论是有意还是无意。作为一个开发者平台,要被纳入发行版中,有一个更高的标准,而不仅仅是使用一个兼容的许可证。源码构建项目使.NET能够满足这一要求。
源码构建的交付品是一个源码压缩包。这个源码压缩包包含了SDK的所有源码(对于一个特定的版本)。从那里,Red Hat(或其他组织)可以建立他们自己的SDK版本。Red Hat的政策要求使用从源码构建的工具链来产生二进制焦油球,这就是为什么他们使用两道工序的方法。但这种双通道方法对于源码构建本身并不是必需的。
在Linux生态系统中,对于一个特定的组件,同时拥有源码和二进制包或焦油球是很常见的。我们已经有了二进制的tarballs,现在也有了源码tarballs。这使得.NET符合标准的组件模式。
.NET 6中最大的改进是,源码压缩包现在是我们构建的产品。过去,它需要大量的手工工作来制作,这也导致了向Red Hat提供源码压缩包的巨大延迟。双方对此都不满意。
在这个项目上,我们已经与红帽紧密合作了五年多。它的成功,在很大程度上是由于我们有幸与之合作的优秀红帽工程师的努力。其他发行版和组织将从他们的努力中受益。
作为一个附带说明,源码构建是向可重复构建迈出的一大步,我们也坚信这一点。.NET SDK和C#编译器具有显著的可重复构建能力。有一些具体的技术问题仍然需要解决,以实现完全的可重复性。令人惊讶的是,剩下的一个主要问题是对汇编中的压缩内容使用稳定的压缩算法。
轮廓引导优化(PGO)
轮廓引导优化(PGO)是大多数开发者平台的一项重要能力。它是基于这样的假设:作为启动的一部分执行的代码通常是统一的,通过利用这一点可以提供更高级别的性能。
你可以用PGO做很多事情,比如说:
- 以更高的质量编译启动代码。
- 通过以较低的质量(或根本不需要)编译低用量的代码来减少二进制文件的大小。
- 重新安排应用程序的二进制文件,使启动时使用的代码位于文件的开头附近。
.NET以各种形式使用PGO已经有20年了。我们最初开发的系统既是专有的,又(非常)难以使用。它是如此难以使用,以至于微软的其他团队很少使用它,尽管它可以提供显著的好处。有了.NET 6,我们决定从头开始重建PGO系统。这在很大程度上是由crossgen2作为新的使能技术所激发的。
启用一个世界级的PGO系统有几个方面(至少在我们看来):
- 易于使用的培训工具,从应用程序中收集PGO数据,在开发者桌面和/或生产中。
- 将PGO数据直接整合到应用程序和库构建流程中。
- 以各种方式处理PGO数据的工具(差异化和转换)。
- 对PGO数据的人和源控制友好的文本格式。
- 静态PGO数据可以被动态PGO系统用来建立最初的洞察力。
在.NET 6中,我们专注于建立可以实现这些和其他经验的基础。在这个版本中,我们只是回到了我们之前的状态。运行时库被编译为随时可以运行的格式,并以(新形式的)PGO数据进行了优化。这都是通过crossgen2启用的。目前,我们还没有让其他任何人使用PGO来优化应用程序。这就是接下来将在.NET 7中出现的内容。
动态PGO
动态PGO是我刚才描述的静态PGO系统的镜像。静态PGO与crossgen2集成,动态PGO与RyuJIT集成。静态PGO需要单独的训练活动并使用特殊工具,而动态PGO是自动的,并使用运行中的应用程序来收集相关数据。静态PGO的数据是持久的,而动态PGO的数据在每次应用程序运行后都会丢失。动态PGO类似于跟踪JIT。
动态PGO目前是选择加入的,需要设置以下环境变量:
DOTNET_TieredPGO=1DOTNET_TC_QuickJitForLoops=1
.NET 6中的性能改进一文很好地展示了动态PGO如何提高性能。
分层编译(TC)具有与动态PGO类似的特性。事实上,动态PGO可以被认为是分层编译的第二版。 TC提供了很多好处,但在多个方面还不成熟,可以大大改进。它是稻草人的大脑。
也许动态PGO最有趣的能力是去虚拟化。方法调用的成本可以这样描述:接口>非接口虚拟>非虚拟。如果我们能将一个接口方法调用转化为非虚拟调用,那么这就是一个显著的性能改进。这在一般情况下是超级困难的,因为要静态地知道哪些类实现了某个接口是非常困难的。如果做错了,程序会(希望)崩溃。动态PGO可以正确有效地做到这一点。
RyuJIT现在可以使用 "守护式去虚拟化 "编译器模式生成代码。我将解释它是如何工作的。动态PGO在运行时收集在方法签名的某些部分满足接口的实际类的数据。如果对一个类有强烈的偏爱,它可以告诉RyuJIT生成偏爱该类的代码,并在该特定类方面使用直接方法调用。正如建议的那样,直接调用要快得多。如果在意外的情况下,对象属于不同的类,那么执行将跳到使用接口调度的较慢的代码。这种模式保留了正确性,在意外情况下不会慢很多,而在预期的典型情况下会快很多。这种双模式系统被称为守护式,因为更快的去虚拟化代码只有在成功的类型检查后才会被执行。
我们还可以想象实现其他的功能。例如,Crossgen2和动态PGO的某种组合可以学习如何根据使用情况来稀疏地编译方法(最初不要编译很少使用的if/else块)。另一个想法是,Crossgen2可以(通过一些加权)沟通哪些方法最有可能在运行时从更高层次的编译中受益。
Crossgen2
我已经多次讨论过crossgen2,包括在这篇文章和之前的文章中。Crossgen2是平台的超前或预编译的重要一步。这有几个方面,为未来的投资和能力奠定了基础。这并不明显,但crossgen2可能是该版本中最有希望的基础性功能。我将尝试解释为什么我对它如此兴奋。
最重要的一点是,crossgen2的设计目标是成为一个独立的编译器。Crossgen1是一个独立的运行时构建,只包含实现代码生成所需的组件。这种方法是一个巨大的黑客,而且由于许多不同的原因而有问题。它完成了工作,但仅此而已。
由于是一个独立的编译器,它可以用任何语言编写。自然,我们选择了C#,但它同样可以用Rust或JavaScript编写。它只需要能够作为一个插件加载RyuJIT的特定构建,并以规定的协议与之通信。
同样地,独立的性质使它可以跨目标。例如,它可以从x64定位到ARM64,从Windows定位到Linux,或者从.NET 7定位到.NET 6。
我将介绍在.NET 6之后,我们立即有兴趣启用的一些场景。从这里开始,我将只使用 "crossgen",但我指的是 "crossgen2"。
默认情况下,可运行(R2R)代码具有与IL相同的版本能力。这在客观上是正确的默认值。如果你不清楚这其中的含义,那就说明我们选择了正确的默认值。在.NET 6中,我们增加了一种新的模式,将版本边界从单个汇编扩展到一组汇编。我们称其为 "版本泡"。版本泡沫有两个主要功能:方法的内联和跨汇编的通用实例化(比如List<string> ,如果List<T> 和string 在不同的汇编中)。前者使我们能够生成更高质量的代码,后者使我们能够实际生成R2R代码,否则我们必须依赖JIT。在我们的测试中,这一功能带来了两位数的启动优势。唯一的缺点是,版本泡沫通常会因此而生成更多的代码。这就是下一个功能可以帮助我们的地方。
今天,crossgen为一个汇编中的所有方法生成R2R代码,包括运行时和SDK。这是很浪费的,因为其中至少有一半的方法最好在运行时进行jitting(如果它们需要的话)。Crossgen拥有部分编译的能力已经有很长一段时间了,我们甚至已经使用了它。在.NET Core 3.0中,我们用它从Linux的运行时分布中删除了大约10MB。遗憾的是,这个配置在某个时候丢失了,我们现在正带着这额外的10MB。对于.NET 7,我们将再次对此进行破解,并希望能找出比10MB更多的R2R代码不再生成(其好处自然不仅仅是减小尺寸)。
矢量或SIMD指令在.NET库中被大量利用,是提供高性能的关键。默认情况下,crossgen使用这些指令的旧版本(SSE2),并依靠分层编译为特定机器生成最佳的SIMD指令。这样做是可行的,但对于现代硬件(如云计算)来说并不是最佳选择,而且对于短时运行的无服务器应用程序来说问题尤其严重。Crossgen可以指定一个现代的、更好的SIMD指令集,如AVX2(针对英特尔和AMD)。我们计划利用.NET 7的这项新功能,为AVX2生成可直接运行的镜像。这种能力目前与Arm64硬件无关,因为NEON是普遍存在的、最好的指令。当SVE和SVE2变得普遍时,我们将需要为Arm64部署一个类似的模型。
无论什么是最理想的跨源配置,这就是我们提供容器镜像的方式。我们认为容器是我们最无遗留的发行类型,并希望更好地利用这一点。我们看到了很多为容器 "默认完全优化 "的机会。
安全缓解措施
我们在今年早些时候发布了一个安全路线图,以提供更多关于我们如何对待行业标准安全技术和硬件功能的见解。路线图也是为了进行交流,特别是如果你对这些话题有想要分享的观点。
我们在这个版本中增加了对两个关键安全缓解措施的预览支持。CET和W^X。我们打算在.NET 7中默认启用它们。
CET
英特尔的控制流执行技术(CET)是一些较新的英特尔和AMD处理器中的安全功能。它在硬件上增加了一些功能,以防止涉及控制流劫持的一些常见类型的攻击。通过CET影子堆栈,处理器和操作系统可以跟踪影子堆栈中线程的调用和返回的控制流,以及数据堆栈,并检测控制流的非故意变化。影子堆栈受到保护,不受应用程序代码内存访问的影响,有助于抵御涉及面向返回编程(ROP)的攻击。
请参阅.NET 6与英特尔CET影子堆栈的兼容性(Windows上的早期预览),以了解更多细节和启用CET的说明。
W^X
W^X是最基本的缓解措施之一。它通过不允许内存页同时可写和可执行来阻止最简单的攻击路径。由于缺乏这种缓解措施,我们没有考虑更高级的缓解措施,因为它们可能会因为缺乏这种能力而被绕过。随着W^X的到位,我们将增加其他补充性的缓解措施,如CET。
作为苹果硅过渡的一部分,苹果公司已经将W^X作为未来版本的macOS桌面操作系统的强制要求。这促使我们安排在所有支持的操作系统上对.NET 6实施这一缓解措施。我们的原则是,在可能的情况下,在安全方面平等对待所有支持的操作系统。W^X在所有装有.NET 6的操作系统上都可用,但在Apple Silicon上只默认启用。对于.NET 7,它将在所有操作系统上启用。
HTTP/3
HTTP/3是一个新的HTTP版本。它与.NET 6一起处于预览状态。HTTP/3通过使用新的底层连接协议(称为QUIC),解决了过去HTTP版本在功能和性能上的挑战。QUIC使用UDP并内置了TLS,所以它建立连接的速度更快,因为TLS握手作为连接的一部分发生。每一帧数据都是独立加密的,因此该协议在数据包丢失的情况下不再有线路阻塞的挑战。与TCP不同,QUIC连接是独立于IP地址的,因此移动客户可以在WIFI和蜂窝网络之间漫游,保持相同的逻辑连接,并可以继续长时间下载。
目前,HTTP/3的RFC还没有最终确定,所以仍然可以改变。我们已经在.NET 6中包含了HTTP/3,这样你就可以开始尝试使用它。它是一个预览功能,所以不受支持。可能会有粗糙的边缘,并且需要与其他服务器和客户端进行更广泛的测试以确保兼容性。
.NET 6不包括对MacOS上的HTTP/3的支持,主要是因为缺乏与QUIC兼容的TLS API。.NET在MacOS上使用SecureTransport来实现其TLS,它还不包括支持QUIC握手的TLS APIs。
关于.NET 6中的HTTP/3的深入研究博文将很快发表。
SDK工作负载
SDK工作负载是一种新的能力,它使我们能够在不增加SDK的情况下向.NET添加新的主要功能。这就是我们为.NET MAUI、Android、iOS和WebAssembly所做的。我们还没有把所有的新工作负载放在一起测量,但很容易猜到它们的总和至少是SDK现有的规模。如果没有工作负载,你可能会对SDK的大小感到不满意。
在未来的版本中,我们打算删除更多的组件,使其成为可有可无的组件,包括ASP.NET和Windows桌面。最后,人们可以想象SDK只包含MSBuild、NuGet、语言编译器和工作负载采集功能。我们非常希望迎合广泛的.NET生态系统,并提供你需要的软件来完成你的特定工作。你可以看到这种模式如何更好地用于CI场景,使dotnet工具能够为正在构建的特定代码获取一套定制的组件。
结束语
.NET 6有很多新的特性和功能是为现在服务的,其中大部分已经在所有的预览中和即将发布的.NET 6文章中进行了探讨。同时,看到.NET 6中的新功能为下一步的发展打下了基础,这也是令人鼓舞的。这些是大赌注的功能,将以明显和不明显的方式推动平台的发展。
在最初的几个版本中,团队需要专注于将.NET核心建立为一个功能全面的开源和跨平台开发系统。接下来,我们专注于将该平台与Xamarin和Mono统一起来。你可以看到,我们正在从那种风格的项目出发,转向更有前瞻性的项目。很高兴看到平台在基本的运行时能力方面再次扩展,而且沿着这些思路还有很多东西要做。
谢谢你成为一名.NET开发者。