统一并发 IV - 跨平台
面向 .NET 和 .NET Core 的跨平台面向对象同步原语方法,基于通用线程和 Async/Await 两个接口之间的一个共享模式。
系列文章
引言
今天,我想宣布 Unified Concurrency 已经实现跨平台!
Unified Concurrency 现在兼容 .Net Standard 2.0,确保在 .NET 4.7+ 和 .NET Core 2.0+、Mono 5.4+ 中可用。
近期 .NET 世界的跨平台开发似乎势不可挡,从 .Net Standard 2.0 来看,该平台的采用率在开源和商业领域似乎都得到了加速。这为 Unified Concurrency 实现跨平台提供了极大的动力。我仍然打算尽可能地保留 .Net 4.6 版本,但主要开发已转移到 .Net Standard 项目。
将库 GreenSuperGreen
(包含 Unified Concurrency)迁移到 .Net Standard 2.0 也需要对整个基准测试和交叉基准测试库进行相同的更新,从而有机会在 .NET / .NetCore 以及可能的 Mono 上运行基准测试、交叉基准测试和跨平台基准测试。对 Linux 进行基准测试会很有趣,但目前基准测试依赖于 PerformanceCounters
,而它们是平台相关的(Windows),并且这个问题似乎在 .Net Standard 的未来版本中仍然悬而未决。
包含所有已实现同步原语的示例,可在 .NetCore 2.1 下的单元测试项目中开始使用。
Unified Concurrency 框架 在开源的 GreenSuperGreen 库中实现,可在 GitHub 和 Nuget 上获取。
.NET 4.6
NetStandard 2.0
- https://nuget.net.cn/packages/GreenSuperGreen.NetStandard/
- https://nuget.net.cn/packages/GreenSuperGreen.NetStandard.Test/
- https://nuget.net.cn/packages/GreenSuperGreen.Benchmarking.NetStandard/
NetCore 2.1
Net 4.7.2
更多同步原语
现在可以使用另外 3 个同步原语,还有 1 个仅供内部使用(用于基准测试)。
AsyncSemaphoreSlimLockUC : IAsyncLockUC
基于 SemaphoreSlim WaitAsync/Release 的锁,似乎能很好地实现 FIFO 风格的公平访问。
性能上与 AsyncLockUC
相似。
SemaphoreSlimLockUC : ILockUC
基于 SemaphoreSlim Wait/Release 的锁,采用混合方法和原子指令,无法很好地实现 FIFO 风格,不公平访问可能导致线程停滞!
SemaphoreLockUC : ILockUC
基于 Semaphore WaitOne/Release 的锁,依赖于操作系统,在 Windows 上大致为 FIFO,不保证公平性。
MutexLockUC : ILockUC - 内部使用,仅用于基准测试
此同步原语不可访问,仅供预定义的基准测试项目使用,因为它要求进入和退出调用具有线程亲和性,这在 Unified Concurrency 的设计中不支持,但对于基准测试而言,它是可维护且有趣的,可以收集数据。
.NET Core 加速
根据微软、外部来源和技术社区的报告,.NET Core 可以加速现有代码库,这已成为普遍认知。
通过跨平台基准测试,我可以在两个方面报告改进。
.NET Core 加速:JIT 编译
在基准测试场景中,吞吐量周期的顺序基线是衡量同一硬件上潜在加速的有用工具,而该代码在 .Net Core 2.1 上的速度比 .Net 4.7.2 快 1.997 倍。这并不意味着所有代码都会快这么多倍,只是某些代码可以更有效地进行 JIT 编译并因此运行得更快,但潜在的加速总是代码相关的,有时可能无法进一步优化。 Stephen Toub 也曾 报道过某些特定情况下的类似加速。
.NET Core 加速:C# lock - Monitor 类改进
跨平台基准测试结果显示,在重负载场景和不良邻居场景下,C# lock(Monitor 类)有显著改进。
.NET 实现容易出现 CPU 僵死,即 C# lock(Monitor 类)浪费大部分 CPU 资源,而完成的工作却很少,同步成本占据了大部分 CPU 资源。这在之前的文章中已有报道。
.NET Core 2.1 似乎对 C# lock(Monitor 类 / .NET Core 2.1 运行时中的 C++ AwareLock 类)有更好的实现。
根据图 2,很容易得出 .NET Core 2.1 在性能上取得优势的地方。C# 锁通常分布在大多数项目的代码中,是许多常用库(包括运行时本身)的一部分,在这里,我们在某些时间情况下看到了显著的 CPU 资源改进,CPU 浪费减少了 80% 以上!请比较 C# lock Monitor
类的蓝色和绿色趋势线。
JIT 改进很重要,但充斥着 C# 锁的多线程代码在许多项目中仍然普遍存在,从简单的代码到业务线代码库。
即使是简单的项目,性能提升也可能相当可观,而且升级到 .NetCore 3.0 并获得 WinForms 和 WPF 的访问权限可能非常吸引人。
这是 .NetCore 2.1 的一项重要改进,但仍有改进空间。例如,我们可以考虑 LockUC,请比较绿色和红色趋势线,这表明仍有约 10% 的提升空间,但这通常是以在 1 毫秒吞吐量周期以下的吞吐量略有下降为代价的,这时基于原子指令的同步原语可以通过保持合理的 CPU 浪费来帮助提高吞吐量,但这需要现代架构设计考虑到多核时代。
跨平台基准测试
摘要
本文作为 GreenSuperGreen
库(内置 Unified Concurrency)的 .NET Standard 2.0 版本发布公告,并包含了一些库的改进。
我们通过跨平台基准测试讨论并展示了从 .NET 升级到 .NET Core 2.1+ 的巨大潜力和动力,这得益于 JIT 编译的改进以及 C# lock(Monitor 类)在减少 CPU 浪费方面的改进,从而改善了多线程代码。
修订历史
- 2019年3月16日:初版
- 2019年3月24日:修正了一些拼写错误和增加了 2 个交叉基准测试