Привет, %username%
Передо мной была поставлена задача сравнить производительность при сериализации для .NET Core и Golang. Поискав в интернете, наткнулся на репозиторий. Рассматривается простой пример REST микросервиса. Это именно то, что нужно, подумал я. Посмотрев результаты тестирования, я был удивлен. Посмотрев исходный код, я понял, что не так. Вот что мне не понравилось:
- Для сериализации и десериализации выбран массив из 3-х элементов. Этого явно недостаточно.
- Для Golang не используются все возможности языка, а, как известно, встроенная библиотека encoding/json работает медленно.
- В итоге автор сравнивает производительность веб-серверов kestrel и net/http.
Именно эти недочеты стали причиной более подробного рассмотрения производительности в рамках примера, описанного выше. Надеюсь, вам будет интересно узнать результаты.
Состав и описание ПО
За основу был взят и исходный код из репозитория выше. Что было доработано:
- Для API сервера использован fasthttp.
- API сервер отвечает массивами записей.
- В каждом клиенте реализовано несколько методов для проверки.
Доработанный код доступен в репозитории.
Для наглядности пример JSON ответа API сервера:
[
{
"Id":"id_8299119732867115081",
"Name":"name_5541535679032008745",
"Time":1566731141
},
...
{
"Id":"id_2804604318195309547",
"Name":"name_5914011395631118540",
"Time":1566731142
}
]
Клиенты
Для оценки производительности в каждом сервисе реализовано три метода:
- получение данных от API сервера и отправка их без обработки [/testNoProcess].
- получение данных от API сервера -десериализация, сериализация с помощью рефлексии и отправка [/testReflection]. Для .NETCore использовался пакет Newtonsoft.Json, для Golang — encoding/json.
- получение данных от API сервера — десериализация, сериализация без использования рефлексии и отправка [/testNoReflection]. Для .NETCore было реализовано решение на основе Span, чтобы минимизировать количество аллокаций памяти. У Golang имеется готовое решение — библиотека easyjson, зарекомендовавшая себя исключительно с положительной стороны.
На основании этих тестов можно оценить относительную производительность веб-серверов (kestrel и net/http), падение производительности при обработке данных с использованием рефлексии и без нее для реализаций на обоих языках.
Описание методики тестирования
Тестирование проводилось в несколько этапов для того, чтобы можно было оценить производительность каждого языка и каждой реализации.
Для создания нагрузки была выбрана утилита bombardier. Утилита запускалась со следующими параметрами: -c 125 –d 120s, что можно интерпретировать следующим образом: как использовать 125 потоков со временем тестирования 120 секунд.
Измерение производительности производилось в 3 этапа:
- Измерение RPS API сервера. Измерения проводились для того, чтобы была возможность оценить влияние методов обработки на производительность каждого из методов.
- Измерение RPS обработки ответов с использованием рефлексии.
- Измерение RPS обработки ответов без использования рефлексии.
На основании этих измерений, были получены данные о производительности при обработке ответов. Утилизация всех ядер процессора была 99,8-100%. Для оценки были выбраны исходные данные размером 10, 30, 100 и 500 записей. Массивы размером в 500 записей в продакшене встречаются не часто, но мне было интересно посмотреть, как поведет себя каждый из языков.
Тестовый стенд
Все тесты запускались на виртуальной машине, под управлением Ubuntu Server 18.04 со всем обновлениями на август 2019 года. Она имеет следующие характеристики:
- Процессор Core I7-3770K – 4 ядра.
- Оперативная память – 4 Гб.
Для сравнения производительности были установлены .NET Core 2.2 и Golang 1.12.
Ну, а теперь пора переходить к самому интересному — результатам.
Результаты
Ниже представлена таблица с результатами тестирования.
Сразу можно заметить, что Golang обладает более производительным веб-сервером. Разница составляет около 12% по сравнению с Kestrel у .NET Core.
На основании данных выше были построена 2 графика. Далее можно наглядно увидеть сравнение RPS.
За счет более быстрой библиотеки net/http Golang показывает не плохие результаты для данных небольшого размера. При увеличении объема данных производительность сравнивается с kestrel.
При использовании рефлексии на небольшом размере данных RPS примерно одинаков с учетом погрешности измерений. С ростом размера данных .NET Core показывает больший RPS.
При тестировании без использования рефлексии оба языка показали прирост производительности. Golang показывает лучшую производительность, так как имеет изначально более высокий RPS(requests per second) на тестах без обработки. На данных небольшого размера преимущество существенное. С ростом размера данных RPS почти сравнивается. На самом большом тесте в 500 записей Golang снова вырывается вперед.
В тестах с использованием рефлексии Golang проиграл по всем фронтам. Падение производительности в худших сценариях составляло более 60%. Реализация сериализации из коробки по производительности вообще никуда не годится.
Без использования рефлексии Golang оказался быстрее во всех тестах. И с ростом данных преимущество Golang только растет. В любом случае, отказ от использования рефлексии дает существенный прирост производительности как для Golang, так и для .NETCore, чего, в общем-то, и следовало ожидать.
Выводы
Какие можно сделать выводы из этого небольшого сравнения производительности? Хотелось бы сформулировать это в виде плюсов и минусов для каждого из решений. Начнем с Golang:
- Имеет более высокую производительность, и ее можно еще улучшить, например, применив в качестве веб-сервера fasthttp.
- Благодаря кодогенерации — удобное использование методов обработки, не используя рефлексию.
- Меньшее потребление памяти.
.NET Core так же имеет ряд преимуществ:
- Производительность подойдет для большинства случаев.
- На мой взгляд, это одна из лучших и удобных сред разработки Visual Studio.
Итог можно подвести такой: если у вас REST API и планируется большая нагрузка, не слишком сложная бизнес логика, лучше использовать Golang, в других случаях можно обойтись и .NET Core. Стоит ли переписывать готовые решения с .NET Core на Golang? Каждый решит для себя сам.
Надеюсь, вам будет полезен данный материал. Всем добра
Комментарии (12)
ApeCoder
29.09.2019 23:40+3Интересно было бы сравнить с .NET Core 3.0 и System.Text.JSON. По ссылкам обещают ускорение:
Description RPS CPU (%) Memory (MB) Newtonsoft.Json – 500 B 136,435 95 172 System.Text.Json – 500 B 167,861 94 169 Newtonsoft.Json – 2.4 KB 97,137 97 174 System.Text.Json – 2.4 KB 132,026 96 169 Newtonsoft.Json – 40 KB 7,712 88 212 System.Text.Json – 40 KB 16,625 96 193
Dedrus
30.09.2019 12:49Я вообще был настроен в статье увидеть .net core 2.2 и .net core 3.0, т.к. новый сериализатор — одно из ключевых изменений нового фреймворка.
NIKOSV
30.09.2019 01:55Разница 10-15%, то есть для реальных приложений разницы практически нет и все будет упираться в кривизну рук разработчиков, то есть при выборе языка/платформы на эту характеристику (а именно скорость работы в этой конкретной задачи) можно не смотреть.
JekaMas
30.09.2019 04:50Мне вот тоже всё больше кажется, что не сильно эта мифическая величина "производительность" и важна. Важнее наличие рабочей команды под стэк проекта. А докинуть 1-2 сервера — это недорого.
23derevo
30.09.2019 09:32-2А во что упёрлись бенчмарки и там и там? Стектрейсы есть? Дампы памяти есть? Данные по кэшам есть? Короче, за счёт чего одно решение быстрее другого? Анализ не сделан, статья бессмысленна.
jetcar
30.09.2019 10:59не понятно почему заминусили, вероятно раз результат совпадает с ожидаемым то нефиг смотреть почему, а то вдруг выяснится что тестировали криво и немного изменив условия всё поменяется
corax
30.09.2019 10:39непонятно есть ли смысл, но можно ещё немного ускорить замени easyjson на
github.com/francoispqt/gojaybat
02.10.2019 10:54Наверное, нет.
В первом комментарии дали ссылку на бенчмарки по json сериализации. Топовое решение на Go c easyjson, ближайшее решении с gojay проигрывает ему 1.4%.
kekekeks
Для сравнения сериализации JSON в Go и .NET в контексте веб-приложений вам сюда. Повторяемая методика тестирования и возможность любому желающему взять и улучшить решение на конкретном языке/технологии вызывают больше доверия к данным бенчмаркам за счёт исключения факторов ангажированности и/или кривых рук бенчмаркающего.
Собственно разница выходит не "12%", а меньше 2% (1,306,423 vs 1,286,433)
unsafePtr
Я почему то более чем уверен что .net побьет go если тесты techempower запустить на винде.
PsyHaSTe
Автор использует стандартный Json.Net, а пример из бенчмарка какую-то странную библиотеку: https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/CSharp/beetlex/PlatformBenchmarks/json.cs
Но да, надо бы переписать на неткор 3.0 и справнить.
atd
Файл по вашей ссылке на 18-м месте с 708,034.
А топовый из бенчмарка это github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Json.cs