Тесты производительности сериализаций, используемых привязками WCF - PullRequest
12 голосов
/ 25 сентября 2010

У меня есть следующий объект:

public partial class Game
{
    public bool Finished { get; set; }

    public Guid GameGUID { get; set; }

    public long GameID { get; set; }

    public bool GameSetup { get; set; }

    public Nullable<int> MaximumCardsInDeck { get; set; }

    public Player Player { get; set; }

    public Player Player1 { get; set; }

    public bool Player1Connected { get; set; }

    public bool Player1EnvironmentSetup { get; set; }

    public long Player1ID { get; set; }

    public int Player1Won { get; set; }

    public bool Player2Connected { get; set; }

    public bool Player2EnvironmentSetup { get; set; }

    public long Player2ID { get; set; }

    public int Player2Won { get; set; }

    public int Round { get; set; }

    public Nullable<int> RoundsToWin { get; set; }

    public bool Started { get; set; }

    public string StateXML { get; set; }

    public Nullable<DateTime> TimeEnded { get; set; }

    public Nullable<int> TimeLimitPerTurn { get; set; }

    public byte[] TimeStamp { get; set; }

    public Nullable<DateTime> TimeStarted { get; set; }    
}

Этот класс будет заполнен некоторыми данными испытаний .

Мне нужно сравнить производительность различных сериализаторов, используемых различными формами привязок для служб WCF:

  • basicHttpBinding => SoapFormatter (TextFormatter?)
  • binaryBinding => BinaryFormatter
  • XMLFormatter

Что мне нужно сделать подробно:

  • Получить к настоящему моменту размер сериализуемого объекта
  • Получить теперь размер после серизации
  • Время для сериализации
  • Время для десериализации

Iуже пробовал кое-что, но я немного борюсь.Возможно, уже есть какой-то простой код для такого рода измерений.

Ответы [ 5 ]

35 голосов
/ 25 сентября 2010

OK; Я укушу ... вот некоторые необработанные метрики serializer (emph: вам может понадобиться рассмотреть base-64 / MTOM, чтобы получить общие требования к пропускной способности, плюс любые фиксированные накладные расходы (как пространство, так и процессор), которые добавляет WCF ), тем не мение; Сначала результаты:

BinaryFormatter
Length: 1314
Serialize: 6746
Deserialize: 6268

XmlSerializer
Length: 1049
Serialize: 3282
Deserialize: 5132

DataContractSerializer
Length: 911
Serialize: 1411
Deserialize: 4380

NetDataContractSerializer
Length: 1139
Serialize: 2014
Deserialize: 5645

JavaScriptSerializer
Length: 528
Serialize: 12050
Deserialize: 30558

(protobuf-net v2)
Length: 112
Serialize: 217
Deserialize: 250

(поэтому я делаю вывод, что protobuf-net v2 победитель ...)

Номера обновлены в .NET 4.5 и текущих сборках библиотеки на более новой машине:

BinaryFormatter
Length: 1313
Serialize: 2786
Deserialize: 2407

XmlSerializer
Length: 1049
Serialize: 1265
Deserialize: 2165

DataContractSerializer
Length: 911
Serialize: 574
Deserialize: 2011

NetDataContractSerializer
Length: 1138
Serialize: 850
Deserialize: 2535

JavaScriptSerializer
Length: 528
Serialize: 8660
Deserialize: 8468

(protobuf-net v2)
Length: 112
Serialize: 78
Deserialize: 134

с тестовой установкой (скомпилировано с оптимизацией, запускается из командной строки):

(и обратите внимание, мне пришлось изобрести класс Player и некоторые примеры данных):

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using ProtoBuf.Meta;


