Сравнение производительности JSON-сериализаторов для .NET

Json в .NET может использоваться для разных целей. В моём случае это формирование ответа на Ajax-запрос в ASP.NET Mvc приложении. Конечно, конвертация ответа в JSON — не самое тонкое место, но мне стало интересно, как можно ускорить эту операцию. Настоящая статья не является детальным обзором существующих JSON-сериализаторов для .NET/Mono. Меня интересовало в первую очередь время, затрачиваемое на сериализацию относительно простых структур данных, и во вторую очередь маппинг. То есть, хочется чтобы сериализация легко и гибко программировалась и быстро работала.

В исследование попали следующие средства сериализации:
  1. Простая конкатенация строк
  2. JavaScriptSerializer (.NET Framework)
  3. DataContractJsonSerializer (.NET Framework 3.5)
  4. Newton Json.net (json.codeplex.com, james.newtonking.com/pages/json-net.aspx)
  5. JsonEx (code.google.com/p/jsonexserializer)
  6. Fluent Json (fluentjson.codeplex.com, code.google.com/p/fluent-json)


Для замера производительности проводилось два типа тестов:
Тест №1. 10 000 раз выполнялась инициализация сериализатора и сериализация одного и того же объекта.

Тест №2. 1 раз создавался и инициализировался сериализатор. 10 000 раз выполнялась сериализация. Тест был придуман с надеждой на то, что «умный» сериализатор, получив заранее информацию о типе объекта, будет выполнять преобразование быстрее. В некоторых случаях, предварительная инициализация невозможна, далее результаты таких тестов буду отмечать прочерком.

Среда тестирования:
Среди испытуемых есть два класса, которые входят в платформу, а это значит, что в их работе в Mono и .NET могут быть различия. В качестве основной и единственной ОС у меня Ubuntu, поэтому для тестирования .NET я воспользовался виртуальной машиной VirtualBox. А если быть точным:
  • Конфигурация «mono»: Intel Atom 330, 4Gb + Ubuntu 10.10, MonoDevelop 2.6, Mono 2.10.5
  • Конфигурация «.NET»: VirtualBox, 1.5GB, Windows XP, VS2010, .NET Framework 3.5

Конкатенация строк


Первым делом я приведу результаты тестирования сериализации путём простого ToString() и конкатенации. В «продакшн» такой способ может быть оправдан лишь в редких случаях. Я же его рассматриваю для того, чтобы использовать в качестве эталона. Сложно придумать способ получающий строку в формате JSON быстрее.
Результаты тестов ниже:
Тест №1 Тест №2
.NET 0,25 сек -
Mono 0,6 сек -

Проведение теста №2, по понятным причинам, невозможно.

JavaScriptSerializer


JavaScriptSerializer находится в пространстве имён System.Web.Script.Serialization платформ .NET/Mono. Пример кода:
var serializer = new JavaScriptSerializer();//Инициализация
string jsonResult = serializer.Serialize(Program.bookA);//Сериализация


Заявлена возможность переопределить способы сериализации для различных типов данных при помощи классов JavaScriptConverter, RegisterConverters. Это может пригодиться, например, для преобразования DateTime или enum-ов. Тесты проводились без использования этой возможности.

Результаты тестов:
Тест №1 Тест №2
.NET 4 сек 3,5 сек
Mono 5 сек 5 сек


DataContractJsonSerializer


Ещё один сериализатор доступный «из коробки» находится в пространстве имён System.Runtime.Serialization.Json. В использовании почти не отличается от предыдущего, за исключением того, что сериализация происходит в stream, а не в строку. Пример:
var serializer = new DataContractJsonSerializer(typeof(Book));
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, Program.bookA);
string jsonResult = Encoding.Default.GetString(ms.GetBuffer());


Есть различия использования этого инструмента в .NET и Mono. В .NET необходимо размечать сериализуемые классы и свойства атрибутами [DataContract] и [DataMember] соответственно. В Mono эти атрибуты не обязательны.
Результаты тестов показывают, что разница не только в атрибутах:
Тест №1 Тест №2
.NET 1,5 сек 1,5 сек
Mono 34 сек 34 сек

Забегая вперёд, скажу 1.5 сек — это лучшее время среди всех испытуемых, 34 сек — худшее.

Fluent Json


Достоинство (а кому и недостаток) этого сериализатора в том, что он предоставляет гибкий способ маппинга свойств. Нет необходимости вводить атрибуты в слой бизнес-логики, что при работе со сторонними библиотеками проблематично. Не понадобятся промежуточные классы, чтобы преобразовать структуры данных слоя бизнес-логики в структуры данных клиент-серверного обмена. Ещё одной из заявленных «фишек», является возможность многопоточной обработки больших наборов данных, что даёт высокую производительность. Не мой случай.

Пример кода ниже показывает, как это работает:

