.Net Core运行速度比.Net Framework慢30% - 有什么方法可以提速吗?

8

背景

我们目前正在将代码库从 .Net Framework 4.8 转换为 .Net Core 3.1。

其中一些代码非常注重性能。一个例子是应用 Hamming 窗口滤波器的某些代码;我有点失望地发现,与为 .Net Framework 4.8 编译的相同代码相比,为 .Net Core 3.1 编译的代码运行速度慢了约30%。

如何重现

我创建了一个多目标 SDK 样式的项目,步骤如下:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworkS>net48;netcoreapp3.1</TargetFrameworkS>
    <Optimize>true</Optimize>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>
</Project>

这个项目的代码如下(重要的代码在 for (int iter = ... 循环内部):
using System;
using System.Diagnostics;

namespace FooBar
{
    class Program
    {
        static void Main()
        {
#if NET48
            Console.WriteLine("NET48: Is 64 bits = " + Environment.Is64BitProcess);
#elif NETCOREAPP3_1
            Console.WriteLine("NETCOREAPP3_1: Is 64 bits = " + Environment.Is64BitProcess);
#else
            Invalid build, so refuse to compile.
#endif
            double[] array = new double[100_000_000];
            var sw = Stopwatch.StartNew();

            for (int trial = 0; trial < 100; ++trial)
            {
                sum(array);
            }

            Console.WriteLine("Average ms for calls to sum() = " + sw.ElapsedMilliseconds/100);
            Console.ReadLine();
        }

        static double sum(double[] array)
        {
            double s = 0;

            for (int i = 0; i < array.Length; ++i)
            {
                s += array[i];
            }

            return s;
        }
    }
}

结果

我测试了.Net Core 3.1和.Net Framework 4.8的x86版本,并计时发布,得到如下结果:

.Net Core 3.1:

NETCOREAPP3_1: Is 64 bits = False
Average ms for calls to sum() = 122

.Net Framework 4.8:

NET48: Is 64 bits = False
Average ms for calls to sum() = 96

因此,.Net Core 3.1 的结果比 .Net Framework 4.8 慢约30%。
注意:这只影响x86版本的构建。对于x64版本,.Net Framework和.Net Core之间的时间差异不大。
我认为这非常令人失望,特别是因为我认为 .Net Core 可能具有更好的优化...
有人能建议一种方法来加速 .Net Core 输出,使其与 .Net Framework 4.8 处于同一水平吗?
编辑:我已更新代码和 .csproj 到最新版本以进行测试。 我添加了一些代码来指示正在运行哪个目标和平台,以确保运行正确的版本。
通过这次编辑,基本上只是在计时一个包含100,000,000个元素的大 double[] 数组的总和所需的时间。
我可以在我的两台PC和笔记本电脑上复制这个问题,它们都运行着最新的Windows 10和Visual Studio 2019安装程序+最新的 .Net Core 3.1。
然而,考虑到其他人无法复制这个问题,我将采纳Lex Li的建议,并将此贴到Microsoft的Github页面上。

1
通过这个,你可以直接与微软的工程师交流 https://github.com/dotnet/runtime/issues - Lex Li
1
正如我在帖子中所说的那样,这是一个发布版本。 - Matthew Watson
@Blindy 是的,那很有趣 - .Net Framework 和 .Net Core 在 x64 上花费的时间大致相同。 - Matthew Watson
4
我明白了,.NETCore更快。简单来说,您使用的是没有AVX2支持的旧处理器。需要注意您正在追求什么。内部循环将被执行十亿次,这告诉我们您使用的是3.6 GHz的处理器,并且正在尝试找到一个 CPU指令的差异。这很难做到。JIT编译器优化的工作至关重要。但是,现有的测试是无效的,当您将测试代码与实际代码混合使用时,它无法可靠地完成该任务。 - Hans Passant
3
最明显的加速代码的方法是使用Math.Abs()来填充window[],这样if语句就不再必要了。 - Hans Passant
显示剩余10条评论
2个回答

4

无法重现。

看起来.NET Core 3.1 至少对于 x86 来说更快。我对每个构建进行了五次或更多次的检查,输出几乎相同。

.NET Framework 4.8

Is 64 bits = False
Computed 4199,58 in 00:00:01.2679838
Computed 4199,58 in 00:00:01.1270864
Computed 4199,58 in 00:00:01.1163893
Computed 4199,58 in 00:00:01.1271687

Is 64 bits = True
Computed 4199,58 in 00:00:01.0910610
Computed 4199,58 in 00:00:00.9695353
Computed 4199,58 in 00:00:00.9601170
Computed 4199,58 in 00:00:00.9696420

.NET Core 3.1

Is 64 bits = False
Computed 4199,580000000003 in 00:00:00.9852276
Computed 4199,580000000003 in 00:00:00.9493986
Computed 4199,580000000003 in 00:00:00.9562083
Computed 4199,580000000003 in 00:00:00.9467359

Is 64 bits = True
Computed 4199,580000000003 in 00:00:01.0199652
Computed 4199,580000000003 in 00:00:00.9763987
Computed 4199,580000000003 in 00:00:00.9612935
Computed 4199,580000000003 in 00:00:00.9815544

更新了新的示例

NET48: Is 64 bits = False
Average ms for calls to sum() = 110

NETCOREAPP3_1: Is 64 bits = False
Average ms for calls to sum() = 110

硬件

Intel(R) Core(TM) i7-4700HQ CPU @ 2.40GHz

Base speed: 2,40 GHz
Sockets:    1
Cores:  4
Logical processors: 8
Virtualization: Enabled
L1 cache:   256 KB
L2 cache:   1,0 MB
L3 cache:   6,0 MB

奖励

如果代码对性能要求很高,或许可以考虑使用 SIMD 技术。

using System.Numerics;

const int ITERS = 100000;

int vectorSize = Vector<double>.Count;
Console.WriteLine($"Vector size = {vectorSize}");
            
for (int trial = 0; trial < 4; ++trial)
{
    double windowSum = 0;
    sw.Restart();
               
    for (int iter = 0; iter < ITERS; ++iter)
    {
        Vector<double> accVector = Vector<double>.Zero;
        for (int i = 0; i <= window.Length - vectorSize; i += vectorSize)
        {
            Vector<double> v = new Vector<double>(window, i);
            accVector += Vector.Abs(v);
        }
        windowSum = Vector.Dot(accVector, Vector<double>.One);
    }
               
    Console.WriteLine($"Computed {windowSum} in {sw.Elapsed}");
}

.NET Core 的卓越性在这里展现 :)

