ProtoBuf Nedir?
Selamlar,
Bu makalede google trafından geliştirilen, Json ve Xml Serialization’a göre çok daha performanslı çalışan Binnary Serialization Protobuf’i inceleyeceğiz. Gerçek ismi Protocol Buffers olan, Google’ın kendi içindeki veri iletisiminde de bolca kullandığı bir veri transfer protokolüdür.
Bir uygulamının performansı göz önüne alındığında, ilk önce en çok kullanılan yani yoğun olarak işlem yapılan dinamikleri monitör edilir. Ve bilin bakalım karşınıza performans canavarı ilk ne çıkar :) Tabii ki Json.NET – Newtonsoft :) Evet kullanımı kolay ama yoğun ve büyük projelerde kesinlikle terk edilmelidir. Alternatiflerinin çokluğu ile kafanız karışabilir. Ve kabul etmek gerekirse var olan bir sisteme, yeni bir serlization kütüphanesi getirmek biraz zahmetlidir. Ama aşağıdaki grafikden de anlaşıldığı gibi, bu kadar büyük bir performans farkı çekilen tüm zahmete deymektedir.
Image Source: https://i2.wp.com/maxondev.com/wp-content/uploads/2015/02/serialization_speed_large.png?w=600&ssl=1
Protobuf C, C#, Haskell, Perl, Ruby,C++, Java ve Python gibi birçok dili third party toolar ile desteklemektedir. Tam listeye buradan erişebilirsiniz. Biz bu makalede C# .Net Core üzerinde protobuf’ı inceleyeceğiz.
1 |
dotnet new console -o protobuf |
Yukarıda görüldüğü gibi yeni oluşturulan protobuf isimli .Net Core Console bir application’a, aşağıdaki komut ile Protobuf .Net kütüphanesi indirilmiştir.
1 |
dotnet add package protobuf-net --version 2.4.0 |
Önce gelin modellerimizi oluşturalım.
Base Model Human.cs: Esas “Person” adında oluşturulacak bir sınıfın, türetildiği Base bir sınıftır.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; using Model; using ProtoBuf; [ProtoContract] [ProtoInclude(100, typeof(Person))] public class Human{ [ProtoMember(1)] public string Surname { get; set; } [ProtoMember(2)] public DateTime BirthDate { get; set; } } |
Yukarıda dikkat edilecek bazı konular, aşağıda detaylıca anlatılmıştır:
- [ProtoContract] : Modellerin bu attribute ile işaretlenmiş olması gerekmektedir. Anlamı, bu modelin protobuf ile Serializa veya Deserializa olacağı anlamına gelmektedir.
- [ProtoInclude(100, typeof(Person))] : Bu sınıfdan inherit olacak sınıflar, gene burada tanımlanmalıdırlar. İşte yazılmış bir projeye, protobuf implementasyonun zorluğu burada başlar. Bu tanımlamada amaç, protobuf işlem yapacağı sınıfı bir bütün olarak görür. İlgili sınıfın tüm propertylerine, türetildiği sınıflar dahil olmak üzere, bu tanımlama ile erişir. Buradaki “100” sayısı, ilgili base sınıfa ait unique verilmiş bir sayı olması gerekmektedir.
- [ProtoMember(1)] : Protobuf ile transfer edilecek her bir property, bu attribute ile o sınıfa ait unique bir sayı ile işaretlenmelidir. Örneğin base Human class’ı için Surname (1) ve BirthDate(2) sayısı ile işaretlenmişlerdir.
Friends: Person sınıfının gene bir property’si olarak kullanılacak, bir başka sınıftır. Aşağıda görüldüğü gibi, bu da “[ProtoContract]” attribute’ü ile işaretlenmiştir. Ve yine protobuf ile işlem yapılacak tüm propertyleri, unique bir şekilde “[ProtoMember(x)]” attribute’ü ile işaretlenmiştir Buradaki “X”, ilgili sınıfta ait her bir kolon için unique olan bir sayıdır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using Model; using ProtoBuf; [ProtoContract] public class Firend{ [ProtoMember(1)] public string Name { get; set; } [ProtoMember(2)] public string Mobile { get; set; } [ProtoMember(3)] public Gender Gender { get; set; } } |
Gender: Gender, birazdan tanımlanacak Person sınıfının bir propery’si olan enum tipidir.
1 2 3 4 5 |
public enum Gender { Male, Female } |
Person: Sıra geldi person sınıfına. Bu sınıf gene “[ProtoContract]” ile işaretlenmiştir. Ve yukarda tanımlanan “Human” sınıfından türetilmiştir. Her bir propertysi “[ProtoMember(X)]” ile işaretlenmiş ve her bir kolonu için unique bir sayı verilmiştir. Ayrıca bir propertysi de yine yukarıda tanımlanan List of Friend’dir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System.Collections.Generic; using ProtoBuf; namespace Model { [ProtoContract] public class Person:Human { [ProtoMember(1)] public string Name { get; set; } [ProtoMember(2)] public decimal Sallary { get; set; } [ProtoMember(3)] public int Age { get; set; } [ProtoMember(4)] public Gender Gender { get; set; } [ProtoMember(5)] public List<Firend> Friends { get; set; } } } |
Program.cs: Aşağıda görüldüğü gibi “List<Person>” tipinde bir PersonList tanımlanmıştır. Amaç, mümkün olduğunca gerçek hayatta kullanılabilecek karışık bir tipi serializa etmektir.
- İlk eklenen Person sınıfı Friends propertyleri ile beraber complex bir sınıftır. Diğer 2 sınıf, sırf diziye birden çok sınıf eklensin diye konulmuştur.
- “using (var stream = new MemoryStream())” : Protobuf “List<Person>”‘ı Serialize edip, bu stream’e atar. Daha sonra “bye[ ]” dizisine çevirir.
- “ProtoBuf.Serializer.Serialize()” Protobuf serialize işlemi.
- “ProtoBuf.Serializer.Deserialize<T>” : Protobuf deserialize işlemi. Kendisine gelen stream’i “T” tipine çevirir.
- “var stream = new MemoryStream(bytePersonList)” : “byte[ ]” dizisine çevrilen “bytePersonList”, Protobuf ile deserialize edilmek için tekrardan “stream”‘e çevrilir.
- Tekrardan Deserialize edilen “ListPerson” gezilerek, ekrana basılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
using System; using System.Collections.Generic; using System.IO; using Model; namespace protobuf { class Program { static void Main(string[] args) { List<Person> PersonList = new List<Person>(); PersonList.Add(new Person() { Name = "Bora", Gender = Gender.Male, Age = 40, Sallary = 500, Surname = "Kaşmer", BirthDate = new DateTime(1978,6,3), Friends = new List<Firend>() { new Firend() { Name = "X", Gender = Gender.Male, Mobile = "5467891234" }, new Firend() { Name = "Y", Gender = Gender.Female, Mobile = "1122334455" } } }); PersonList.Add(new Person() { Name = "Kadir", Gender = Gender.Male, Age = 36, Sallary = 2000 }); PersonList.Add(new Person() { Name = "Mümin", Gender = Gender.Male, Age = 35, Sallary = 3000 }); byte[] bytePersonList = null; List<Person> ListPerson = null; try { using (var stream = new MemoryStream()) { ProtoBuf.Serializer.Serialize(stream, PersonList); bytePersonList = stream.ToArray(); Console.WriteLine(bytePersonList); } using (var stream = new MemoryStream(bytePersonList)) { ListPerson = ProtoBuf.Serializer.Deserialize<List<Person>>(stream); foreach (Person per in ListPerson) { Console.WriteLine($"Name : {per.Name} Gender: {per.Gender} Age: {per.Age} Sallary: {per.Sallary} SurName: {per.Surname} BirthDate: {per.BirthDate}"); if (per.Friends != null && per.Friends.Count > 0) { foreach (Firend fir in per.Friends) { Console.WriteLine($".Friend Name: {fir.Name} Gender: {fir.Gender} Mobile: {fir.Mobile}"); } } } } } catch { throw; } Console.ReadLine(); } } } |
Şimdiye kadar Protobuf ile serialize ve deserialize işlemleri nasıl oluyor, hep beraber inceledik. Şimdi gelin Json.NewtonSoft ile olan performance farkını test edelim.
Program.cs(Test): Yukarıda tanımlı Bora adlı Person Sınıfı, “ListPerson<Person>”‘a ilk test’de 5 adet 2. test’de 10 adet eklenerek, 1000000 kere “Protobuf” ve “NewtonSoft” ile Seserialize, Deserialize edilmiştir. Başlangıç ve bitiş zamanları arasındaki fark, “saniye” cinsinden ekrana yukarıdaki gibi yazılmıştır. Yapılan testten de anlaşıldığı gibi, Protobuf data büyüklüğü arttıkça kendini daha da göstermekte ve neden tercih edilmesi gerektiğini bu kadar az data ile bile, bu kadar büyük performans farkı göstererek ispatlamaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
byte[] bytePersonList = null; List<Person> ListPerson = null; DateTime StartTime = DateTime.Now; DateTime EndTime; DateTime StartTime2 = DateTime.Now; DateTime EndTime2; try { for (var i = 0; i < 1000000; i++) { using (var stream = new MemoryStream()) { ProtoBuf.Serializer.Serialize(stream, PersonList); bytePersonList = stream.ToArray(); } using (var stream = new MemoryStream(bytePersonList)) { ListPerson = ProtoBuf.Serializer.Deserialize<List<Person>>(stream); foreach (Person per in ListPerson) { //Console.WriteLine($"Name : {per.Name} Gender: {per.Gender} Age: {per.Age} Sallary: {per.Sallary} SurName: {per.Surname} BirthDate: {per.BirthDate}"); if (per.Friends != null && per.Friends.Count > 0) { foreach (Firend fir in per.Friends) { //Console.WriteLine($".Friend Name: {fir.Name} Gender: {fir.Gender} Mobile: {fir.Mobile}"); } } } } } } catch { throw; } finally { EndTime = DateTime.Now; Console.WriteLine("Half Time : ".PadRight(200, '*')); } try { StartTime2 = DateTime.Now; for (var i2 = 0; i2 < 1000000; i2++) { var dataList = JsonConvert.SerializeObject(PersonList); ListPerson = JsonConvert.DeserializeObject<List<Person>>(dataList); foreach (Person per in ListPerson) { //Console.WriteLine($"Name : {per.Name} Gender: {per.Gender} Age: {per.Age} Sallary: {per.Sallary} SurName: {per.Surname} BirthDate: {per.BirthDate}"); if (per.Friends != null && per.Friends.Count > 0) { foreach (Firend fir in per.Friends) { //Console.WriteLine($".Friend Name: {fir.Name} Gender: {fir.Gender} Mobile: {fir.Mobile}"); } } } } } catch { throw; } finally { EndTime2 = DateTime.Now; TimeSpan duration = new TimeSpan(EndTime2.Ticks - StartTime2.Ticks); Console.WriteLine("Total seconds Of NewtonSoft:" + (duration.TotalMilliseconds / 1000).ToString()); Console.WriteLine("".PadRight(200, '*')); TimeSpan duration2 = new TimeSpan(EndTime.Ticks - StartTime.Ticks); Console.WriteLine("Total seconds Of ProtoBuf:" + (duration2.TotalMilliseconds / 1000).ToString()); } Console.ReadLine(); |
Protobuf’a benzer kütüphaneler bulunmaktadır. Bunlardan bir tanesi de ZeroFormatter‘dır. Daha çok oyun programlama ve Unity için tasarlanmış bir kütüphanedir. Bir diğeri de MessagePack kütüphanesidir. Bu iki kütüphane sadece C# dilini desteklemektedir. Halbuki Protobuf, farklı bir çok dili desteklemektedir. Çalışma şekilleri ve kodlar, nerde ise Protobuf ile birebir aynıdır. Kısacası bu makalede geçen kodalardan, sınıfların başına konan “[ProtoContract]” yerine başka bir komut ve property’ye atanan “[ProtoMember(2)]” Unique index komutu yerine, ilgili kütüphaneye ait komut yazılarak kodlamaya devam edilebilir.
Geldik bir makalenin daha sonuna. Bu makalede, performans üzerine konuştuk. Umarım işinize yaramıştır. Artık büyük ve yoğun projelerde, NewtonSoft kullanırken iki kere düşünürsünüz:)
Yeni bir makalede görüşmek üzere, hepinize hoşçakalın.
Source Code: http://www.borakasmer.com/projects/protobufvsnewtonsoft
Kaynak :
- https://developers.google.com/protocol-buffers/docs/csharptutorial
- https://maxondev.com/serialization-performance-comparison-c-net-formats-frameworks-xmldatacontractserializer-xmlserializer-binaryformatter-json-newtonsoft-servicestack-text/
- https://stackoverflow.com/questions/4143421/fastest-way-to-serialize-and-deserialize-net-objects
Hocam makale için teşekkürler, .net core da varsayılan olarak bir api methodunda örneğin List döndüğümde bunu json ‘a çeviren newtonsoft sanırım bunun arasına girip zero formatter ile nasıl değiştirebiliriz sorum yanlış olabilir kusura bakmayın
Öncelikle data dönen List modelinin propertylerini ZeroFormatter’a göre aşağıdaki gibi indexleyip, [ZeroFormattable] attribute’ü ile işaretlemelisin.
Kısacası view modellerini kullanacağın serialize aracına göre değiştirmelisin. Sonra normal ZeroFormat kodunu içine gömücen.
[ZeroFormattable] Prop2 { get; set; }
public class MyClass
{
[Index(0)]
public virtual int[] Prop1 { get; set; }
[Index(1)]
public virtual IList
}
paket boyutlarından bahsettmemişsiniz json a göre trafik açısından ne kadar fark var
Çok var :) Daha detaylı cevap için, ölçüm yapacağım.
İyi çalışmalar.
merhaba bora hocam. merak ediyorumda bir online oyun -mmorg veya rpg- için socket mi yoksa protobuf mı kullanılmalıdır? ikisi arasında ne tür farklar var? protobuf klasik yöntem olan socket sisteminden daha stabil ve hızlı olabilir mi?
deneyimlerinizi düşününce fikrinizi merak ediyorum. ve bu konuda bir makale veya videolu bir performans testi görmek çokta iyi olur.
sağlıklı günler dilerim.
Selam Ahmet,
Socket ve Protobuf birbirinen biraz farklı konular. Ama amacını söylersen daha sağlıklı bir cevap verebilirim. Socket kullanacak isen bence en performanslısı Go Socket sonra SocketIO ve son olarak Azure SignalR Service.
İyi çalışmalar.
Hocam yorumlara cevap verseniz çok iyi olur. Trafik ve online oyun soruları benimde merak ettiğim konular. Sizi 600r seven motorcu gibi seviyorum hocam öptüm.
Verdim sorulara cevap :) 600RR candır. Eski motorlarımdan biridir. Pert etmeseydim iyiydi :)
Hoşçakal..