var serializer = Json.EnсoderFor<Book>(config => config
                .MapType<Book>(map => map
                    .AllFields()
                    .Field<DateTime>(field => field.PubDate, pubDate => pubDate
                        .EncodeAs<string>(value => value.ToShortDateString()))
                    .Field<BookType>(field => field.Type, type => type
                        .EncodeAs<int>(value => (int)value)
                        .To("book_type")))
                .MapType<Author>(map => map.AllFields())
				.UseTidy(true) );
string jsonResult = serializer.Enсode(Program.bookA);


Если сериализуемый тип данных содержит свойства типа перечисления или DateTime, как в примере выше, то указывать преобразование для них обязательно. Самостоятельно Fluent Json не догадается, что с ними делать.

Результаты тестов:
Тест №1 Тест №2
.NET 52,5 сек 9 сек
Mono 34 сек 10 сек

JsonExSerializer


По заверениям разработчика, этот инструмент предназначен для точной сериализации/десериализации объектов, которую не дают другие библиотеки. Для меня же это не самое главное, тестировать буду на скорость. Пример кода:
var serializer = new Serializer(typeof(Book));
var memoryStream = new MemoryStream();
serializer.Serialize(Program.bookA,	memoryStream);
var jsonResult = Encoding.Default.GetString(memoryStream.GetBuffer());


Пикантной особенностью JsonEx, является то, что в результат он добавляет форматирование и комментарий вида:
/*
  Created by JsonExSerializer
  Assembly: JsonTestConsole, Version=1.0.4347.32516, Culture=neutral, PublicKeyToken=null
  Type: JsonTestConsole.Book
*/

Форматирование может быть полезно при сохранении, например, настроек в файл, который в дальнейшем может редактироваться в текстовом редакторе.

Результаты тестов:
Тест №1 Тест №2
.NET 32 сек 8 сек
Mono 34 сек 10 сек

Newton JSON.NET


В описании возможностей есть интригующая строчка «High performance, faster than .NET's built-in JSON serializers». Пример кода использующего Newton укладывается в одну строку:
string jsonResult = JsonConvert.SerializeObject(Program.bookA);


Какой либо предварительной инициализации не требует, при этом показывает очень хорошее время:
Тест №1 Тест №2
.NET 1.5 сек -
Mono 2 сек -


Заключение


Результаты тестирования отсортированные по возрастанию времени (в миллисекундах) для обоих конфигураций (столбец K показывает во сколько раз тест медленнее «конкатенации строк»):

Платформа Mono .Net Framework
Сериализатор Тест №1 Тест №2 K Тест №1 Тест №2 K
String concatination 600 - 1 250 - 1
Newton JSON.NET 2 000 - 3,4 1 500 - 6
JavaScriptSerializer 5 000 4 000 8,3/6,7 1700 1700 6,8
JsonExSerializer 34 000 10 000 56,7/16,7 4 000 3 500 16/14
Fluent Json 34 000 10 000 56,7/16,7 32 000 8 000 128/32
DataContractJsonSerializer 34 000 34 000 56,7/56,7 1 500 1 500 6

Из таблицы видно, что если есть планы гонять приложение под Mono или платформа не определена, то лучше обратиться к сторонней библиотеке Newton JSON.net — она показывает хорошие результаты как в Win+.NET так и Linux+Mono. Если целевая платформа .NET и обязательность атрибутов не смущает, достаточно встроенного DataContractJsonSerializer. Если оба варианта не устраивают, можно изобрести свою библиотеку — стремиться есть куда.

Код тестов здесь: code.google.com/p/research-net-json/source/browse/Main.cs. Сторонние библиотеки придётся выкачать самостоятельно, ещё раз ссылки:


UPD. Дополняю тестами ещё нескольких сериализаторов (спасибо atd,skyboy,Guderian и kekekeks):
Платформа Mono .Net Framework
Сериализатор Тест №1 Тест №2 K Тест №1 Тест №2 K
new StringBuilder(400) 550 - 0,9 270 - 1
ServiceStack 1 300 - 2,2 1 200 - 4,8
Fast Json 2 600 - 4,3 1 200 - 4,8
Jayrock 8 200 - 13,7 9 200 - 36,8

Как видно, появился новый лидер — ServiceStack.

