为什么.NET Core 2.0比.NET Framework 4.6.1表现更差

14

我编写了一个程序,创建了4个线程,每个线程将20,000个数字从小到大排序50次。我在.NET Core 2.0和.NET Framework 4.6.1上多次运行了此测试。在这个测试中,.NET Framework总是比.NET Core表现更好。

设置

  • .NET Core以发布模式发布
  • Windows 10,i7双核处理器,4个线程(超线程)

下面的代码用于对比这两个框架。

static void Main()
    {
        const int amountParallel = 4;
        var globalStopwatch = new Stopwatch();

        globalStopwatch.Start();

        var tasks = new Task<double[]>[4];

        for (int i = 0; i < amountParallel; i++)
        {
            tasks[i] = Start();
        }

        Task.WaitAll(tasks);

        globalStopwatch.Stop();

        Console.WriteLine("Averages: {0}ms", tasks.SelectMany(r => r.Result).Average(x => x));
        Console.WriteLine("Time completed: {0}", globalStopwatch.Elapsed.TotalMilliseconds);
    }

    private static Task<double[]> Start()
    {
        return Task.Factory.StartNew(() =>
        {
            var numbersToSort = new int[20000];

            var globalStopwatch = new Stopwatch();
            var individualStopwatch = new Stopwatch();
            var stopwatchTimes = new double[50];
            int temp;

            globalStopwatch.Start();

            for (int i = 0; 50 > i; i++)
            {
                Console.WriteLine("Running task: {0}", i);
                numbersToSort = Enumerable.Range(0, 20000).Reverse().ToArray();
                individualStopwatch.Start();

                for (int indexNumberArray = 0; numbersToSort.Length > indexNumberArray; indexNumberArray++)
                {
                    for (int sort = 0; numbersToSort.Length - 1 > sort; sort++)
                    {
                        if (numbersToSort[sort] > numbersToSort[sort + 1])
                        {
                            temp = numbersToSort[sort + 1];
                            numbersToSort[sort + 1] = numbersToSort[sort];
                            numbersToSort[sort] = temp;
                        }
                    }
                }

                individualStopwatch.Stop();

                Console.WriteLine("Task {0} completed, took: {1}ms", i, Math.Round(individualStopwatch.Elapsed.TotalMilliseconds));

                stopwatchTimes[i] = individualStopwatch.Elapsed.TotalMilliseconds;

                individualStopwatch.Reset();
            }

            globalStopwatch.Stop();

            Console.WriteLine("Total time: {0}s", Math.Round(globalStopwatch.Elapsed.TotalSeconds, 2));
            Console.WriteLine("Average: {0}ms", Math.Round(stopwatchTimes.Average(time => time)));

            return stopwatchTimes;
        }, TaskCreationOptions.LongRunning);
    }

测试结果:

.NET Core

  • 平均值: 761毫秒
  • 总时间: 38秒

.NET Framework

  • 平均值: 638毫秒
  • 总时间: 32秒

.NET Core不仅在CPU相关任务上较慢,而且在磁盘I/O任务上也较慢。

有任何想法为什么.NET Core在这个部分上稍微慢一些?是否有更改可以提高.NET Core的性能?


2
@series0ne 我想是的。根据 https://andrewlock.net/understanding-net-core-netstandard-and-asp-net-core/,.NET Core 似乎有一个额外的层,叫做运行时适配层。这可能会导致额外的开销。 - Jamie
尝试使用https://github.com/dotnet/BenchmarkDotNet获取更准确的结果。确保在Release配置下运行测试。 - Andrii Litvinov
8
比较苹果和橘子是一个容易犯的错误。在.NET Framework项目中,默认情况下运行32位代码,而.NET Core则喜欢64位代码。对于浮点数密集型代码来说,它们是非常不同的水果。取消"Prefer 32-bit"复选框,然后再试一次。 - Hans Passant
4
心灵调试又得分了。可以使用猜测来完成此问答。 - Hans Passant
4
值得注意的另一个要点是,桌面版x86 JIT与x64 JIT是不同的代码库。对于64位来说,.NET Framework和.NET Core现在都使用RyuJIT;对于32位,.NET Core仍然使用RyuJIT,但.NET Framework使用传统的JIT,因此你有不同的位数和不同的Jitter。 - Jeroen Mostert
显示剩余4条评论
2个回答

12

默认情况下,.NET Framework项目使用32位代码。此选项在项目的构建设置中可见,并默认选择。.NET Core项目默认为64位代码。如果取消“Prefer 32-bit”框中的选择,您会注意到.NET Framework的性能下降。

另一个需要注意的地方是,桌面x86 JIT与x64 JIT是单独的代码库。对于64位,.NET Framework和.NET Core现在都使用RyuJIT;对于32位,.NET Core仍然使用RyuJIT,但.NET Framework使用传统JIT,因此你有不同的位数和不同的JIT。

Hans Passant和Jeroen Mostert在评论中提供了答案。


10

这个问题应该在.NET Core 2.0.7和.NET Framework 4.7.2中得到修复,修复方法请见https://github.com/dotnet/coreclr/pull/15323

根本原因是JIT的公共子表达式消除(也称为CSE)优化中存在错误。有关详细信息,请参见问题(链接来自PR)。


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