.NET Core 3.1

Is 64 bits = False
Vector size = 4
Computed 4199,58 in 00:00:00.3678926
Computed 4199,58 in 00:00:00.3046166
Computed 4199,58 in 00:00:00.2910941
Computed 4199,58 in 00:00:00.2900221

Is 64 bits = True
Vector size = 4
Computed 4199,58 in 00:00:00.3446433
Computed 4199,58 in 00:00:00.2616570
Computed 4199,58 in 00:00:00.2606452
Computed 4199,58 in 00:00:00.2582038

顺带一提,你希望在不能运行64位应用的PC上获得什么性能?谁说这台电脑不能运行64位应用程序了?当然不是我... - Matthew Watson
@MatthewWatson 我的意思是一些客户端正在运行32位操作系统的个人电脑。 - aepot
1
好的,稍等一下。与此同时,我刚刚在我的笔记本电脑上尝试了一下(对我来说是第三台测试电脑),那台电脑显示的时间也是相同的! - Matthew Watson
PC显示不同:CPUIntel(R) Core(TM) i7-6700 CPU @ 3.40GHz基础速度:3.41 GHz 插槽数:1 核心数:4 逻辑处理器:8 虚拟化:已启用 L1缓存:256 KB L2缓存:1.0 MB L3缓存:8.0 MB利用率5% 速度1.56 GHz 正常运行时间1:18:13:22 进程数273 线程数3526 句柄数130907 - Matthew Watson
2
这就是我的意思:我们必须集中精力在那些提高性能最有影响的领域。顺便说一下,这是医学诊断软件,所以我们必须小心! - Matthew Watson
显示剩余7条评论

0

好的,我尝试了一下,并且也包括了 .Net5,正如预期的那样它们在性能上几乎完全相同。

我认为这是使用更严格的测试方法(Benchmark.NET)的信号,因为此时我确信您没有运行正确的可执行文件,而 Benchmark.NET 会为您处理这个问题。

C:\Users\_\source\repos\ConsoleApp3\ConsoleApp3\bin\Release\net48>ConsoleApp3.exe
Computed 4199.58 in 00:00:01.0134120
Computed 4199.58 in 00:00:01.0136130
Computed 4199.58 in 00:00:01.0163664
Computed 4199.58 in 00:00:01.0161655

C:\Users\_\source\repos\ConsoleApp3\ConsoleApp3\bin\Release\net5>ConsoleApp3
Computed 4199.580000000003 in 00:00:01.0269673
Computed 4199.580000000003 in 00:00:01.0214385
Computed 4199.580000000003 in 00:00:01.0295102
Computed 4199.580000000003 in 00:00:01.0241006

C:\Users\_\source\repos\ConsoleApp3\ConsoleApp3\bin\Release\netcoreapp3.1>ConsoleApp3
Computed 4199.580000000003 in 00:00:01.0234075
Computed 4199.580000000003 in 00:00:01.0216327
Computed 4199.580000000003 in 00:00:01.0227448
Computed 4199.580000000003 in 00:00:01.0328213

很抱歉,结果只对x86有意义,因为问题似乎出现在那里。 - Matthew Watson
2
在32位的 .Net 领域中,你会遇到很多问题,它通常被认为是非平台的。许多主要的优化只会在64位上应用,以及前面提到的高效GC模型。欢迎在 .Net JIT 仓库中发布你的问题,但如果你不能使用64位运行时,请不要期望出现奇迹。 - Blindy
1
在使用.Net的32位环境中会遇到很多问题,因此它通常被认为不是一个平台。但我必须表示不同意! - Matthew Watson
1
@Blindy,我也不同意.NET的首选目标通常被认为是“非平台”。大多数MVP和整个MS编译器和运行时团队也是这样认为的。当您选择“任何CPU”时,首选目标是x86。暂且不论ARM将来会变得更加重要。 - Panagiotis Kanavos
1
实际上,在更现代的运行时,如Net Core 3和Net 5中,"Prefer 32-bit"默认是禁用的。请不要替别人发言。 - Blindy
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接