[recovery mode] Сериализация и десериализация данных .NET Core vs Go
Привет, %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? Каждый решит для себя сам.
Надеюсь, вам будет полезен данный материал. Всем добра