Почему.NET Core 2.0 работает хуже, чем.NET Framework 4.6.1

Я написал программу, которая создает 4 потока, каждый из которых сортирует 20 000 чисел от низкого до высокого в 50 раз. Я запускал этот тест несколько раз на.NET Core 2.0 и.NET Framework 4.6.1. В этом тесте.NET Framework всегда выигрывает у.NET Core.

Настроить

  • .NET Core в режиме выпуска и опубликовано
  • Windows 10, ядро i7 duo, 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 не работает медленнее только на задачах, связанных с процессором. Это также медленнее на задачах дискового ввода-вывода.

Есть идеи, почему.NET Core немного медленнее в этой части? Могу ли я внести изменения, чтобы улучшить производительность.NET Core?

Ответ 1

По умолчанию.NET Framework создает 32-разрядный код. Эта опция видна в настройках сборки проекта и выбрана по умолчанию. Для проектов.NET Core по умолчанию используется 64-разрядный код. Если вы отключите поле "Предпочитаете 32-разрядные", вы заметите, что производительность.NET Framework падает.

Еще одно замечание: рабочий стол x86 JIT представляет собой отдельную базу кода из x64 JIT. Для 64-битной платформы.NET Framework и.NET Core теперь используют RyuJIT; для 32-разрядного.NET Core по-прежнему используется RyuJIT, но.NET Framework использует устаревший JIT, так что у вас есть как разная битность, так и другой джиттер.

Ответы были представлены в комментариях Ханса Пассанта и Йеруэна Морерта.

Ответ 2

Это должно быть исправлено в.Net Core 2.0.7 и.Net Framework 4.7.2 через https://github.com/dotnet/coreclr/pull/15323.

Основной причиной была ошибка в оптимизации JIT Common Subexpression Elmination (aka CSE). Смотрите вопрос (связан с PR) для кровавых подробностей.