P.S. Если Вы заметили ошибку, или Вам известны другие библиотеки, пишите, с радостью дополню статью.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 26

    +2
    Спасибо! Ещё можно вот эту штуку посмотреть jayrock.berlios.de/
      +3
      Из этого всего пользовался только Newtonsoft.Json, про некоторые описанные в статье даже не слышал. Есть еще библиотека FastJson www.codeproject.com/KB/IP/fastJSON.aspx, которая работает немного быстрее Newtonsoft, судя по графикам и моим тестам. А вообще после проведения тестов, написали свою библиотеку для сериализации/дессериализации Json.
        0
        Это в каких таких задачах не хватает производительности сериализатора от NewtonSoft?
        +3
        >> Сложно придумать способ получающий строку в формате JSON быстрее.

        Учитывая иммутабельность строк, использовать конкатенацию в качестве эталона скорости — очень странное решение. Для таких целей придуман StringBuilder.
          +2
          Я просто оставлю это здесь
          www.codeproject.com/KB/cs/StringBuilder_vs_String.aspx
            0
            Сложно серьезно рассматривать тест, в котором не используется главная возможность StringBuilder — аллокация памяти по строковой буффер. Без нее он, естественно, ведет себя точно также как иммутабельные строки.
              0
              Пруфтест?
                +7
                Пруфтест — хотя бы статья на которую вы же и ссылаетесь, просто читать ее надо внимательней и применять к тому кейсу, который обсуждается, а не абстрактно. На графике прекрасно видно, как с ростом буфера ведет себя билдер, а как остальные методы. Билдер тормозит, поскольку по дефолту он поднимает capacity на длине 16, 32, 64, а потом начинает разгоняться, поскольку все реже и реже нуждается в реаллокациях. Строковые методы напротив начинают тормозить, поскольку с ростом строки ее все тяжелее каждый раз копировать. Просто автор мало того, что не инициализировал билдер как следует, так еще и остановился на конкатенации только 10 строк. Я думаю не надо доказывать, что это не кейс JSON-сериализации.
            +3
            А лучше сразу писать в TextWriter.
              +1
              Категорически согласен. Тем более, что бэкендом к нему может быть и StringBuilder через StringWriter.
            0
            Почему тестировали на .NET 3.5? Было бы интересно расмотреть поведение там.
              0
              Эх, еще бы сравнить скорость сериализации JSON и XML…
                +5
                Автор вот этой штуки утверждает, что она по скорости уделывает всё остальное. Сравните в том же окружении, если не трудно.
                  0
                  И такой вопрос, когда на Mono тестировали, были ли включены оптимизации (mono -O=all yourfile.exe)? Так же хотелось бы узнать, учитывалось ли время на «прогрев» среды?
                    0
                    Это не Ява, прогревать тут нечего. Один раз, при запуске, в первой итерации теста бинарник в репозиторий попал, и все.
                      0
                      Про emit кода в рантайме, чтобы не ломиться каждый раз через рефлекшн вы слышали? Некоторые библиотеки его активно эксплуатируют для оптимизации. В моно так вообще по умолчанию используется «ленивый» JIT, если ему не сказать --compileall. Так что первый вызов может быть в разы медленее, чем последующие.
                        +1
                        Тьфу, не так прочитал. В общем, изначально имелось ввиду, сравнивался ли «холодный» вызов или последующие.
                          0
                          Учитывая «Тест №1. 10 000 раз выполнялась инициализация сериализатора и сериализация одного и того же объекта» очень похоже на то, что сравнивось ещё и время вызова на холодную.
                        0
                        Компилировал и запускал в Monodevelop и VisualStudio с теми настройками, которые там есть «по умолчанию».
                        Чтобы исключить время «прогрева», я перед тестом 1 и 2, каждый сериализатор прогонял по 1-му разу.
                          0
                          MonoDevelop запускает сборки с мягко говоря не вполне оптимальными настройками, не говоря уже о том, что в большинстве дистрибутивов линукса у Mono отсутствует LLVM-бакэнд.
                            0
                            Согласен. Но врядли, это как-то может повлиять на сравнительные тесты. Все «конкурсанты» в одинаковых условиях.
                        0
                        Мне нужно было как-то сериализовать весьма большой кусок данных, с весьма простой структурой, желательно побыстрее. Был неприятно удивлен тем, насколько штатные сериализаторы тормозные.

                        ~6 раз — это как-то уж слишком накладно. Может есть что-то быстрое?
                          +1
                          И еще один момент — как по мне сериализатор должен уметь напрямую писать в TextWriter, и именно так и надо его использовать. Зачем там строки промежуточные? Почему бы не делать тесты, приближенные к реальному использованию?
                          +1
                          спасибо, очень полезная статья
                            +4
                            Простите, я что-то не совсем поляла, как именно вы тестируете сериализаторы.

                            Вы вообще что сериализируете? Какой грубины граф? Встречаются ли там повторяющие типы? Зацикленый ли граф объектов? Размер объектов?

                            Из того, что можно в статье увидеть, вы проверили, как быстро сериализаторы запускаются, и сериализируют «простые данные», типа двух строк. Умеет ли сериализатор справляться с интерфейсами, коллекциями и прочими абстракциями.

                            Реально, если на кубиках, вы подвесили на подъёмнике машину и нажали педаль газа.

                            В заключении вы написали:
                            1. BMW — скокрость раскручивания колеса составляет N ms
                            2. Lada Granta — M ms.
                            3. F1 — -Z ms
                              +1
                              Я в самом начале статьи указал, что статья есть не детальный обзор, и тесты проводились под те потребности, с которыми столкнулся я. Когда у меня появится потребность в сравнении функциональных возможностей сериализаторов, я напишу об этом статью, если Вы не сделаете этого раньше.

                              На типы объектов можно посмотреть, открыв исходный код теста — ссылка есть в статье.

                            Only users with full accounts can post comments. Log in, please.