static class Program
{
    static void Main()
    {
        var orig = new Game {
             Finished = true, GameGUID = Guid.NewGuid(), GameID = 12345, GameSetup = false, MaximumCardsInDeck = 20,
             Player = new Player { Name = "Fred"}, Player1 = new Player { Name = "Barney"}, Player1Connected = true,
             Player1EnvironmentSetup = true, Player1ID = 12345, Player1Won = 3, Player2Connected = true, Player2EnvironmentSetup = true,
             Player2ID = 23456, Player2Won = 0, Round = 4, RoundsToWin = 5, Started = true, StateXML = "not really xml",
             TimeEnded = null, TimeLimitPerTurn = 500, TimeStamp = new byte[] {1,2,3,4,5,6}, TimeStarted = DateTime.Today};
        const int LOOP = 50000;

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        using (var ms = new MemoryStream())
        {
            var ser = new BinaryFormatter();
            Console.WriteLine();
            Console.WriteLine(ser.GetType().Name);
            ser.Serialize(ms, orig);
            Console.WriteLine("Length: " + ms.Length);
            ms.Position = 0;
            ser.Deserialize(ms);

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ms.SetLength(0);
                ser.Serialize(ms, orig);
            }
            watch.Stop();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ser.Deserialize(ms);
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        using (var ms = new MemoryStream())
        {
            var ser = new XmlSerializer(typeof(Game));
            Console.WriteLine();
            Console.WriteLine(ser.GetType().Name);
            ser.Serialize(ms, orig);
            Console.WriteLine("Length: " + ms.Length);
            ms.Position = 0;
            ser.Deserialize(ms);

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ms.SetLength(0);
                ser.Serialize(ms, orig);
            }
            watch.Stop();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ser.Deserialize(ms);
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        using (var ms = new MemoryStream())
        {
            var ser = new DataContractSerializer(typeof(Game));
            Console.WriteLine();
            Console.WriteLine(ser.GetType().Name);
            ser.WriteObject(ms, orig);
            Console.WriteLine("Length: " + ms.Length);
            ms.Position = 0;
            ser.ReadObject(ms);

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ms.SetLength(0);
                ser.WriteObject(ms, orig);
            }
            watch.Stop();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ser.ReadObject(ms);
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        using (var ms = new MemoryStream())
        {
            var ser = new NetDataContractSerializer();
            Console.WriteLine();
            Console.WriteLine(ser.GetType().Name);
            ser.Serialize(ms, orig);
            Console.WriteLine("Length: " + ms.Length);
            ms.Position = 0;
            ser.Deserialize(ms);

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ms.SetLength(0);
                ser.Serialize(ms, orig);
            }
            watch.Stop();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ser.Deserialize(ms);
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        {
            var sb = new StringBuilder();
            var ser = new JavaScriptSerializer();
            Console.WriteLine();
            Console.WriteLine(ser.GetType().Name);
            ser.Serialize(orig, sb);
            Console.WriteLine("Length: " + sb.Length);
            ser.Deserialize(sb.ToString(), typeof(Game));

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                sb.Length = 0;
                ser.Serialize(orig, sb);
            }
            watch.Stop();
            string s = sb.ToString();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ser.Deserialize(s, typeof(Game));
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        using (var ms = new MemoryStream())
        {
            var ser = CreateProto();
            Console.WriteLine();
            Console.WriteLine("(protobuf-net v2)");
            ser.Serialize(ms, orig);
            Console.WriteLine("Length: " + ms.Length);
            ms.Position = 0;
            ser.Deserialize(ms, null, typeof(Game));

            var watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ms.SetLength(0);
                ser.Serialize(ms, orig);
            }
            watch.Stop();
            Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);
            watch = Stopwatch.StartNew();
            for (int i = 0; i < LOOP; i++)
            {
                ms.Position = 0;
                ser.Deserialize(ms, null, typeof(Game));
            }
            watch.Stop();
            Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
        }

        Console.WriteLine();
        Console.WriteLine("All done; any key to exit");
        Console.ReadKey();
    }
    static TypeModel CreateProto()
    {
        var meta = TypeModel.Create();
        meta.Add(typeof(Game), false).Add(Array.ConvertAll(typeof(Game).GetProperties(),prop=>prop.Name));
        meta.Add(typeof(Player), false).Add(Array.ConvertAll(typeof(Player).GetProperties(),prop=>prop.Name));
        return meta.Compile();
    }
}

