Что лучше net framework или net core

.NET Core vs Framework. Производительность коллекций

Мне стало интересно, какой прирост производительности можно ожидать от Core в самых базовых классах, которые максимально часто используются в коде. Например, коллекции List, Array и Dictionary.

Если вам тоже интересно, как и почему изменилась производительность основных коллекций в Core 3 — прошу под кат!

Бенчмарки

Также я прогонял все тесты на двух дополнительных машинах (на Haswell и Sky Lake), чтобы убедиться, что результаты тестов стабильны и воспроизводятся на другом железе.

Цикл for

Method Runtime Size Mean Error StdDev Ratio
IterateFor_Int .NET 4.8 1000 565.09 ns 0.191 ns 0.127 ns 1.00
IterateFor_Int .NET Core 2.1 1000 451.14 ns 0.236 ns 0.156 ns 0.80
IterateFor_Int .NET Core 3.1 1000 451.08 ns 0.143 ns 0.085 ns 0.80
IterateFor_String .NET 4.8 1000 574.80 ns 6.795 ns 4.494 ns 1.00
IterateFor_String .NET Core 2.1 1000 460.86 ns 3.771 ns 2.494 ns 0.80
IterateFor_String .NET Core 3.1 1000 460.35 ns 0.681 ns 0.405 ns 0.80

В Core JIT генерирует более эффективный код, чтение элементов из List в цикле for стало быстрее на

Цикл foreach

Method Runtime Size Mean Error StdDev Ratio
IterateForEach_Int .NET 4.8 1000 1,574.5 ns 2.73 ns 1.81 ns 1.00
IterateForEach_Int .NET Core 2.1 1000 1,575.8 ns 3.82 ns 2.27 ns 1.00
IterateForEach_Int .NET Core 3.1 1000 1,568.1 ns 0.61 ns 0.40 ns 1.00
IterateForEach_String .NET 4.8 1000 8,046.3 ns 36.51 ns 24.15 ns 1.00
IterateForEach_String .NET Core 2.1 1000 6,465.0 ns 15.26 ns 10.09 ns 0.80
IterateForEach_String .NET Core 3.1 1000 5,886.3 ns 14.65 ns 9.69 ns 0.73

Итерирование List с ссылочными типами через foreach стало быстрее на 27%, но для значимых типов ничего не поменялось. Здесь можно оценить, насколько foreach медленнее, чем for. Разница в их эффективности на Core составляет 3.5x (value types) и 12x (reference types), примерно также как и в полном фреймворке.

Чтобы протестировать метод без ресайза внутреннего массива в тесте используется конструктор List с заданной ёмкостью (capacity).

Method Runtime Size Mean Error StdDev Ratio
Add_Int .NET 4.8 1000 2,006.5 ns 11.65 ns 6.93 ns 1.00
Add_Int .NET Core 2.1 1000 1,249.0 ns 1.00 ns 0.60 ns 0.62
Add_Int .NET Core 3.1 1000 1,260.9 ns 5.88 ns 3.89 ns 0.63
Add_String .NET 4.8 1000 3,250.8 ns 53.13 ns 35.14 ns 1.00
Add_String .NET Core 2.1 1000 2,816.8 ns 37.26 ns 22.18 ns 0.87
Add_String .NET Core 3.1 1000 2,538.2 ns 30.55 ns 20.21 ns 0.78

Contains

Давайте возьмём негативный сценарий для метода Contains: будем искать элементы, которых нет в коллекции.

Method Runtime Size Mean Error StdDev Ratio
Contains_Int .NET 4.8 1000 1,128.975 us 5.4951 us 3.6347 us 1.00
Contains_Int .NET Core 2.1 1000 456.040 us 0.1437 us 0.0950 us 0.40
Contains_Int .NET Core 3.1 1000 188.002 us 0.1619 us 0.0964 us 0.17
Contains_String .NET 4.8 1000 4,027.20 us 9.479 us 5.641 us 1.00
Contains_String .NET Core 2.1 1000 3,332.93 us 2.156 us 1.128 us 0.83
Contains_String .NET Core 3.1 1000 2,723.48 us 2.460 us 1.464 us 0.68

Кстати, раньше я всегда думал, что метод Contains внутри вызывает IndexOf, но оказалось, что это верно только для Core. В полном фреймворке это разные методы, и работают они с разной скоростью.

List Methods Summary

Сводная таблица относительной производительности (ratio) основных методов List при N = 1000.

List Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.82 0.47 Report
Ctor String 1.00 0.90 0.92 Report
IterateFor Int 1.00 0.80 0.80 Report
IterateFor String 1.00 0.80 0.80 Report
IterateForEach Int 1.00 1.00 1.00 Report
IterateForEach String 1.00 0.80 0.73 Report
Add Int 1.00 0.62 0.63 Report
Add String 1.00 0.87 0.78 Report
Contains Int 1.00 0.40 0.17 Report
Contains String 1.00 0.83 0.68 Report
IndexOf Int 1.00 0.99 0.43 Report
IndexOf String 1.00 0.95 0.95 Report

Array Methods Summary

Подробно останавливаться на методах массива я не буду, поскольку List — это обертка над массивом.
Так что здесь я приведу таблицу относительной производительности Array при N = 1000.

Array Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.73 0.88 Report
Ctor String 1.00 0.75 0.84 Report
IterateFor Int 1.00 0.86 1.00 Report
IterateFor String 1.00 1.00 1.00 Report
IterateForEach Int 1.00 0.84 1.00 Report
IterateForEach String 1.00 1.00 1.00 Report

Здесь можно отметить, что как и прежде, цикл foreach для массива преобразуется в обычный for. Т.е. с точки зрения производительности для итерации массива нет разницы какой из циклов использовать.

Dictionary

Randomized Hash

Method Runtime Size Mean Error StdDev Ratio
Add_IntKey .NET 4.8 1000 10.449 us 0.0690 us 0.0456 us 1.00
Add_IntKey .NET Core 2.1 1000 12.270 us 0.0492 us 0.0325 us 1.17
Add_IntKey .NET Core 3.1 1000 11.355 us 0.0723 us 0.0478 us 1.09
Add_StringKey .NET 4.8 1000 33.229 us 0.0331 us 0.0219 us 1.00
Add_StringKey .NET Core 2.1 1000 35.303 us 0.1821 us 0.1084 us 1.06
Add_StringKey .NET Core 3.1 1000 26.976 us 0.1248 us 0.0825 us 0.81

Добавление в Dictionary с ключом String стало быстрее на 19%. В случае с Int ключом результат (ratio) зависит от размера: на 100 — 0.95, на 1’000 — 1.09, на 10’000 — 0.93. Отклонения небольшие, возможно, это просто «шум». На других машинах отклонения ещё меньше. Будем считать, что с ключом типа Int добавление элемента происходит примерно с той же скоростью.

GetValue

Method Runtime Size Mean Error StdDev Ratio
GetValue_IntKey .NET 4.8 1000 10.916 us 0.019 us 0.013 us 1.00
GetValue_IntKey .NET Core 2.1 1000 10.985 us 0.135 us 0.089 us 1.01
GetValue_IntKey .NET Core 3.1 1000 9.424 us 0.086 us 0.056 us 0.86
GetValue_StringKey .NET 4.8 1000 31.622 us 0.294 us 0.175 us 1.00
GetValue_StringKey .NET Core 2.1 1000 31.787 us 0.090 us 0.047 us 1.00
GetValue_StringKey .NET Core 3.1 1000 23.572 us 0.098 us 0.058 us 0.75

Получение элемента по строковому ключу стало быстрее на 25%, по Int ключу — на 14%. Однако, здесь есть зависимость от размера Dictionary. Чем меньше размер — тем больше Framework отстает от Core 3 и наоборот. На маленьких размерах Core 3 работает в 1.5 раза быстрей. При достижении размера в 10’000 производительность Core 3 падает до уровня Framework и даже чуть ниже (см. отчеты ниже).

В коде класса Dictionary слишком много изменений, чтобы однозначно сказать, какие из них больше всего повлияли на производительность.

Dictionary Methods Summary

Сводная таблица относительной производительности основных методов Dictionary при N = 1000.

Dictionary Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.95 0.62 Report
Ctor String 1.00 4.06 3.84 Report
Add Int 1.00 1.17 1.09 Report
Add String 1.00 1.06 0.81 Report
GetValue Int 1.00 1.01 0.86 Report
GetValue String 1.00 1.00 0.75 Report
ContainsKey Int 1.00 0.84 0.78 Report
ContainsKey String 1.00 0.99 0.73 Report
ContainsValue Int 1.00 0.54 0.54 Report
ContainsValue String 1.00 0.86 0.90 Report

Результаты

Как и ожидалось, почти все рассмотренные методы на Core 3 работают быстрее. Разница зачастую составляет 20-30%, а то и больше. Для таких базовых коллекций это отличный результат.

Код и детальные результаты всех тестов доступны на GitHub.

Источник

.NET Core vs Framework. Производительность коллекций

Мне стало интересно, какой прирост производительности можно ожидать от Core в самых базовых классах, которые максимально часто используются в коде. Например, коллекции List, Array и Dictionary.

