HashSet & HashSet Performans Değerlendirmesi
Bu makalemizde HashSet kullanımı ve HashSet performans değerlerine göz atacağız. HashSet<T> .Net 3.5 framework ile System.Collections.Generic namespace içinde kullanıma sunulan koleksiyonlardan biridir. HashSet<T> için özellikle List Generic tip’ in gölgesinde kalmış, yazdığımız kodlarda nadiren kullanılan framework’ün istenmeyen koleksiyon tipi demek çok da yanlış olmaz. Durum bu şekilde olunca HashSet yeteneklerinin başka bir koleksiyon tip ile sağlandığı kod bloklarının hali HashSet’ in egosunu epeyce kabartacak dereceye gelebiliyor. HashSet in özelliklerinden bahsedecek olursak
- Diğer koleksiyon tiplerinde olduğu gibi genişletilebilir kapasiteye sahiptir. Yeni öğe eklendiğinde veya silindiğinde otomatik olarak kapasitesinde değişik yapar.
- HashSet sıralı bir koleksiyon tipi değildir. Bu nedenle eklediğimiz nesnelerin ilk ,son veya herhangi eklenme index’leri konusunda garanti vermez. HashSet indexlemesini HashCode olarak yapmaktadır.
- HashSet tekrar eden nesne içermez. 1,3,1 değerlerini sırasıyla eklediğimizi varsayarsak işlem sonunda elimizde 1,3 değerleri olacaktır.Veri eklemek içn kulanılan Add metodu dönüş tipi olarak bool tipinde bir değer dönmektedir.
- HashSet içinde bir değeri aramak çok hızlıdır. Koleksiyonun en önemli tercih noktası da bu olmalıdır. Arama yaparken hash-based searching algorthm kullanır.
Şimdi kısaca HashSet metodlarına göz atıp kullanımını örnekleyelim.
(Tanımlama)Initialize Instance
1 2 3 4 5 |
HashSet<string> hashSet = new HashSet<string>(); |
Add
Set içine nesne eklemek için kullanılır ve geriye bool tipinde değer döner Add HashSet ‘ in dezavantajlı durumunu ortaya çıkarır. HashSet’ e ekleme işlemi yapmak diğer koleksiyon tiplerine göre daha yavaştır. Bunun neden eklenilen her nesneyi hash-based olarak saklamasıdır. Bu yavaşlığın meyvesini ise set içine lookup(arama) yaptığımızda alırız.
1 2 3 4 5 6 7 8 |
var result1 = hashSet.Add("Burak"); var result2 = hashSet.Add("Ece"); var result3 = hashSet.Add("Ece"); Console.Write(result1 + "-" + result2 + "-" + result3 + " set count :" + hashSet.Count); |
sonucu kontrol ettiğimizde true true false set count : 2 elde edeceğiz.
Remove
set içindeki öğeyi silme işlemi için kullanılır ve geriye bool tipinde değişken döner. Remove çalışma mantığında ilk olarak set içine lookup yapar nesneyi bulunur sonra siler. Bu işlem HashSet üzerinde oldukça hızlıdır. Set içinde olmayan bir nesne remove edildiğinde değer false olarak geri döner. Parametre olarak Initialize edilen set tipinde nesne alır. Biz set’imizi string oluşturduğumuz için remove metoduna parametre olarak string nesne göndereceğiz.
1 2 3 4 5 |
hashSet.Remove("Burak"); |
RemoveWhere
Set üzerinde verilen koşul’a uygun tüm kayıtları silmek için kullanılır. Metod parametre olarak koşul(condition) alır yani lambda expression veya predicate kullanabiliriz.
1 2 3 4 5 |
hashSet.RemoveWhere(p => p.Equals("Ece")); |
Contains
Set üzerindeki nesneye direk olarak erişim sağlar. O(1) ilişkisi vardır. Yani nesneye direk erişim. Bu ifadeyi koleksiyon dökümanlarında O(n) veya O(1) şeklinde görebilirsiniz. Geriye bool tipinde değer döndürür.
1 2 3 4 5 |
bool resultContains = hashSet.Contains("Ece"); |
HashSet ile ilgili diğer metodları msdn üzerinden detaylı şekilde inceleyebilirsiniz.
HashSet kullanımı performans incelemesi
HashSet performansını incelerken karşılaştırma koleksiyon tipi olarak alternatifi olduğu List koleksiyon tipini kullanacağız.
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 |
Stopwatch _stopwatch = new Stopwatch(); for (int d = 0; d < 5; d++) { List<string> list = new List<string>(); HashSet<string> hashSet = new HashSet<string>(); Console.WriteLine("**************Test" + d + "**************"); _stopwatch.Start(); Console.WriteLine("List.Add Start Count : 1000000"); for (int i = 0; i < 1000000; i++) { list.Add("Burak_" + i); } _stopwatch.Stop(); Console.WriteLine("List.Add Finish Result_ms :" + _stopwatch.ElapsedMilliseconds); _stopwatch.Restart(); _stopwatch.Start(); Console.WriteLine("hashSet.Add Start Count : 1000000"); for (int j = 0; j < 1000000; j++) { hashSet.Add("Burak_" + j ); } _stopwatch.Stop(); Console.WriteLine("hashSet.Add Finish Result_ms :" + _stopwatch.ElapsedMilliseconds); _stopwatch.Restart(); } Console.ReadLine(); |
Performans Sonucu
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 |
**************Test0************** List.Add Start Count : 1000000 List.Add Finish Result_ms :525 hashSet.Add Start Count : 1000000 hashSet.Add Finish Result_ms :737 **************Test1************** List.Add Start Count : 1000000 List.Add Finish Result_ms :477 hashSet.Add Start Count : 1000000 hashSet.Add Finish Result_ms :579 **************Test2************** List.Add Start Count : 1000000 List.Add Finish Result_ms :370 hashSet.Add Start Count : 1000000 hashSet.Add Finish Result_ms :622 **************Test3************** List.Add Start Count : 1000000 List.Add Finish Result_ms :384 hashSet.Add Start Count : 1000000 hashSet.Add Finish Result_ms :660 **************Test4************** List.Add Start Count : 1000000 List.Add Finish Result_ms :488 hashSet.Add Start Count : 1000000 hashSet.Add Finish Result_ms :618 |
üst üste gerçekleştirdiğimiz Add işlemi List ‘ in üstünlüğüyle sonuçlandı. Buradan elde edeceğimiz sonuç set içinde lookup yapmayacaksak List koleksiyonunu tercihimiz olabilir. Şimdi senaryomuzu yenileyelim ve sonucu tekrar değerlendirelim.
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 |
for (int d = 1; d< 50; d+= 3) { List<object> list = new List<object>(); HashSet<object> hashset = new HashSet<object>(); for (int i = 0; i < d; i++) { list.Add(new object()); hashset.Add(new object()); } object objToAddRem = list[0]; Stopwatch timer = new Stopwatch(); timer.Start(); for (int i = 0; i < 10000000; i++) { list.Remove(objToAddRem); list.Add(objToAddRem); } timer.Stop(); Console.WriteLine(d.ToString() + " LIST : " + timer.ElapsedMilliseconds.ToString() + "ms"); timer = new Stopwatch(); timer.Start(); for (int i = 0; i < 10000000; i++) { hashset.Remove(objToAddRem); hashset.Add(objToAddRem); } timer.Stop(); Console.WriteLine(d.ToString() + " HASHSET : " + timer.ElapsedMilliseconds.ToString() + "ms"); Console.WriteLine(); } Console.ReadLine(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
1 LIST : 610ms 1 HASHSET : 613ms 6 LIST : 779ms 6 HASHSET : 494ms 11 LIST : 1105ms 11 HASHSET : 506ms 16 LIST : 1509ms 16 HASHSET : 543ms 21 LIST : 1833ms 21 HASHSET : 511ms 26 LIST : 2168ms 26 HASHSET : 536ms |
yukarıdaki kod blogunda hashSet’in Add Remove Contains gibi methodlarla oluşturduğu iş parçacıklarında yükselen grafiğini görebilirsiniz. Veri büyüklüğü arttıkça HashSet in list’e göre daha iyi sonuçlar verdiği ortada.
O halde büyük verinin olduğu özellikle search işlemi yapacağımız kod bloklarında hashset ‘ i kullanmak bize performans yönünden fayda sağlayacaktır demek yanlış olmayacaktır.
H.Burak Karadağ
Referans