[Serializable, DataContract]
public partial class Game
{
    [DataMember]
    public bool Finished { get; set; }
    [DataMember]
    public Guid GameGUID { get; set; }
    [DataMember]
    public long GameID { get; set; }
    [DataMember]
    public bool GameSetup { get; set; }
    [DataMember]
    public Nullable<int> MaximumCardsInDeck { get; set; }
    [DataMember]
    public Player Player { get; set; }
    [DataMember]
    public Player Player1 { get; set; }
    [DataMember]
    public bool Player1Connected { get; set; }
    [DataMember]
    public bool Player1EnvironmentSetup { get; set; }
    [DataMember]
    public long Player1ID { get; set; }
    [DataMember]
    public int Player1Won { get; set; }
    [DataMember]
    public bool Player2Connected { get; set; }
    [DataMember]
    public bool Player2EnvironmentSetup { get; set; }
    [DataMember]
    public long Player2ID { get; set; }
    [DataMember]
    public int Player2Won { get; set; }
    [DataMember]
    public int Round { get; set; }
    [DataMember]
    public Nullable<int> RoundsToWin { get; set; }
    [DataMember]
    public bool Started { get; set; }
    [DataMember]
    public string StateXML { get; set; }
    [DataMember]
    public Nullable<DateTime> TimeEnded { get; set; }
    [DataMember]
    public Nullable<int> TimeLimitPerTurn { get; set; }
    [DataMember]
    public byte[] TimeStamp { get; set; }
    [DataMember]
    public Nullable<DateTime> TimeStarted { get; set; }
}
[Serializable, DataContract]
public class Player
{
    [DataMember]
    public string Name { get; set; }
}
20 голосов
/ 25 сентября 2010

У меня также есть графы тестов для различных сериализаторов в .NET , которые показывают двоичный сериализатор protobuf-net @Marc Gravell в качестве явного победителя. Хотя я поддерживаю самые быстрые текстовые сериализаторы .NET, которые наиболее близки к его сопоставлению и также намного быстрее, чем все сериализаторы, входящие в BCL в .NET.

Эти тесты основаны на образце базы данных Nortwind от Microsoft и показывают, насколько медленнее каждый сериализатор по сравнению с Protobuf-net.

ProtoBuf.net(v1)                      1x
ServiceStack TypeSerializer           2.23x
ServiceStack JsonSerializer           2.58x
Microsoft DataContractSerializer      6.93x
NewtonSoft.Json                       7.83x
Microsoft BinaryFormatter             9.21x
Microsoft JsonDataContractSerializer  9.31x

Полные тесты доступны здесь

Так что, если вы предпочитаете / хотите использовать быстрый текстовый сериализатор, вот ссылки на Стерилизатор текстов с открытым исходным кодом:

Кстати, JavaScriptSerializer от Microsoft показывал худшую производительность и временами был в 40-100 раз медленнее, чем Protobuf-сети. Забрал, потому что они замедляли мои тесты :)

5 голосов
/ 25 сентября 2010

Я изменил исходный код @ Marc и добавил результаты для сериализаторов ServiceStack JSV и JSON. Вот результаты моего 3-летнего iMac:

BinaryFormatter
Length: 1313
Serialize: 3959
Deserialize: 3395

XmlSerializer
Length: 1049
Serialize: 1710
Deserialize: 2716

DataContractSerializer
Length: 911
Serialize: 712
Deserialize: 2117

NetDataContractSerializer
Length: 1138
Serialize: 1093
Deserialize: 4825

TypeSerializer
Length: 431
Serialize: 496
Deserialize: 887

JsonSerializer
Length: 507
Serialize: 558
Deserialize: 1213

Вот исходный код, который я добавил в @ Marc's.бенчмарк выше.

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
var sbJsv = new StringBuilder(4096);
using (var sw = new StringWriter(sbJsv))
{
    Console.WriteLine();
    Console.WriteLine(typeof(TypeSerializer).Name);
    TypeSerializer.SerializeToWriter(orig, sw);
    var jsv = sbJsv.ToString();
    Console.WriteLine("Length: " + sbJsv.Length);
    TypeSerializer.DeserializeFromString<Game>(jsv);

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        sbJsv.Length = 0;
        TypeSerializer.SerializeToWriter(orig, sw);
    }
    watch.Stop();
    Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);

    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        TypeSerializer.DeserializeFromString<Game>(jsv);
    }
    watch.Stop();
    Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
}

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
var sbJson = new StringBuilder(4096);
using (var sw = new StringWriter(sbJson))
{
    Console.WriteLine();
    Console.WriteLine(typeof(JsonSerializer).Name);
    JsonSerializer.SerializeToWriter(orig, sw);
    var json = sbJson.ToString();
    Console.WriteLine("Length: " + sbJson.Length);
    JsonSerializer.DeserializeFromString<Game>(json);

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        sbJson.Length = 0;
        JsonSerializer.SerializeToWriter(orig, sw);
    }
    watch.Stop();
    Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds);

    watch = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        JsonSerializer.DeserializeFromString<Game>(json);
    }
    watch.Stop();
    Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds);
}