Если вам тоже интересно, как и почему изменилась производительность основных коллекций в Core 3 — прошу под кат!

Бенчмарки

Также я прогонял все тесты на двух дополнительных машинах (на Haswell и Sky Lake), чтобы убедиться, что результаты тестов стабильны и воспроизводятся на другом железе.

Цикл for

Method Runtime Size Mean Error StdDev Ratio
IterateFor_Int .NET 4.8 1000 565.09 ns 0.191 ns 0.127 ns 1.00
IterateFor_Int .NET Core 2.1 1000 451.14 ns 0.236 ns 0.156 ns 0.80
IterateFor_Int .NET Core 3.1 1000 451.08 ns 0.143 ns 0.085 ns 0.80
IterateFor_String .NET 4.8 1000 574.80 ns 6.795 ns 4.494 ns 1.00
IterateFor_String .NET Core 2.1 1000 460.86 ns 3.771 ns 2.494 ns 0.80
IterateFor_String .NET Core 3.1 1000 460.35 ns 0.681 ns 0.405 ns 0.80

В Core JIT генерирует более эффективный код, чтение элементов из List в цикле for стало быстрее на

Цикл foreach

Method Runtime Size Mean Error StdDev Ratio
IterateForEach_Int .NET 4.8 1000 1,574.5 ns 2.73 ns 1.81 ns 1.00
IterateForEach_Int .NET Core 2.1 1000 1,575.8 ns 3.82 ns 2.27 ns 1.00
IterateForEach_Int .NET Core 3.1 1000 1,568.1 ns 0.61 ns 0.40 ns 1.00
IterateForEach_String .NET 4.8 1000 8,046.3 ns 36.51 ns 24.15 ns 1.00
IterateForEach_String .NET Core 2.1 1000 6,465.0 ns 15.26 ns 10.09 ns 0.80
IterateForEach_String .NET Core 3.1 1000 5,886.3 ns 14.65 ns 9.69 ns 0.73

Итерирование List с ссылочными типами через foreach стало быстрее на 27%, но для значимых типов ничего не поменялось. Здесь можно оценить, насколько foreach медленнее, чем for. Разница в их эффективности на Core составляет 3.5x (value types) и 12x (reference types), примерно также как и в полном фреймворке.

Чтобы протестировать метод без ресайза внутреннего массива в тесте используется конструктор List с заданной ёмкостью (capacity).

Method Runtime Size Mean Error StdDev Ratio
Add_Int .NET 4.8 1000 2,006.5 ns 11.65 ns 6.93 ns 1.00
Add_Int .NET Core 2.1 1000 1,249.0 ns 1.00 ns 0.60 ns 0.62
Add_Int .NET Core 3.1 1000 1,260.9 ns 5.88 ns 3.89 ns 0.63
Add_String .NET 4.8 1000 3,250.8 ns 53.13 ns 35.14 ns 1.00
Add_String .NET Core 2.1 1000 2,816.8 ns 37.26 ns 22.18 ns 0.87
Add_String .NET Core 3.1 1000 2,538.2 ns 30.55 ns 20.21 ns 0.78

Contains

Давайте возьмём негативный сценарий для метода Contains: будем искать элементы, которых нет в коллекции.

Method Runtime Size Mean Error StdDev Ratio
Contains_Int .NET 4.8 1000 1,128.975 us 5.4951 us 3.6347 us 1.00
Contains_Int .NET Core 2.1 1000 456.040 us 0.1437 us 0.0950 us 0.40
Contains_Int .NET Core 3.1 1000 188.002 us 0.1619 us 0.0964 us 0.17
Contains_String .NET 4.8 1000 4,027.20 us 9.479 us 5.641 us 1.00
Contains_String .NET Core 2.1 1000 3,332.93 us 2.156 us 1.128 us 0.83
Contains_String .NET Core 3.1 1000 2,723.48 us 2.460 us 1.464 us 0.68

Кстати, раньше я всегда думал, что метод Contains внутри вызывает IndexOf, но оказалось, что это верно только для Core. В полном фреймворке это разные методы, и работают они с разной скоростью.

List Methods Summary

Сводная таблица относительной производительности (ratio) основных методов List при N = 1000.

List Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.82 0.47 Report
Ctor String 1.00 0.90 0.92 Report
IterateFor Int 1.00 0.80 0.80 Report
IterateFor String 1.00 0.80 0.80 Report
IterateForEach Int 1.00 1.00 1.00 Report
IterateForEach String 1.00 0.80 0.73 Report
Add Int 1.00 0.62 0.63 Report
Add String 1.00 0.87 0.78 Report
Contains Int 1.00 0.40 0.17 Report
Contains String 1.00 0.83 0.68 Report
IndexOf Int 1.00 0.99 0.43 Report
IndexOf String 1.00 0.95 0.95 Report

