.Net Core ile Protobuf Kullanımı ve JSON Performans Karşılaştırması

Merhabalar,

Öncelikle eğer kemerlerinizi bağlamadıysanız hemen bağlayın, çünkü birazdan Protobuf-net ile uçuşa geçeceğiz. Nasıl mı?

Şöyle ki kodlara girmeden önce kısaca Protocol Buffers’dan bahsedelim.

Google tarafından geliştirilmiştir ve amacı ise yapılandırdığınız dataların serileştirilmesini(serialization) sağlamaktır.

Örnek olarak herkes tarafından bilinen XML Format’ını düşünebilirsiniz. Bu örnekteki handikap şudur. XML DOM’ları içerisinde gezinmek kompleksdir ve Serialize/Deserialize işlemlerinin de maliyeti biraz yüksektir. Buna alternatif olarak yine tüm geliştiriciler tarafından bilinen ve sektörde kabul görmüş JSON Format karşımıza çıkıyor. Bunların detaylarına girmeyeceğim. Çünkü asıl konumuzdan uzaklaşmak istemiyorum. Protobuf bize bu serileştirme işlemlerini birçok formata göre çok daha hızlı bir şekilde gerçekleştiriyor.

Şimdi, Protobuf’ın nasıl kullanıldığına ve JSON’a göre nasıl bir performans gösterdiğine bakacağımız uygulamamıza geçelim.

  • İlk olarak bir .Net Core Console Application oluşturun.
  • Ardından Package Manager Console’u açıp şu komutları çalıştırın.
 Install-Package protobuf-net
Install-Package Newtonsoft.json



 

Protobuf Contract Tanımlamaları

Contract based çalışan bir yapısı var ve ilgili model classlarımıza attribute olarak tek seferlik tanımlayıp kullanıyoruz.  Örnek için kendime şu contractları oluşturdum. Siz isterseniz farklı bir senaryo üretebilirsiniz.

using ProtoBuf;
using System;

namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    [ProtoContract]
    [ProtoInclude(3, typeof(Order))]
    public class BaseContract
    {
        [ProtoMember(1)]
        public int CreatedBy { get; set; }
        [ProtoMember(2)]
        public DateTime CreatedDate { get; set; }
    }
}
namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    public enum AddressType
    {
        Billing = 1,
        Delivery = 2
    }
}
using ProtoBuf;

namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    [ProtoContract]
    public class Address
    {
        [ProtoMember(1)]
        public string City { get; set; }
        [ProtoMember(2)]
        public string Line1 { get; set; }
        [ProtoMember(3)]
        public string Line2 { get; set; }
        [ProtoMember(4)]
        public AddressType Type { get; set; }
    }
}
using ProtoBuf;
using System.Collections.Generic;

namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    [ProtoContract]
    public class Customer
    {
        [ProtoMember(1)]
        public string FullName { get; set; }
        [ProtoMember(2)]
        public string Phone { get; set; }
        [ProtoMember(3)]
        public List<Address> Addresses { get; set; }
    }
}
using ProtoBuf;

namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    [ProtoContract]
    public class Product
    {
        [ProtoMember(1)]
        public string ProductSKU { get; set; }
        [ProtoMember(2)]
        public string ProductName { get; set; }
        [ProtoMember(3)]
        public int Quantity { get; set; }
        [ProtoMember(4)]
        public decimal Price { get; set; }
    }
}
using ProtoBuf;
using System;
using System.Collections.Generic;

namespace ProtobufnetVSNewtonsoftjson.Contracts
{
    [ProtoContract]
    public class Order : BaseContract
    {
        [ProtoMember(1)]
        public string OrderNumber { get; set; }
        [ProtoMember(2)]
        public DateTime OrderDate { get; set; }
        [ProtoMember(3)]
        public decimal TotalPrice { get; set; }
        [ProtoMember(4)]
        public List<Product> Products { get; set; }
        [ProtoMember(5)]
        public Customer CustomerInfo { get; set; }
    }
}

Yukarıdaki Contract tanımlamalarında inheritance ve diğer caseler için kullanımlarını görebilirsiniz. Simple bir yapıya sahip. Her member Unique’dir.
Örnekte “ProtoContract, ProtoInclude ve ProtoMember(N)” attribute’leri kullanılmıştır.

ProtoInclude: Inheritance kullanılacaksa bu etiket eklenir.

ProtoContract: Object modelinin en tepesine eklenen etiketdir.

