.NET 6 Preview 1 发布
前言
2021 年 2 月 17 日微软发布了 .NET 6 的 Preview 1 版本,那么来看看都有什么新特性和改进吧,由于内容太多了因此只介绍一些较为重点的项目。ASP.NET Core 6 Preview 1 和 EF Core 6 Preview 1 同样有很多的更新内容,但是限于篇幅就不在这里介绍了。
统一和扩展
.NET 6 在 .NET 5 的统一的基础之上,继续借助 Xamarin 扩展到 Android、iOS 和 macOS。此外,.NET 6 还扩展了 Blazor 的适用范围,开发者可以通过 Blazor Hybrid 开发混合的跨平台客户端应用。
对于 Andriod、iOS 和 WebAssembly 将使用 mono 作为 runtime,但是基础库将全面与 .NET 统一。
安装了 .NET 6 的 SDK 之后,将能构建移动平台应用,例如对于安卓来说,运行 dotnet new android
就能创建一个安卓应用的项目,然后运行 dotnet run
便能直接启动安卓模拟器运行。
另外,为了统一、简化并扩展构建 Xamarin.Forms 应用,也将推出 MAUI 提供诸多改进和功能,允许开发者直接使用 .NET 开发桌面和移动客户端程序。
主题
微软利用 Blazor Server 开发了 themesof.net 用于展示和跟踪主题,用户可以通过查看该网站中列出的各项目内容来跟踪 .NET 目前的状态和未来的发展计划。
平台支持
.NET 6 LTS 将在 2021 年 11 月正式发布,除了目前支持的平台之外,还将支持以下平台:
- Android
- iOS
- Mac 和 Mac Catalyst(x64 和 M1)
- Windows Desktop 的 ARM64
MAUI
.NET MAUI (Multi-platform App UI) 是一组现代 UI 框架,在 Xamarin.Forms 的基础上扩展并集成到 .NET 6 中,利用 MAUI 将能够面向 Android、iOS、macOS 和 Windows 等构建应用。
在统一的过程中,将会把 Xamarin.Essentials 库集成到 MAUI 当中,除此之外你将还能容易地利用设备机能,例如传感器、照片库、联系人和存储等等。
.NET 6 Preview 1 中首先包含了 Android 和 iOS 两个平台的 MAUI,可以在此处查看示例项目和安装说明:https://github.com/dotnet/net6-mobile-samples。
未来还将添加 macOS 和 Windows 桌面支持,以及除了 XAML 热重载之外,还将支持 C# 代码的热重载。
对于今天已经在使用 Xamarin 构建应用的开发者,将会提供转换工具和迁移指导帮助迁移到 .NET 6,
上图中,Android 和 iOS 应用直接通过 dotnet
的命令从命令行中启动,分别运行在模拟器中,代码利用了 dotnet-runtimeinfo 在控制台中输出了运行时的信息:应用是利用 .NET 的 SDK 开发,在 mono 的运行时之上使用 .NET 的类库。
Blazor 桌面应用
Blazor Desktop 将允许开发者利用 Blazor 技术开发混合客户端程序,将原生 UI 和 Web 技术混合起来构建原生的客户端应用。
例如你可以直接将 Blazor 作为组件集成到现有的 WPF 应用当中,下面是几个例子:
在 macOS 运行的 Blazor 桌面客户端应用:
在 WPF 中集成 Blazor 的混合应用:
快速内部循环
快速迭代开发是任何高效且令人愉悦的平台的标志,为此微软启动了一个新项目:快速内部循环(fast inner loop)。该项目旨在让应用的构建速度大幅度提高,并提供在运行时修改代码无需重新编译和重启应用,直接热重载代码并应用的功能。
几年来 .NET 一直具有 XAML 热重载功能,这一次,热重载功能将不仅局限于 XAML,而是扩展到 C# 和 IL。微软将定义代码热重载模型以让该功能支持所有类型的 app,这其中的一些功能需要通过改进运行时来做到,届时 CoreCLR 和 Mono 将一起受益。
最终通过该项目,开发者将能够非常快的构建项目,并且在调试运行时直接跳过编译,通过热重载功能完成代码的修改,而无需重新启动。
ARM64
.NET 将持续改进 ARM64 的支持。
ARM64 的性能改进计划
.NET 将持续改善在 ARM64 架构上的性能表现,具体可以去这里查看 .NET 6 在此方面的计划:https://github.com/dotnet/runtime/issues/43629
WPF 支持
WPF 现在支持 Windows ARM64 了,如需反馈相关问题可以前去: https://github.com/dotnet/wpf/issues/4117
macOS ARM64 支持
.NET 5 将提供对 macOS ARM64 的 x86_64 模拟器支持,而 .NET 6 将提供对 macOS ARM64 的原生支持,下图中展示了在 macOS 原生运行 ARM64 的 .NET:
容器
容器是 .NET 团队的日常工作,既是构建基础结构的基础,又是产品方案,.NET 性能测试也在容器中完成。
在 .NET 6 中将针对如下项目改进容器支持:
- 改善容器的缩放支持,并更好地支持 Windows 进程隔离的容器。 我们还计划了一种针对密度和累加机器性能的新型容器性能测试。
- 使用 PGO 减小容器镜像的大小
- 通过使用 R2R 版本气泡来提高启动和吞吐量性能。
- 默认情况下,通过使用现代向量指令来提高启动和吞吐量性能。
- [高级方案] 为 R2R 合成镜像启用大页面支持
除了第一条之外,上述所有特性都依赖 crossgen2 完成。虽然很多特性和容器没太多关系,但是 .NET 将会在容器中使用他们。
在某些情况下,例如版本气泡,容器可能是唯一默认启用功能的分发工具。
容器的一个重要优点是,与更通用的 .tar.gz,.deb 或 .msi 交付方式相比,可以以更“自以为是”的配置提供 .NET,例如提供性能更高配置的容器,但可能无法在所有情况下都可用(例如在旧硬件上)。
.NET 6 镜像将分别在 Alpine 3.13(或更高)、Debian 11(bullseye)和 Ubuntu 20.04 上构建。
主题:PGO - 利用运行时信息提升启动速度和吞吐量性能
本次来介绍一下 PGO 这个主题,后续每个 Preview 都将会介绍一些主题。
PGO(Profile-Guided Optimization) 的目标是优化二进制内的原生代码,让其在 CPU 和其他方面的计算机上执行的效率更高。优化代码可以让程序速度更快,并能减少内存使用和硬盘使用。现在微软已经在 native runtime 上面使用了 PGO,这是由在 Windows、macOS 和 Linux 上使用的 C++ 编译器提供,虽然这部分内容很相关并重要,但是并不是这里的 PGO 所说的东西。
这里的 PGO 是指优化 RyuJIT(.NET 的 JIT)产生的本机代码。Crossgen 和 RyuJIT 已经支持了 PGO,但是在 .NET 6 中将在可用度和性能上大幅改善该特性。
其中一个 PGO 技术是冷热分离:将最常调用的代码放在一起(热),不常调用的代码放到另一边(冷)。理性情况下,由于已处于从磁盘加载的页面的相同物理顺序中,接下来一连串的方法调用或者基本块访问需要的代码已经被载入到了 CPU 的缓存中。这种情况下方法或者块的调用将非常快。除了上述的冷热之外,还引入了 “非常冷” 这一组,这一组代码将不会预编译任何的原生代码,只会在需要的时候被 JIT 编译,这么做将能在不牺牲主要性能的情况下减小程序体积。
PGO 这项技术虽然不仅仅只在 .NET 中有所应用,但是该项技术非常不流行,因为做起来非常有难度,而且这类工具通常很笨重,并且在处理过程中需要非常注意细节:开发者必须定期进行“训练”,在此过程中,需要在各种情况下运行程序,同时工具会收集程序的运行数据,然后把这些数据提供给编译器,编译器根据这些数据改善编译结果,开发者接着去验证结果。这一系列的过程非常麻烦且令人沮丧。
在 .NET 6 中,将计划做以下 PGO 相关的支持:
- 提供一组容易使用的工具用于 PGO 训练和数据分析
- 为 .NET 库公开发布分享训练数据,以让其他人能够使用这些数据
- 在生产环境中启用训练数据收集
- 在运行时允许 JIT 使用静态训练的数据
- 在运行时允许 JIT 生成和使用动态训练的数据(不需要手动训练)
PGO 将期望能得到 10% 的启动速度提升和吞吐量性能提升,对于计算不敏感的工作负载,提升将更为显著。
.NET 期望用两个大版本的时间完成此方面完整的企划。
TFM
完整的 .NET TFM 将包含如下:
- net6.0
- net6.0-android
- net6.0-ios
- net6.0-maccatalyst
- net6.0-macos
- net6.0-tvos
- net6.0-windows
通过类似 <TargetFramework>net6.0</TargetFramework>
的方式可以适配到不同的平台上。
命令行
在 .NET 6 中,对 CLI 也有不少改进。
响应文件
命令行支持响应文件,通过响应文件可以绕过控制台对于字符数量的限制,同时也能减少用户反复输入相同命令的麻烦。
响应文件的支持已经被添加到 .NET CLI 中,语法是 @file.rsp
。而该文件本身就是简单的一行文本,会在命令行中被结构化,例如下图使用响应文件进行 dotnet build
:
新指令
添加了两个新的指令:
dotnet [suggest]
:用于搜索命令,例如dotnet suggest buil
将返回build
、build-server
和msbuild
等搜索结果dotnet [parse]
:用于解析命令,可用来分析为什么输入的命令有误等问题
库
.NET 6 Preview 1 的类库新增了一些 API。
System.Math 的新数学 API
SinCos
:用于同时计算sin
和cos
ReciprocalEstimate
:用于估算1 / x
ReciprocalSqrtEstimate
:用于估算1 / Sqrt(x)
Clamp
,DivRem
,Min
和Max
支持nint
和nuint
Abs
和Sign
支持nuint
DivRem
返回元组的多态
Windows 访问控制列表的支持改进
用于操作 Windows ACLs 的包System.Threading.AccessControl 已经被改进,为 EventWaitHandle
, Mutex
和 Semaphore
加入了新的 OpenExisting
和 TryOpenExisting
方法,允许打开现有的通过特殊 Windows 安全描述符创建的线程同步对象。
运行时
.NET 6 Preview 1 的新运行时特性包含 Apple Silicon 支持、crossgen2 以及部分 PGO 改进等等。
可移植线程池
.NET 6 通过托管实现,重新实现了 .NET 的线程池,并且作为 .NET 默认的线程池。
该线程池可以在不同平台(CoreCLR、Mono 等)上提供相同的行为。
如果想要恢复以前用非托管代码实现的线程池,可以指定环境变量 COMPlus_ThreadPool_UsePortableThreadPool=0
,不过后续原来的线程池实现可能会被移除。
Apple Silicon 支持
.NET 6 Preview 1 开始原生支持 Apple Silicon,但是目前还处于 alpha 状态。
对于 .NET 6,将同时支持 macOS ARM64 的原生运行以及通过罗塞塔 2 模拟运行 x64 版本的 .NET。
下面是同一台 macOS ARM64 机器上运行 .NET 5 和 .NET 6 的输出,你可以看到区别:
rich@MacBook-Air dotnet-runtimeinfo % pwd
/Users/rich/git/core/samples/dotnet-runtimeinfo
rich@MacBook-Air dotnet-runtimeinfo % dotnet run
**.NET information
Version: 5.0.3
FrameworkDescription: .NET 5.0.3
Libraries version: 5.0.3
Libraries hash: c636bbdc8a2d393d07c0e9407a4f8923ba1a21cb
**Environment information
OSDescription: Darwin 20.4.0 Darwin Kernel Version 20.4.0: Fri Jan 22 03:28:00 PST 2021; root:xnu-7195.100.296.111.3~3/RELEASE_ARM64_T8101
OSVersion: Unix 11.0.0
OSArchitecture: X64
ProcessorCount: 8
rich@MacBook-Air dotnet-runtimeinfo % export DOTNET_ROLL_FORWARD=Major
rich@MacBook-Air dotnet-runtimeinfo % export DOTNET_ROLL_TO_PRERELEASE=1
rich@MacBook-Air dotnet-runtimeinfo % dotnet run
**.NET information
Version: 6.0.0
FrameworkDescription: .NET 6.0.0-preview.1.21102.12
Libraries version: 6.0.0-preview.1.21102.12
Libraries hash: 9b2776d48183632662e0be873cef029cdb57f8d6
**Environment information
OSDescription: Darwin 20.4.0 Darwin Kernel Version 20.4.0: Fri Jan 22 03:28:00 PST 2021; root:xnu-7195.100.296.111.3~3/RELEASE_ARM64_T8101
OSVersion: Unix 11.3.0
OSArchitecture: Arm64
ProcessorCount: 8
Apple Silicon
原生支持
苹果的新芯片相对于其他 ARM64 芯片来说有更严格运行要求,其中包括对 JIT 的要求,.NET 6 Preview 1 已经满足这些要求。
通用二进制(Universal binaries)是发布到苹果商店的另一个新要求,但是目前 .NET 6 应用并不支持,因此不能发布到苹果的应用商店,不过这部分也不是大部分 .NET 开发者所需要的。如果需要的话,.NET 会在 .NET 7 重新考虑是否支持通用二进制。
为了支持 Apple Silicon ABI 的要求,.NET 已经做出了一些改进,下面是对应的 Pull Request:
- Use bytes in
fgArgTabEntry
- Support byte sizes from lowering to codegen
- Preserve precise argument sizes
- Use 4-byte stack alignment for
hfa<float>
- Arg alignment
调试
目前还无法在 Apple Silicon 上面调试原生 ARM64 的 .NET 程序,这将会在 Preview 3 或之后提供支持。不过通过罗塞塔 2 模拟运行 x64 的运行时是支持调试的。
已知问题
- 由于 Apple Silicon 页面大小为 16K,对于大的栈分配,JIT 无法生成清栈代码
- 可靠性不如 x64 版本的
- 因为没有机器所以 macOS ARM64 的 CI 还没有启用
- 还没有设计同时运行原生 .NET 和模拟 .NET 的方式,因此如果想要在 macOS ARM64 同时使用 .NET 6 和 .NET 5,建议通过
.tar.gz
手动安装,而不是通过包管理器直接安装,以便于控制版本 .tar.gz
的包被误报成恶意软件了
罗塞塔 2 仿真
.NET 5 的 x64 版本目前已经支持通过罗塞塔 2 仿真运行在 macOS ARM64 上。
单文件应用
在 .NET 6,完成了用于 Windows 和 macOS 的完全单文件应用支持。此前这一项只支持 Linux,所以在其他平台即使利用 PublishSingleFile
也还会带几个 .NET 的 dll 或者 dylib 文件。.NET 6 开始,这些文件都将被静态链接到程序当中,变成真正的单文件。
当然,这只是对于 runtime 而言的,如果你的程序引用了其他的 native 库,那么这些库还是会外带,而不会被链接进去。
macOS 单文件应用签名
.NET 6 的单文件应用现在满足了苹果的公证和签名要求,相关改动可以参考:https://github.com/dotnet/runtime/issues/3671。
Crossgen2
Crossgen2 将代替原有的 crossgen,旨在带来如下优点:
- 使 crossgen 更加高效并启用现有 crossgen 无法启用的一些特性
- PGO 相关的计划取决于 crossgen2,影响 R2R 代码生成。.NET 6 中有 6 个项目依赖 crossgen2,这个东西非常重要
- 允许在不同系统和架构之间交叉编译
目前 .NET 的核心库 System.Private.Corelib 本身已经使用了 crossgen2 进行预编译了,后面将会把整个 .NET 自身利用 crossgen2 进行预编译。
这个项目并不是为了改进性能的,而是为托管 RyuJIT 提供更好的体系结构,在不需要或者不启动运行时的情况下以“离线”方式生成代码,以便更好地支持交叉编译。
动态 PGO
动态 PGO是 .NET 正在探索和启用的PGO模式之一。一方面,可以将其视为“无需训练” 的 PGO。在文章的前面,我描述了 PGO 的使用过程,而动态PGO的优点是不需要任何这些,但是缺点是过程需要更长的时间才能达到最佳性能。
另一方面,动态 PGO 可以被认为是当今分层编译所使用的更为简单(且效果较差)的策略的替代品。
最引人注目案例中,动态 PGO 和静态 PGO 组合到了一起。在运行时, JIT 可以细化一小部分经过静态编译的 PGO 优化代码,以提供最大的好处。
例如,JIT 可以注意到,在到目前为止已加载的过程中,只有一个类实现了给定的接口,然后,JIT 可以生成通过类直接调用的代码,而不是通过接口间接调用。
这种经典的编译器技术称为去虚拟化,它通过消除方法调用中的间接操作并启用内联来提高性能。
作为启用动态 PGO 的一部分,.NET 已经做出了如下改动:
- Guarded devirtualization:通过类概要文件和针对类类型的探针启用受保护的去虚拟化
- Flowgraph visualizations:带有配置文件数据的流程图的图形转储
- Redundant branch elimination:优化完全确定分支结果的分支
- Enable CSE for PGO scenarios:为 PGO 引入的类型测试启用 CSE
ARM64 性能
Preview 1 中包含了以下改进:
- Stack frame zeroing:使用 SIMD 清零初始帧
上图展示了改进带来的影响,数值越低越好。绿色线是改进之后的,橘色是另一个实验性改进的,蓝色是原来的。
硬件加速的 struct
struct 值类型是 .NET 中很重要的性能工具,被频繁使用,但是此前 struct 并没有在 JIT 中得到应得的优化。在 .NET 5 和 .NET 6 中,将针对 struct 进行性能优化,一部分是确保 struct 能够被加载到 CPU 寄存器中进行访问。
Preview 1 中包含如下改进:
- Struct promotion for HFAs:Struct promotion for HFA and non-HFA multireg args on x64 and Arm64
- Enregister HFAs:Enregister HFAs and other structs with matching fields
结语
以上就是个人认为值得关注的 .NET 6 Preview 1 带来的新特性了,后续还会有十个左右的预览版本,会包含更多的新增和改进内容,敬请期待吧。