Array Methods Summary

Подробно останавливаться на методах массива я не буду, поскольку List — это обертка над массивом.
Так что здесь я приведу таблицу относительной производительности Array при N = 1000.

Array Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.73 0.88 Report
Ctor String 1.00 0.75 0.84 Report
IterateFor Int 1.00 0.86 1.00 Report
IterateFor String 1.00 1.00 1.00 Report
IterateForEach Int 1.00 0.84 1.00 Report
IterateForEach String 1.00 1.00 1.00 Report

Здесь можно отметить, что как и прежде, цикл foreach для массива преобразуется в обычный for. Т.е. с точки зрения производительности для итерации массива нет разницы какой из циклов использовать.

Dictionary

Randomized Hash

Method Runtime Size Mean Error StdDev Ratio
Add_IntKey .NET 4.8 1000 10.449 us 0.0690 us 0.0456 us 1.00
Add_IntKey .NET Core 2.1 1000 12.270 us 0.0492 us 0.0325 us 1.17
Add_IntKey .NET Core 3.1 1000 11.355 us 0.0723 us 0.0478 us 1.09
Add_StringKey .NET 4.8 1000 33.229 us 0.0331 us 0.0219 us 1.00
Add_StringKey .NET Core 2.1 1000 35.303 us 0.1821 us 0.1084 us 1.06
Add_StringKey .NET Core 3.1 1000 26.976 us 0.1248 us 0.0825 us 0.81

Добавление в Dictionary с ключом String стало быстрее на 19%. В случае с Int ключом результат (ratio) зависит от размера: на 100 — 0.95, на 1’000 — 1.09, на 10’000 — 0.93. Отклонения небольшие, возможно, это просто «шум». На других машинах отклонения ещё меньше. Будем считать, что с ключом типа Int добавление элемента происходит примерно с той же скоростью.

GetValue

Method Runtime Size Mean Error StdDev Ratio
GetValue_IntKey .NET 4.8 1000 10.916 us 0.019 us 0.013 us 1.00
GetValue_IntKey .NET Core 2.1 1000 10.985 us 0.135 us 0.089 us 1.01
GetValue_IntKey .NET Core 3.1 1000 9.424 us 0.086 us 0.056 us 0.86
GetValue_StringKey .NET 4.8 1000 31.622 us 0.294 us 0.175 us 1.00
GetValue_StringKey .NET Core 2.1 1000 31.787 us 0.090 us 0.047 us 1.00
GetValue_StringKey .NET Core 3.1 1000 23.572 us 0.098 us 0.058 us 0.75

Получение элемента по строковому ключу стало быстрее на 25%, по Int ключу — на 14%. Однако, здесь есть зависимость от размера Dictionary. Чем меньше размер — тем больше Framework отстает от Core 3 и наоборот. На маленьких размерах Core 3 работает в 1.5 раза быстрей. При достижении размера в 10’000 производительность Core 3 падает до уровня Framework и даже чуть ниже (см. отчеты ниже).

В коде класса Dictionary слишком много изменений, чтобы однозначно сказать, какие из них больше всего повлияли на производительность.

Dictionary Methods Summary

Сводная таблица относительной производительности основных методов Dictionary при N = 1000.

Dictionary Method Type .NET 4.8 Core 2.1 Core 3.1 Details
Ctor Int 1.00 0.95 0.62 Report
Ctor String 1.00 4.06 3.84 Report
Add Int 1.00 1.17 1.09 Report
Add String 1.00 1.06 0.81 Report
GetValue Int 1.00 1.01 0.86 Report
GetValue String 1.00 1.00 0.75 Report
ContainsKey Int 1.00 0.84 0.78 Report
ContainsKey String 1.00 0.99 0.73 Report
ContainsValue Int 1.00 0.54 0.54 Report
ContainsValue String 1.00 0.86 0.90 Report

Результаты

Как и ожидалось, почти все рассмотренные методы на Core 3 работают быстрее. Разница зачастую составляет 20-30%, а то и больше. Для таких базовых коллекций это отличный результат.

Код и детальные результаты всех тестов доступны на GitHub.

На сегодня Core практически догнал Framework по возможностям, а по производительности давно оставил его позади. Что касается ASP.NET Core — к третьей версии он вышел в топ самых производительных веб-фреймворков (топ-5 по последним тестам TechEmpower).

Материалы по теме

Источник

Читайте также:  чем можно заняться в постели
Библиотека с советами