ProtoMember(N): Object modelindeki memberlara uygulanır ve N(=1,=2=3, vb.) değeri unique olmalıdır. Aksi halde runtimeda hata fırlatır.

  • Oluşturduğumuz contractlar ile program.cs->main method içerisine inMemory data ekliyoruz.
        #region inMemoryData
            var data = new Order
            {
                CreatedBy = 1,
                CreatedDate = DateTime.Now,
                OrderDate = DateTime.Now,
                OrderNumber = "MSTF123456789",
                TotalPrice = 150,
                Products = new List<Product>{
                                              new Product {
                                                            ProductName = "Test Product 1" ,
                                                            ProductSKU = "TP123456",
                                                            Quantity = 2,
                                                            Price = 25
                                                          },
                                              new Product {
                                                            ProductName = "Test Product 2" ,
                                                            ProductSKU = "TP123457",
                                                            Quantity = 1,
                                                            Price = 100
                                                          }
                                            },
                CustomerInfo = new Customer
                {
                    FullName = "Mustafa KOÇ",
                    Phone = "5551112233",
                    Addresses = new List<Address> {
                                                   new Address {
                                                                   City ="İstanbul",
                                                                   Line1 = "Maslak Mh.",
                                                                   Line2 = "Şişli",
                                                                   Type = AddressType.Delivery
                                                                },
                                                   new Address {
                                                                   City ="İstanbul",
                                                                   Line1 = "Maslak Mh.",
                                                                   Line2 = "Şişli",
                                                                   Type = AddressType.Billing
                                                               }
                                                  }
                }
            };
        #endregion
  • Hemen ardına aşağıdaki kod bloğunu ekleyin.
                byte[] serializedData;
                Order deserializedOrder = new Order();
    
                #region Protobuf-net
                var watchProtoBuf = System.Diagnostics.Stopwatch.StartNew();
                for (int i = 0; i < 1000000; i++)
                {
                    using (var mStream = new MemoryStream())
                    {
                        ProtoBuf.Serializer.Serialize(mStream, data);
                        serializedData = mStream.ToArray();
                    }
                    using (var mStream = new MemoryStream(serializedData))
                    {
                        deserializedOrder = ProtoBuf.Serializer.Deserialize<Order>(mStream);
                    }
                }
                watchProtoBuf.Stop();
                var elapsedMsProtoBuf = watchProtoBuf.ElapsedMilliseconds;
                Console.WriteLine($"ProtoBuf-net 1.000.000 row data Serialize/Deserialize ExecutionTime(ms): {elapsedMsProtoBuf}");
                #endregion
    
                #region  Newtonsoft.Json
                string jsonSerializedData = string.Empty;
                var watchJson = System.Diagnostics.Stopwatch.StartNew();
                for (int i = 0; i < 1000000; i++)
                {
                    jsonSerializedData = JsonConvert.SerializeObject(data);
                    deserializedOrder = JsonConvert.DeserializeObject<Order>(jsonSerializedData);
                }
                watchJson.Stop();
                var elapsedMsJson = watchJson.ElapsedMilliseconds;
                Console.WriteLine($"Newtonsoft.Json 1.000.000 row data Serialize/Deserialize ExecutionTime(ms): {elapsedMsJson}");
                #endregion

    Yukarıdaki kod bloğunda “Protobuf-net” c# dili ile kullanımı #region etiketleri içerisinde belirtilmiştir. Hemen altında ise Json kullanım örneği yine #region etiketleri arasında gösterilmiştir.

    ProtoBuf.Serializer.Serialize(mStream, data) satırında inMemory datasını stream ediyoruz ve sonrasında byte dizisine dönüştürüyoruz. Bu dönüşüp sonucu bize serialized datayı veriyor. Datayı deserialize etmek için ise serialized datayı stream edip ProtoBuf.Serializer.Deserialize<T>(mStream) methodunu kullanıyoruz. Burada T generic dönüşümün yapılacağı tür, mStream ise deserialize edilecek data.

Şimdi çalıştırıp performans analizini gözlemleyebiliriz.

Senaryo: Serialize/Deserialize for 1.000.000 object
Formatlar: Protobuf, Json

Result:

ProtoBuf-net 1.000.000 row data Serialize/Deserialize ExecutionTime(ms): 9715
Newtonsoft.Json 1.000.000 row data Serialize/Deserialize ExecutionTime(ms): 25840

Resultımızı görselleştirirsek performans farkını daha iyi görebiliriz.

 

Application Open Source Code: https://github.com/mustafa-koc/ProtobufnetVSNewtonsoftjson

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir