ÜNOG
Published in

ÜNOG

Teknik Analiz — Bitmasking

“Bitmask” nedir ve oyunumuzu hızlandırmada bize nasıl yardımcı olur, bu makalemizde bu konuyu ele alacağız.

Hayvan türlerinden ve biyomlardan oluşan bir ekosistem simülasyonu yaptığımızı farz edelim. Aşağıda simülasyonumuzda bulunabilecek hayvan türlerinin yaşadığı birkaç örnek biyom yer almakta.

  • Savan
  • Çöl
  • Çalılık
  • Tropikal
  • Tundra
  • Kutup
  • Dağ
  • Su

Ekosistemimizdeki hayvan türlerini yaşayabilecekleri biyomla eşleştirmek istiyoruz. Ayrıca ekosistemimizin olabildiğince gerçekçi olması için bir hayvan türünün birden fazla biyomda yaşayabildiği bir sistem yazmamız gerekiyor. Örneğin yılan, birden fazla biyomda yaşayabilir. Bunun için aklımıza ilk gelen yöntemle başlayıp şansımızı deneyelim: Booleanlar.

Şimdi de ekosistemimize rastgele hayvan türleri ekleyelim.

Şu an simülasyonumuzda rastgele biyomlara atanmış bir milyon adet hayvan türümüz var. Diyelim ki simülasyonumuzun belli bir evresinde kocaman bir olay oluyor ve ekosistemin savan, tropikal ve kutup iklimlerini yok ediyor. Biz de hayvan listemizi o biyomlarda yaşayıp nesli tükenen hayvanları çıkartarak güncellemek istiyoruz. Bu biyomlardan herhangi birinde yaşayan hayvanların bu olaydan çok etkilenip neslinin tükendiğini varsayalım.

Biz yukarıdaki işlemi kendi bilgisayarımızda test ettiğimizde yaklaşık olarak 14 milisaniyede tamamlandığını gördük. Ayrıca boolean veri tipleri sadece bir bitle (1 ya da 0, true ya da false) ifade edilebilecek data’yı saklasalar da bilgisayarların çalışma sisteminden dolayı minimum 8 bitle yani 1 byte ile ifade edilebiliyorlar. Yani bir milyon adet hayvan türünün biyom datasını saklamak için yaklaşık olarak 61 megabyte kullanıyoruz.

Peki bu durumu nasıl daha verimli hale getirebiliriz? Bu noktada olaya bitmaskler dahil oluyor. Birkaç adet boolean değerini alıp tek bir veri tipinde topluyoruz ve elimize bir bitmask geçiyor.

Elimizde sekiz adet biyom olduğundan dolayı bitmask’in veri tipi olarak byte’ı kullanabiliriz; ama sekizden fazla sayıda biyom olsaydı, uint gibi daha büyük veri tipleri kullanmamız gerektiğinde de hiçbir sorun olmazdı.

Dikkat ederseniz rastgele hayvan türlerini belirlerken bu sefer bitshift (<<) operatörünü kullandık. Bu operatör bize bool değerinin (1 veya 0 sayısı) bit maskemizde isteğimiz index’e denk gelmesini sağlıyor. Bunun ardından bitwise or (|) operatörünü kullanarak orijinal maske ile birleştirdik.

Şimdi ise sıra savan, tropikal ve kutup bölgelerinde yaşayabilen hayvan türlerinin tespitini yapmaya geldi.

İlk olarak savan, tropikal ve kutup indexlerini içeren bit maskemizi oluşturuyoruz. Maskedeki biyom indexlerinin sırasının önceki örnekte oluşturduğumuz bool sırasıyla aynı olduğunu kabul ettim. Yani 0. index’te savanlık, 3. indexte tropikal, 5. indexte de kutup biyomu yer alıyor. Bu bilgiler doğrultusunda gerekli shift işlemlerini yaptıktan sonra da bitwise or (|) operatörünü kullanarak maskeleri birleştiriyoruz.

Tekil maskeleri bitwise or (|) operasyonuna sokarak birleştiriyoruz.

Bu işlemin ardından da bitwise and (&) operasyonunu kullanarak yeni oluşturduğumuz maskemizi hayvan türünün maskesiyle işleme soktuk. Bitwise and operasyonunun sonucunun 0'a eşit olduğu durumlarda hayvan türünün bu üç iklimde yaşamadığı kanısına ulaşabiliriz çünkü diğer her durumda sonuç 0'dan farklı çıkıyor. Yani bizim tek yapmamız gereken sonucun 0'a eşit olup olmadığına bakmak.

Peki bitmaskleri kullanmak bize ne kazandırdı? Öncelikle biyom bilgisi için memory’de kapladığımız alan %88 azaldı (61mb →7.1mb). Buna bağlı olarak da memory access oranımız azaldı, cache hit oranımız arttı. Yani daha cache friendly bir yapı kurduk.

Maske testimiz ise iki tane or (||) operasyonundan tek bir bitwise and (&) operasyonuna indirgendi. Ayrıca bitmaskleri kullandığımız sistem bool kullandığımız sisteme göre kat kat daha iyi scale ediliyor çünkü bitwise operasyonlarda condition sayısına göre testlerde fazladan vakit harcamıyoruz.

Hem daha cache friendly yapı kurmamızın hem de maske testinin bitwise operasyonlarda daha kısa sürede tamamlanmasının sonucu olarak yaptığımız işlemler %133 oranında hızlandı (14ms →6ms). Ayrıca kelimenin tam anlamıyla hiç bir biti boşa harcamadık.

Sonuçların doğruluğundan emin olmak adına aynı testi class yerine struct kullanarak tekrar gerçekleştirdim ve büyük bir fark olmadığını gözlemledim. Yine de gerçek proje koşullarında sonuçların farklı olabileceğini göz önünde bulundurursak, kendiniz test edip hangi data tipini kullanacağınızı belirlemeniz en doğrusu olur.

Bitmasklerin özellikle oyun motorlarında yaygın olarak kullanıldığı iki alan var: rendering sistemleri ve fizik (örneğin raycasting) simülasyonları. Unity oyun motorunda maksimum 32 adet katmanın bulunabilmesinin sebebi de bu. Arka tarafta, bizim kullandığımız byte (8 bit) yerine int (32 bit) kullanılarak bitmasking yapılıyor.

Sonuç olarak, bitmasking tekniğine pratikte pek fazla rastlanmıyor. Fakat bu teknik gerekli olduğu durumlarda kolayca uygulanabilen bir optimizasyon yöntemi olarak kullanılabiliyor.

Bu yazıdan sonra Bitarray denilen ve bitmask işlemleri için wrapper class özelliği taşıyan sistemleri de incelemek isteyebilirsiniz.

-Bora Aygen’e editleri için teşekkürler.

--

--

--

Profesyonel, bağımsız ve hatta öğrenci oyun geliştiricilerinin bir araya geldiği ÜNOG Oyun Geliştirici Topluluğu, bilgi ve tecrübe paylaşımına özendiriyoruz.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alpago Göktenay Göçer

Alpago Göktenay Göçer

Game Developer

More from Medium

Why I prefer DoTween over Animator for UI animations in Unity3D

Lerp-ing to the smallest angle in Unity

Unity Performance Optimization Ⅴ: Lua Scripts Optimization

Operation: Game Development