原文 | Brennan Conroy

翻译 | 郑子铭

受到 Stephen Toub 关于 .NET 性能的博文的启发,我们正在写一篇类似的文章来强调 6.0 中对 ASP.NET Core 所做的性能改进。

基准设置

我们将在整个示例中使用 BenchmarkDotNet。在 https://github.com/BrennanConroy/BlogPost60Bench 上提供了一个 repo,其中包括本文中使用的大部分基准。

这篇文章中的大多数基准测试结果都是使用以下命令行生成的:

dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 net5.0 net6.0

然后从列表中选择要运行的特定基准。

这告诉 BenchmarkDotNet:

  • 在发布配置中构建所有内容。
  • 针对 .NET Framework 4.8 外围区域构建它。
  • 在 .NET Framework 4.8、.NET Core 3.1、.NET 5 和 .NET 6 上运行每个基准测试。

对于某些基准测试,它们仅在 .NET 6 上运行(例如,如果比较同一版本上的两种编码方式):

dotnet run -c Release -f net6.0 --runtimes net6.0

而对于其他版本,只运行了其中的一个子集,例如

dotnet run -c Release -f net5.0 --runtimes net5.0 net6.0

我将包括用于运行每个基准测试的命令当他们出现时。

帖子中的大部分结果都是通过在 Windows 上运行上述基准测试生成的,主要是为了将 .NET Framework 4.8 包含在结果集中。但是,除非另有说明,否则所有这些基准测试通常在 Linux 或 macOS 上运行时都显示出相当的改进。只需确保您已安装要测量的每个运行时。基准测试是在夜间构建的 .NET 6 RC1 以及最新发布的 .NET 5 和 .NET Core 3.1 下载中运行的。

Span

自从在 .NET 2.1 中添加 Span 以来的每个版本,我们都转换了更多代码以在内部和作为公共 API 的一部分使用跨度以提高性能。本次发布也不例外。

PR dotnet/aspnetcore#28855 在添加两个 PathString 实例时删除了来自 string.SubString 的 PathString 中的临时字符串分配,而是使用 Span

dotnet run -c Release -f net48 --runtimes net48 net5.0 net6.0 --filter *PathStringBenchmark*
private PathString _first = new PathString("/first/");
private PathString _second = new PathString("/second/");
private PathString _long = new PathString("/longerpathstringtoshowsubstring/");

[Benchmark]
public PathString AddShortString()
{
    return _first.Add(_second);
}

[Benchmark]
public PathString AddLongString()
{
    return _first.Add(_long);
}

dotnet run -c Release -f net48 --runtimes net48 netcoreapp3.1 net5.0 net6.0 --filter *ContentDispositionBenchmark*
[Benchmark]
public void ParseContentDispositionHeader()
{
    var contentDisposition = new ContentDispositionHeaderValue("inline");
    contentDisposition.FileName = "FileÃName.bat";
}

来自 martincostellodotnet/aspnetcore#31333 将 Http.Sys 转换为使用 LoggerMessage.Define,这是高性能日志记录 API。这避免了不必要的值类型装箱、日志格式字符串的解析,并且在某些情况下避免了在日志级别未启用时分配字符串或对象。

dotnet/aspnetcore#31784 添加了一个新的 IApplicationBuilder。使用重载来注册中间件,以避免在运行中间件时进行一些不必要的按请求分配。旧代码如下所示:

app.Use(async (context, next) =>
{
    await next();
});

新代码如下所示:

app.Use(async (context, next) =>
{
    await next(context);
});

下面的基准测试模拟了中间件管道,而没有设置服务器来展示改进。使用 int 代替 HttpContext 进行请求,中间件返回完成的任务。

dotnet run -c Release -f net6.0 --runtimes net6.0 --filter *UseMiddlewareBenchmark*
static private Func<Func<int, Task>, Func<int, Task>> UseOld(Func<int, Func<Task>, Task> middleware)
{
    return next =>
    {
        return context =>
        {
            Func<Task> simpleNext = () => next(context);
            return middleware(context, simpleNext);
        };
    };
}

static private Func<Func<int, Task>, Func<int, Task>> UseNew(Func<int, Func<int, Task>, Task> middleware)
{
    return next => context => middleware(context, next);
}

Func<int, Task> Middleware = UseOld((c, n) => n())(i => Task.CompletedTask);
Func<int, Task> NewMiddleware = UseNew((c, n) => n(c))(i => Task.CompletedTask);

[Benchmark(Baseline = true)]
public Task Use()
{
    return Middleware(10);
}

[Benchmark]
public Task UseNew()
{
    return NewMiddleware(10);
}
MethodMeanRatioAllocatedUse15.832 ns1.0096 BUseNew2.592 ns0.16–

总结

希望您喜欢阅读 ASP.NET Core 6.0 中的一些改进!我鼓励您查看 .NET 6 博客文章中的性能改进,它超越了运行时的性能。

原文链接

Performance improvements in ASP.NET Core 6

【译】ASP.NET Core 6 中的性能改进-LMLPHP

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

03-11 01:32