Json в .NET может использоваться для разных целей. В моём случае это формирование ответа на Ajax-запрос в ASP.NET Mvc приложении. Конечно, конвертация ответа в JSON — не самое тонкое место, но мне стало интересно, как можно ускорить эту операцию. Настоящая статья не является детальным обзором существующих JSON-сериализаторов для .NET/Mono. Меня интересовало в первую очередь время, затрачиваемое на сериализацию относительно простых структур данных, и во вторую очередь маппинг. То есть, хочется чтобы сериализация легко и гибко программировалась и быстро работала.
В исследование попали следующие средства сериализации:
Для замера производительности проводилось два типа тестов:
Тест №1. 10 000 раз выполнялась инициализация сериализатора и сериализация одного и того же объекта.
Тест №2. 1 раз создавался и инициализировался сериализатор. 10 000 раз выполнялась сериализация. Тест был придуман с надеждой на то, что «умный» сериализатор, получив заранее информацию о типе объекта, будет выполнять преобразование быстрее. В некоторых случаях, предварительная инициализация невозможна, далее результаты таких тестов буду отмечать прочерком.
Среда тестирования:
Среди испытуемых есть два класса, которые входят в платформу, а это значит, что в их работе в Mono и .NET могут быть различия. В качестве основной и единственной ОС у меня Ubuntu, поэтому для тестирования .NET я воспользовался виртуальной машиной VirtualBox. А если быть точным:
Первым делом я приведу результаты тестирования сериализации путём простого ToString() и конкатенации. В «продакшн» такой способ может быть оправдан лишь в редких случаях. Я же его рассматриваю для того, чтобы использовать в качестве эталона. Сложно придумать способ получающий строку в формате JSON быстрее.
Результаты тестов ниже:
Проведение теста №2, по понятным причинам, невозможно.
JavaScriptSerializer находится в пространстве имён System.Web.Script.Serialization платформ .NET/Mono. Пример кода:
Заявлена возможность переопределить способы сериализации для различных типов данных при помощи классов JavaScriptConverter, RegisterConverters. Это может пригодиться, например, для преобразования DateTime или enum-ов. Тесты проводились без использования этой возможности.
Результаты тестов:
Ещё один сериализатор доступный «из коробки» находится в пространстве имён System.Runtime.Serialization.Json. В использовании почти не отличается от предыдущего, за исключением того, что сериализация происходит в stream, а не в строку. Пример:
Есть различия использования этого инструмента в .NET и Mono. В .NET необходимо размечать сериализуемые классы и свойства атрибутами [DataContract] и [DataMember] соответственно. В Mono эти атрибуты не обязательны.
Результаты тестов показывают, что разница не только в атрибутах:
Забегая вперёд, скажу 1.5 сек — это лучшее время среди всех испытуемых, 34 сек — худшее.
Достоинство (а кому и недостаток) этого сериализатора в том, что он предоставляет гибкий способ маппинга свойств. Нет необходимости вводить атрибуты в слой бизнес-логики, что при работе со сторонними библиотеками проблематично. Не понадобятся промежуточные классы, чтобы преобразовать структуры данных слоя бизнес-логики в структуры данных клиент-серверного обмена. Ещё одной из заявленных «фишек», является возможность многопоточной обработки больших наборов данных, что даёт высокую производительность. Не мой случай.
Пример кода ниже показывает, как это работает:
Если сериализуемый тип данных содержит свойства типа перечисления или DateTime, как в примере выше, то указывать преобразование для них обязательно. Самостоятельно Fluent Json не догадается, что с ними делать.
Результаты тестов:
По заверениям разработчика, этот инструмент предназначен для точной сериализации/десериализации объектов, которую не дают другие библиотеки. Для меня же это не самое главное, тестировать буду на скорость. Пример кода:
Пикантной особенностью JsonEx, является то, что в результат он добавляет форматирование и комментарий вида:
Форматирование может быть полезно при сохранении, например, настроек в файл, который в дальнейшем может редактироваться в текстовом редакторе.
Результаты тестов:
В описании возможностей есть интригующая строчка «High performance, faster than .NET's built-in JSON serializers». Пример кода использующего Newton укладывается в одну строку:
Какой либо предварительной инициализации не требует, при этом показывает очень хорошее время:
Результаты тестирования отсортированные по возрастанию времени (в миллисекундах) для обоих конфигураций (столбец K показывает во сколько раз тест медленнее «конкатенации строк»):
Из таблицы видно, что если есть планы гонять приложение под 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):
Как видно, появился новый лидер — ServiceStack.
P.S. Если Вы заметили ошибку, или Вам известны другие библиотеки, пишите, с радостью дополню статью.
В исследование попали следующие средства сериализации:
- Простая конкатенация строк
- JavaScriptSerializer (.NET Framework)
- DataContractJsonSerializer (.NET Framework 3.5)
- Newton Json.net (json.codeplex.com, james.newtonking.com/pages/json-net.aspx)
- JsonEx (code.google.com/p/jsonexserializer)
- 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. Сторонние библиотеки придётся выкачать самостоятельно, ещё раз ссылки:
- Newton Json.net:json.codeplex.com, james.newtonking.com/pages/json-net.aspx
- JsonEx:code.google.com/p/jsonexserializer
- Fluent Json:fluentjson.codeplex.com, code.google.com/p/fluent-json
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. Если Вы заметили ошибку, или Вам известны другие библиотеки, пишите, с радостью дополню статью.