前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET性能系列文章一:.NET7的性能改进

.NET性能系列文章一:.NET7的性能改进

作者头像
JusterZhu
发布2022-12-07 21:01:49
6800
发布2022-12-07 21:01:49
举报
文章被收录于专栏:JusterZhu

以下文章来源于InCerry ,作者InCerry

这些方法在.NET7 中变得更快

照片来自 CHUTTERSNAP[1]Unsplash[2]

欢迎阅读.NET 性能系列的第一章。这一系列的特点是对.NET 世界中许多不同的主题进行研究、比较性能。正如标题所说的那样,本章节在于.NET7 中的性能改进。你将看到哪种方法是实现特定功能最快的方法,以及大量的技巧和敲门,如何付出较小的代价就能最大化你代码性能。如果你对这些主题感兴趣,那请您继续关注。

.NET 7 目前(17.10.2022)处于预览阶段,将于 2022 年 11 月发布。通过这个新版本,微软提供了一些大的性能改进。这篇 .NET 性能系列的第一篇文章,是关于从.NET6 到.NET7 最值得注意的性能改进。

LINQ

最相关的改进肯定是在 LINQ 中,在.NET 7 中dotnet 社区[3]利用 LINQ 中对数字数组的处理来使用Vector<T>(SIMD)。这大大改善了一些 LINQ 方法性能,你可以在List<int>int[]以及其他数字集合上调用。现在 LINQ 方法也能直接访问底层数组,而不是使用枚举器访问。让我们来看看这些方法相对于.NET 6 是如何表现的。

我使用BenchmarkDotNet[4]来比较.NET6 和.NET7 相同代码的性能。

1. Min 和 Max 方法

首先是 LINQ 方法Min()Max()。它们被用来识别数字枚举中的最低值或最高值。新的实现特别要求有一个先前枚举的集合作为源,因此我们必须在这个基准测试中创建一个数组。

代码语言:javascript
复制
[Params(1000)]
public int Length { get; set; }

private int[] arr;

[GlobalSetup]
public void GlobalSetup() => arr = Enumerable.Range(0, Length).ToArray();

[Benchmark]
public int Min() => arr.Min();

[Benchmark]
public int Max() => arr.Max();

在.NET 6 和.NET 7 上执行这些基准,在我的机器上会得出以下结果。

方法

运行时

数组长度

平均值

比率

分配

Min

1000

3,494.08 ns

53.24

32 B

Min

1000

65.64 ns

1.00

-

Max

1000

3,025.41 ns

45.92

32 B

Max

1000

65.93 ns

1.00

-

这里非常突出的是新的.NET7 所展示的性能改进有多大。我们可以看到与.NET 6 相比,改进幅度超过 4500%。这不仅是因为在内部实现中使用了另一种类型,而且还因为不再发生额外的堆内存分配。

2. Average 和 Sum

另一个很大的改进是Average()Sum()方法。当处理大的double集合时,这些性能优化能展现出更好的结果,这就是为什么我们要用一个double[]来测试它们。

代码语言:javascript
复制
[Params(1000)]
public int Length { get; set; }

private double[] arr;

[GlobalSetup]
public void GlobalSetup()
{
    var random = new Random();
    arr = Enumerable
        .Range(0, Length)
        .Select(_ => random.NextDouble())
        .ToArray();
}

[Benchmark]
public double Average() => arr.Average();

[Benchmark]
public double Sum() => arr.Sum();

结果显示,性能显著提高了 500%以上,而且同样没有了内存分配!

方法

运行时

数组长度

平均值

比率

分配

Average

1000

3,438.0 ns

5.50

32 B

Average

1000

630.3 ns

1.00

-

Sum

1000

3,303.8 ns

5.25

32 B

Sum

1000

629.3 ns

1.00

-

这里的性能提升并不像前面的例子那么突出,但还是非常高的!

3. Order

接下来是这是新增了两个排序方法Order()OrderDescending()。当你不想映射到IComparable类型时,应该使用新的方法取代.NET7 中旧的OrderBy()OrderByDescending()方法。

代码语言:javascript
复制
[Params(1000)]
public int Length { get; set; }

private double[] arr;