Примечание: я не смог достать dll @ Marc protobuf-net v2 r352, который он использовал для этого, поэтому мне пришлось прокомментировать тесты protobuf-net.

4 голосов
/ 25 сентября 2010

на простейшем уровне; просто сериализовать загрузку данных, рассчитать время и измерить пропускную способность. Кроме того, shed-load должен включать как большие, так и маленькие (но много) полезные нагрузки.

Вы также должны рассмотреть с / без MTOM. И хотя я, возможно, пристрастен, я предлагаю вам включить альтернативные сериализаторы WCF, такие как protobuf-net (дайте мне знать, если вам нужна помощь, подключив это). Из-за большой работы в этой области, она обычно побеждает всех, кого вы упомянули, с приличным запасом на каждом такте.

Многое из того, что здесь задействовано, может быть исследовано на уровне сериализатора, даже не касаясь WCF, однако это игнорирует base-64 / MTOM, поэтому изображение не на 100%.

Однако мы не можем определить ваши меры для вас; только вы можете решить, что является ключевым. Хотя у меня есть ряд мер - обычно это просто:

  • сериализовать один раз в MemorySteam (и десериализовать); это дает вам размер и простые JIT
  • теперь сохраняем этот поток памяти (в виде удобного буфера) и (внутри секундомера) сериализуем много тысяч раз. Делить. Перематывайте каждый раз, чтобы перезаписывать (не расширять).
  • повторять, но десериализовать много тысяч раз. Делить.
1 голос
/ 25 сентября 2010

Использовать объект постоянного размера; Получение информации о размере от Type является грязным и не принесет вам много пользы с точки зрения выяснения, что является «лучшим». Любой объект, оформленный как DataContract, можно сериализовать в двоичный (DataContract наследует Serializable), базовый XML (любой объект с конструктором по умолчанию можно сериализовать в XML) или DataContract XML (для этого требуется большая часть разметки для начала, но это довольно просто) .

Для работающего теста создайте метод, который будет принимать объект и сериализатор. Он должен создать MemoryStream и запустить StopWatch, затем сериализовать объект в MemoryStream (убедитесь, что Flush ()). Затем он останавливает секундомер и возвращает вам результаты в виде TimeSpan и длины потока. Затем перезагрузите и запустите секундомер, десериализовать поток и запомните это время. Вы можете установить возвращаемые результаты в виде простой структуры.

Запустите его с тем же объектом для каждого сериализатора, который вы хотите проверить. Выведите каждый из результатов на консоль или отладочный вывод, и пусть победит лучший сериализатор.

В общем, я думаю, вы найдете:

  • BinarySerializer будет самым быстрым и наименьшим, так как у него меньше всего байтов для записи при сериализации. Однако двоичные сериализации .NET зависят от платформы; если вы хотите говорить с чем-либо, кроме другой сборки .NET, которая знает ваш точный тип, забудьте об этом.

  • XMLSerializer, SoapSerializer и DataContractSerializer выводят различные формы XML. DataContract на самом деле является самым простым форматом (XML является чрезвычайно простым, потому что рукопожатие и другая информация о протоколе / связи отделена) и, вероятно, будет довольно быстрым. В сериализованном файле SOAP много раздувается из-за информации о транспорте и метаданных, но его легко генерировать, так как это довольно строгий формат. Базовая XML-сериализация, потому что она очень гибкая, имеет много накладных расходов, но может генерировать очень простую или очень сложную схему.

...