Veri Odaklı Zihniyet
Veri odaklı tasarım, SoA'ları her yere uygulamak anlamına gelmez. Basitçe veri temsiline ağırlıklı olarak odaklanan mimarileri tasarlamak - özellikle verimli bellek düzeni ve bellek erişimine odaklanmak anlamına gelir.
Muhtemelen böyle uygun olduğunda SoA temsilcilerine yol açabilir:
struct BallSoa
{
vector<float> x; // size n
vector<float> y; // size n
vector<float> z; // size n
vector<float> r; // size n
};
... bu genellikle küre merkezi vektör bileşenlerini ve yarıçapı aynı anda (dört alan aynı anda sıcak değildir) işlemeyen dikey döngü mantığı için uygundur, bunun yerine bir seferde bir tane (yarıçaptan geçen bir döngü, başka bir 3 döngü küre merkezlerinin bireysel bileşenleri aracılığıyla).
Diğer durumlarda, alanlara sık sık erişilirse (döngüsel mantığınız tek tek değil tüm top alanları boyunca yineleniyorsa) ve / veya topun rasgele erişilmesi gerekiyorsa, bir AoS kullanmak daha uygun olabilir:
struct BallAoS
{
float x;
float y;
float z;
float r;
};
vector<BallAoS> balls; // size n
... diğer durumlarda, her iki yararı da dengeleyen bir melez kullanmak uygun olabilir:
struct BallAoSoA
{
float x[8];
float y[8];
float z[8];
float r[8];
};
vector<BallAoSoA> balls; // size n/8
... daha fazla top alanını önbellek çizgisine / sayfaya sığdırmak için yarım yüzer kullanarak bir topun boyutunu yarıya bile sıkabilirsiniz.
struct BallAoSoA16
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
Float16 r2[16];
};
vector<BallAoSoA16> balls; // size n/16
... belki de yarıçapa bile neredeyse kürenin merkezine kadar erişilmez (belki kod üssünüz sık sık onlara puanlar gibi davranır ve sadece nadiren küreler gibi davranır). Bu durumda, daha sonra sıcak / soğuk alan bölme tekniğini uygulayabilirsiniz.
struct BallAoSoA16Hot
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
};
vector<BallAoSoA16Hot> balls; // size n/16: hot fields
vector<Float16> ball_radiuses; // size n: cold fields
Veri odaklı tasarımın anahtarı, tasarım kararlarınızı vermeden önce bu tür bütün temsilleri göz önünde bulundurmak, kendinizi arkasında kamuya açık bir arayüze sahip alt-optimal bir gösterime hapsetmemek.
Hafıza erişim düzenlerine ve beraberindeki düzenlere ışık tutar ve bunları normalden çok daha güçlü bir endişe haline getirir. Bir anlamda, soyutlamaları bir nebze bile parçalayabilir. Bu zihniyeti daha fazla uygulayamadığımdan std::deque
, örneğin, algoritmik gereklilikleri bakımından, sahip olduğu toplanmış bitişik blokların gösterimi kadar ve bunun rasgele erişimin bellek düzeyinde nasıl çalıştığını görmeye başladım . Uygulama detaylarına biraz odaklanmak, ancak ölçeklendirilebilirliği tanımlayan algoritmik karmaşıklık kadar performans üzerinde bir etkiye veya daha fazla etkiye sahip olma eğiliminde olan uygulama detayları.
Erken Optimizasyon
Veri odaklı tasarımın ana odak odasının çoğu, en azından bir bakışta, tehlikeli durumdaki erken optimizasyona tehlikeli bir şekilde yakın görünecektir. Tecrübe genellikle bize bu tür mikro optimizasyonların en iyi şekilde ve en iyi şekilde bir profiler ile uygulandığını öğretir.
Yine de veri odaklı tasarımdan almak için güçlü bir mesaj, bu tür optimizasyonlara yer bırakmaktır. Veri odaklı bir zihniyet buna izin verebilir.
Veri odaklı tasarım, daha etkili temsiller keşfetmek için nefes alandan ayrılabilir. Bu, bir seferde bellek düzeni mükemmelliğini elde etmekle ilgili olmak zorunda değil, giderek artan şekilde en iyi sunumları mümkün kılmak için uygun düşünceleri önceden yapmakla ilgili.
Granüler Nesneye Dayalı Tasarım
Birçok veri odaklı tasarım tartışması, klasik nesne yönelimli programlama kavramlarına karşı çıkacak. Yine de, OOP'yi tamamen reddetmek kadar zor olmayan bir şeye bakmanın bir yolunu sunacağım.
Nesne yönelimli tasarımın zorluğu, bizi arabirimleri çok granüler bir seviyede modellemeye teşvik etmemiz ve bizi paralel bir zihniyet yerine, birer birer birer zihniyet ile tuzağa düşürmemize neden olması.
Abartılı bir örnek olarak, görüntünün tek bir pikseline uygulanan nesne yönelimli tasarım zihniyetini hayal edin.
class Pixel
{
public:
// Pixel operations to blend, multiply, add, blur, etc.
private:
Image* image; // back pointer to access adjacent pixels
unsigned char rgba[4];
};
Umarım kimse bunu yapmaz. Örneği gerçekten brüt yapmak için, piksel içeren görüntüye bir arka işaretçi koydum, böylece bulanıklaştırma gibi görüntü işleme algoritmaları için komşu piksellere erişebilir.
Görüntü arka göstericisi derhal bir göze çarpan ek yükü ekler, ancak dışladıysak bile (yalnızca pikselin genel arayüzünü tek bir piksele uygulanan işlemler sağlar), sadece bir pikseli temsil eden bir sınıfla sonuçlanır.
Şimdi, genel gider anlamında bir sınıfın, C ++ bağlamında, bu arka göstericinin yanı sıra hiçbir sorunu yoktur. C ++ derleyicilerini optimize etmek, inşa ettiğimiz tüm yapıyı almak ve onu smithereen'lere bölmek için mükemmeldir.
Buradaki zorluk, kapsüllenmiş bir arayüzü çok fazla piksel seviyesinde granülleştirmemizdir. Bu, bizi bu tür bir granüler tasarım ve verilerle hapsolmuş durumda bırakıyor, potansiyel olarak onları bu Pixel
arayüze bağlayan çok sayıda müşteri bağımlılığı .
Çözüm: tanecikli bir pikselin nesne yönelimli yapısını ortadan kaldırın ve arabirimlerinizi çok sayıda pikselle ilgilenen daha kalın bir seviyede modellenmeye başlayın (görüntü seviyesinde).
Toplu görüntü düzeyinde modelleme yaparak, optimize etmek için önemli ölçüde daha fazla alana sahibiz. Örneğin, 64 byte önbellek çizgisine mükemmel şekilde uyan 16x16 piksel boyutunda birleştirilmiş döşemeler olarak büyük görüntüleri temsil edebiliriz, ancak tipik olarak küçük bir adımla piksellerin verimli komşu dikey erişimine izin veriyoruz (eğer çok sayıda görüntü işleme algoritması varsa). sabit veri yönelimli bir örnek olarak komşu piksellere dikey biçimde erişilmesi gerekir).
Kaba Düzeyde Tasarım
Bir görüntü seviyesindeki modelleme arayüzlerinin yukarıdaki örneği, görüntü işleme, üzerinde çalışılmış ve ölüme göre optimize edilmiş çok olgun bir alan olduğu için daha akıllıca bir örnektir. Yine de daha az belirgin olan, bir parçacık yayıcıdaki bir parçacık, bir sprite ya da bir sprite topluluğuna, bir kenar grafiğindeki bir kenar veya hatta bir insana karşı bir insan topluluğuna olabilir.
Veriye yönelik optimizasyonlara izin vermenin anahtarı (öngörü veya ön görüşte) genellikle arayüzleri çok daha kaba bir seviyede, toplu olarak tasarlamaya düşecektir. Tekil varlıklar için arayüzler tasarlama fikri, onları büyük oranda işlemden geçiren büyük operasyonlara sahip varlıklar koleksiyonları için tasarlamanın yerini almaktadır. Bu özellikle ve hemen her şeye erişmesi gereken ve yardım edemeyen ancak doğrusal karmaşıklığa sahip olan sıralı erişim döngülerini hedefler.
Veri odaklı tasarım, genellikle veri toplama toplu modelleme oluşturmak için veri birleştirme fikri ile başlar. Benzer bir zihniyet, ona eşlik eden arayüz tasarımlarına da yansır.
Bu, veri odaklı tasarımdan aldığım en değerli ders, çünkü bilgisayar denemesine meraklıyım çünkü ilk denememde bir şeyler için en uygun bellek düzenini bulanlar. Bu, elinizde bir profiler ile yinelendiğim bir şey haline geliyor (ve bazen işleri hızlandırmayı başaramadığım bir kaç özlemle). Yine de, veri odaklı tasarımın arayüz tasarımı, beni daha verimli veri sunumları aramaya yer bırakıyor.
Kilit nokta, arayüzleri genellikle bizim istediğimizden daha kaba bir seviyede tasarlamaktır. Bu aynı zamanda, sanal işlevlerle ilişkili dinamik gönderim ek yükünü, işlev işaretçi çağrıları, dylib çağrıları ve sıralananların yapılamaması gibi yan yararları da beraberinde getirir. Tüm bunlardan kurtulmanın ana fikri işleme (toplu halde) bakmaktır.
ball->do_something();
karşıball_table.do_something(ball)
size sahte bir sahte-pointer aracılığıyla tutarlı bir varlık istemiyorsanız)(&ball_table, index)
.