Привет, %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 этапа:


  1. Измерение RPS API сервера. Измерения проводились для того, чтобы была возможность оценить влияние методов обработки на производительность каждого из методов.
  2. Измерение RPS обработки ответов с использованием рефлексии.
  3. Измерение 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.


Ну, а теперь пора переходить к самому интересному — результатам.


Результаты


Ниже представлена таблица с результатами тестирования.


alt text


Сразу можно заметить, что Golang обладает более производительным веб-сервером. Разница составляет около 12% по сравнению с Kestrel у .NET Core.
На основании данных выше были построена 2 графика. Далее можно наглядно увидеть сравнение RPS.


alt text


За счет более быстрой библиотеки net/http Golang показывает не плохие результаты для данных небольшого размера. При увеличении объема данных производительность сравнивается с kestrel.


При использовании рефлексии на небольшом размере данных RPS примерно одинаков с учетом погрешности измерений. С ростом размера данных .NET Core показывает больший RPS.


При тестировании без использования рефлексии оба языка показали прирост производительности. Golang показывает лучшую производительность, так как имеет изначально более высокий RPS(requests per second) на тестах без обработки. На данных небольшого размера преимущество существенное. С ростом размера данных RPS почти сравнивается. На самом большом тесте в 500 записей Golang снова вырывается вперед.


alt text


В тестах с использованием рефлексии Golang проиграл по всем фронтам. Падение производительности в худших сценариях составляло более 60%. Реализация сериализации из коробки по производительности вообще никуда не годится.
Без использования рефлексии Golang оказался быстрее во всех тестах. И с ростом данных преимущество Golang только растет. В любом случае, отказ от использования рефлексии дает существенный прирост производительности как для Golang, так и для .NETCore, чего, в общем-то, и следовало ожидать.


Выводы


Какие можно сделать выводы из этого небольшого сравнения производительности? Хотелось бы сформулировать это в виде плюсов и минусов для каждого из решений. Начнем с Golang:


  • Имеет более высокую производительность, и ее можно еще улучшить, например, применив в качестве веб-сервера fasthttp.
  • Благодаря кодогенерации — удобное использование методов обработки, не используя рефлексию.
  • Меньшее потребление памяти.

.NET Core так же имеет ряд преимуществ:


  • Производительность подойдет для большинства случаев.
  • На мой взгляд, это одна из лучших и удобных сред разработки Visual Studio.

Итог можно подвести такой: если у вас REST API и планируется большая нагрузка, не слишком сложная бизнес логика, лучше использовать Golang, в других случаях можно обойтись и .NET Core. Стоит ли переписывать готовые решения с .NET Core на Golang? Каждый решит для себя сам.


Надеюсь, вам будет полезен данный материал. Всем добра

Комментарии (12)


  1. kekekeks
    29.09.2019 22:33
    +4

    Для сравнения сериализации JSON в Go и .NET в контексте веб-приложений вам сюда. Повторяемая методика тестирования и возможность любому желающему взять и улучшить решение на конкретном языке/технологии вызывают больше доверия к данным бенчмаркам за счёт исключения факторов ангажированности и/или кривых рук бенчмаркающего.


    Собственно разница выходит не "12%", а меньше 2% (1,306,423 vs 1,286,433)


    1. unsafePtr
      29.09.2019 23:13
      +1

      Я почему то более чем уверен что .net побьет go если тесты techempower запустить на винде.


    1. PsyHaSTe
      30.09.2019 15:24
      -1

      Автор использует стандартный Json.Net, а пример из бенчмарка какую-то странную библиотеку: https://github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/CSharp/beetlex/PlatformBenchmarks/json.cs


      Но да, надо бы переписать на неткор 3.0 и справнить.


      1. atd
        30.09.2019 15:52

        Файл по вашей ссылке на 18-м месте с 708,034.
        А топовый из бенчмарка это github.com/TechEmpower/FrameworkBenchmarks/blob/master/frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.Json.cs


  1. 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


    1. Dedrus
      30.09.2019 12:49

      Я вообще был настроен в статье увидеть .net core 2.2 и .net core 3.0, т.к. новый сериализатор — одно из ключевых изменений нового фреймворка.


  1. NIKOSV
    30.09.2019 01:55

    Разница 10-15%, то есть для реальных приложений разницы практически нет и все будет упираться в кривизну рук разработчиков, то есть при выборе языка/платформы на эту характеристику (а именно скорость работы в этой конкретной задачи) можно не смотреть.


    1. JekaMas
      30.09.2019 04:50

      Мне вот тоже всё больше кажется, что не сильно эта мифическая величина "производительность" и важна. Важнее наличие рабочей команды под стэк проекта. А докинуть 1-2 сервера — это недорого.


  1. 23derevo
    30.09.2019 09:32
    -2

    А во что упёрлись бенчмарки и там и там? Стектрейсы есть? Дампы памяти есть? Данные по кэшам есть? Короче, за счёт чего одно решение быстрее другого? Анализ не сделан, статья бессмысленна.


    1. jetcar
      30.09.2019 10:59

      не понятно почему заминусили, вероятно раз результат совпадает с ожидаемым то нефиг смотреть почему, а то вдруг выяснится что тестировали криво и немного изменив условия всё поменяется


  1. corax
    30.09.2019 10:39

    непонятно есть ли смысл, но можно ещё немного ускорить замени easyjson на
    github.com/francoispqt/gojay


    1. bat
      02.10.2019 10:54

      Наверное, нет.
      В первом комментарии дали ссылку на бенчмарки по json сериализации. Топовое решение на Go c easyjson, ближайшее решении с gojay проигрывает ему 1.4%.