[GlobalSetup]
public void GlobalSetup()
{
    var random = new Random();
    arr = Enumerable
        .Range(0, Length)
        .Select(_ => random.NextDouble())
        .ToArray();
}

[Benchmark]
public double[] OrderBy() => arr.OrderBy(d => d).ToArray();

#if NET7_0
[Benchmark]
public double[] Order() => arr.Order().ToArray();
#endif

方法

数组长度

平均值

风波无

OrderBy

1000

51.13 μs

27.61 KB

Order

1000

50.82 μs

19.77 KB

在这个基准中,只使用了.NET 7,因为Order()方法在旧的运行时中不可用。

我们无法看到这两种方法之间的性能影响。然而,我们可以看到的是在堆内存分配方面有很大的改进,这将显著减少垃圾收集,从而节省一些 GC 时间。

System.IO

在.NET 7 中,Windows 下的 IO 性能有了些许改善。WriteAllText()方法不再使用那么多分配的内存,ReadAllText()方法与.NET 6 相比也快了一些。

代码语言:javascript
复制
[Benchmark]
public void WriteAllText() => File.WriteAllText(path1, content);

[Benchmark]
public string ReadAllText() => File.ReadAllText(path2);

方法

运行时

平均值

比率

分配

WriteAllText

193.50 μs

1.03

10016 B

WriteAllText

187.32 μs

1.00

464 B

ReadAllText

23.29 μs

1.08

24248 B

ReadAllText

21.53 μs

1.00

24248 B

序列化 (System.Text.Json)

来自System.Text.Json命名空间的JsonSerializer得到了一个小小的升级,一些使用了反射的自定义处理程序会在幕后为你缓存,即使你初始化一个JsonSerialzierOptions的新实例。

代码语言:javascript
复制
private JsonSerializerOptions options = new JsonSerializerOptions();
private TestClass instance = new TestClass("Test");

[Benchmark(Baseline = true)]
public string Default() => JsonSerializer.Serialize(instance);

[Benchmark]
public string CachedOptions() => JsonSerializer.Serialize(instance, options);

[Benchmark]
public string NoCachedOptions() => JsonSerializer.Serialize(instance, new JsonSerializerOptions());

public record TestClass(string Test);

在上面代码中,对NoCachedOptions()的调用通常会导致JsonSerialzierOptions的额外实例化和一些自动生成的处理程序。在.NET 7 中这些实例是被缓存的,当你在代码中使用这种方法时,你的性能会好一些。否则,无论如何都要缓存你的JsonSerialzierOptions,就像在CachedOptions例子中,你不会看到很大的提升。

方法

运行时

平均值

比率

分配

分配比率

Default

135.4 ns

1.04

208 B

3.71

CachedOptions

145.9 ns

1.12

208 B

3.71

NoCachedOptions

90,069.7 ns

691.89

7718 B

137.82

Default

130.2 ns

1.00

56 B

1.00

CachedOptions

129.8 ns

0.99

56 B

1.00

NoCachedOptions

533.8 ns

4.10

345 B

6.16

基本类型

1. Guid 相等比较

有一项改进,肯定会导致现代应用程序的性能大增,那就是对Guid相等比较的新实现。

代码语言:javascript
复制
private Guid guid0 = Guid.Parse("18a2c952-2920-4750-844b-2007cb6fd42d");
private Guid guid1 = Guid.Parse("18a2c952-2920-4750-844b-2007cb6fd42d");

[Benchmark]
public bool GuidEquals() => guid0 == guid1;

方法

运行时

平均值

比率

GuidEquals

1.808 ns

1.49

GuidEquals

1.213 ns

1.00

可以感觉到,新的实现也使用了 SIMD,比旧的实现快 30%左右。

由于有大量的 API 使用Guid作为实体的标识符,这肯定会积极的产生影响。

2. BigInt 解析

一个很大的改进发生在将巨大的数字从字符串解析为BigInteger类型。就我个人而言,在一些区块链项目中,我曾使用过BigInteger类型,在那里有必要使用这种类型来表示 ETH 代币的精度。所以在性能方面,这对我来说会很方便。

代码语言:javascript
复制
private string bigIntString = string.Concat(Enumerable.Repeat("123456789", 100000));

[Benchmark]
public BigInteger ParseBigInt() => BigInteger.Parse(bigIntString);

方法

运行时

平均值

比率

分配

ParseBigInt

2.058 s

1.62

2.09 MB

ParseBigInt

1.268 s

1.00

2.47 MB

我们可以看到性能有了明显的提高,不过我们也看到它比.NET6 上多分配一些内存。

3. Boolean 解析

对于解析boolean类型,我们也有显著的性能改进:

代码语言:javascript
复制
[Benchmark]
public bool ParseBool() => bool.TryParse("True", out _);

方法

运行时

平均值

比率

ParseBool

8.164 ns

5.21

ParseBool

1.590 ns

1.00

诊断

System.Diagnostics命名空间也进行了升级。进程处理有两个重大改进,Stopwatch有一个新功能。

1. GetProcessByName

代码语言:javascript
复制
[Benchmark]
public Process[] GetProcessByName()
      => Process.GetProcessesByName("dotnet.exe");

方法

运行时

平均值

比率

分配

分配比率

GetProcessByName

2.065 ms

1.04

529.89 KB

247.31

GetProcessByName

1.989 ms

1.00

2.14 KB

1.00

新的GetProcessByName()的速度并不明显,但使用的分配内存比前者少得多。

2. GetCurrentProcessName

代码语言:javascript
复制
[Benchmark]
public string GetCurrentProcessName()
      => Process.GetCurrentProcess().ProcessName;

方法

运行时

平均值

比率

分配

分配比率

GetCurrentProcessName

1,955.67 μs

103.02

3185 B

6.98

GetCurrentProcessName

18.98 μs

1.00

456 B

1.00

在这里,我们可以看到一个更有效的内存方法,对.NET 7 的实现有极高的性能提升。

3. Stopwatch

Stopwatch被广泛用于测量运行时的性能。到目前为止,存在的问题是,使用Stopwatch需要分配堆内存。为了解决这个问题,dotnet 社区实现了一个静态函数GetTimestamp(),它仍然需要一个复杂的逻辑来有效地获得时间差。现在又实现了另一个静态方法,名为GetElapsedTime(),在这里你可以传递之前的时间戳,并在不分配堆内存的情况下获得经过的时间。

代码语言:javascript
复制
[Benchmark(Baseline = true)]
public TimeSpan OldStopwatch()
{
    Stopwatch sw = Stopwatch.StartNew();
    return sw.Elapsed;
}

[Benchmark]
public TimeSpan NewStopwatch()
{
    long timestamp = Stopwatch.GetTimestamp();
    return Stopwatch.GetElapsedTime(timestamp);
}

Method

Mean

Ratio

Allocated

Alloc Ratio

OldStopwatch

39.44 ns

1.00

40 B

1.00

NewStopwatch

37.13 ns

0.94

-

0.00

这种方法的速度优化并不明显,然而节省堆内存分配可以说是值得的。

结尾

我希望,我可以在性能和基准测试的世界里给你一个有趣的切入点。如果你关于特定性能主题想法,请在评论中告诉我。

如果你喜欢这个系列的文章,请务必关注我,因为还有很多有趣的话题等着你。

谢谢你的阅读!

版权

原文版权:Tobias Streng 翻译版权:InCerry

原文链接: https://medium.com/@tobias.streng/net-performance-series-1-performance-improvements-in-net-7-fb793f8f5f71

参考资料

[1]

CHUTTERSNAP: https://unsplash.com/@chuttersnap?utm_source=medium&utm_medium=referral

[2]

Unsplash: https://unsplash.com/?utm_source=medium&utm_medium=referral

[3]

dotnet社区: https://github.com/microsoft/dotnet

[4]

BenchmarkDotNet: https://benchmarkdotnet.org/articles/overview.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JusterZhu 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 这些方法在.NET7 中变得更快
  • LINQ
    • 1. Min 和 Max 方法
      • 2. Average 和 Sum
        • 3. Order
        • System.IO
        • 序列化 (System.Text.Json)
        • 基本类型
          • 1. Guid 相等比较
            • 2. BigInt 解析
              • 3. Boolean 解析
              • 诊断
                • 1. GetProcessByName
                  • 2. GetCurrentProcessName
                    • 3. Stopwatch
                      • 结尾
                        • 版权
                          • 参考资料
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档