Yani bu bir ek yükü değil mi?
Evet Hayır Belki?
Bu çok garip bir soru çünkü makinedeki hafızayı adresleme aralığını ve eşyaların hafızada nerede olduğunu yığına bağlanamayacak şekilde sürekli izlemesi gereken bir yazılım hayal edin.
Örneğin, kullanıcı başka bir müzik dosyası yüklemeye çalıştığında, müzik dosyasının kullanıcı tarafından basılan bir düğmeye yüklendiği ve geçici bellekten çıkarıldığı bir müzik çaları hayal edin.
Ses verilerinin nerede saklandığını nasıl takip ederiz? Bunun için bir hafıza adresine ihtiyacımız var. Programın sadece ses veri yığınını bellekte değil aynı zamanda bellekte bulunduğunu da takip etmesi yeterlidir . Bu nedenle bir hafıza adresini (bir işaretçi gibi) tutmamız gerekir. Bellek adresi için gereken depolamanın boyutu, makinenin adres aralığıyla eşleşecektir (ör: 64 bit adresleme aralığı için 64 bit işaretçi).
Yani bir tür "evet", bir bellek adresini takip etmek için depolama alanı gerektiriyor, ancak bu tür dinamik olarak tahsis edilmiş bellek için önleyemeyeceğimiz bir durum değil.
Bu nasıl telafi edilir?
Bir işaretçinin kendisinin büyüklüğünden bahsedersek, bazı durumlarda yığını kullanarak maliyetlerden kaçınabilirsiniz, örneğin, bu durumda, derleyiciler göreceli bellek adresini etkili bir şekilde kodlayan ve bir işaretçinin maliyetini önleyen talimatlar oluşturabilir. Yine de, eğer bunu büyük, değişken boyutlu tahsisler için yaparsanız, kullanıcı girişi tarafından yönlendirilen karmaşık dallar dizisi için (ses örneğinde olduğu gibi) yapmak için pratik olmama eğiliminde olursunuz. ile elde edilmiş).
Başka bir yol daha bitişik veri yapılarını kullanmaktır. Örneğin, düğüm başına iki işaret gerektiren iki kat bağlı bir liste yerine dizi tabanlı bir sıra kullanılabilir. Bu ikisinin bir melezini, her bir bitişik N elementi grubu arasında sadece işaretçiler depolayan, kontrolsüz bir liste gibi kullanabiliriz.
İşaretçiler zaman kritik düşük bellek uygulamalarında kullanılıyor mu?
Evet, çok yaygın olarak, pek çok performans kritik uygulama işaretçi kullanımının baskın olduğu C ya da C ++ dilinde yazıldığından (bunlar akıllı bir işaretçi ya da kapsayıcısının arkasında olabilir , std::vector
ya std::string
da temel mekanikler kullanılan işaretçi için kaynar) adresi dinamik bir hafıza bloğunda takip etmek için).
Şimdi bu soruya dönelim:
Bu nasıl telafi edilir? (Bölüm iki)
İşaretçiler, milyonlarca (örneğin 64 bitlik bir makinede hala yaklaşık olarak 8 megabayt olan) gibi saklamadığınız sürece, genellikle ucuzdurlar.
* Ben'in, "kızarık" 8 megs 'in hala L3 önbellek büyüklüğü olduğuna dikkat çektiğine dikkat edin. Burada “DRAM kullanımı” anlamında daha “ölçülü” kullandım ve bellek parçalarına olan tipik göreceli boyut, işaretçilerin sağlıklı bir şekilde kullanıldığına işaret edecektir.
İşaretçilerin pahalı olduğu yerler işaretçilerin kendileri değildir, ancak:
Dinamik bellek ayırma. Dinamik bellek tahsisi, altta yatan bir veri yapısından geçmesi gerektiği için pahalı olma eğilimindedir (örneğin: dostum veya döşeme tahsisatörü). Bunlar genellikle ölüme göre optimize edilmiş olsalar da, genel amaçlıdırlar ve "arama" 'ya (hafif ve muhtemelen sabit zamanlara rağmen) en az bir miktar iş yapmalarını gerektiren değişken büyüklükteki blokları işlemek için tasarlanmışlardır. bellekte bir dizi bitişik sayfa bulun.
Hafıza erişimi. Bu, endişelenecek en büyük masraf olma eğilimindedir. Dinamik olarak ilk kez tahsis edilen belleğe ne zaman erişirsek, zorunlu bir sayfa hatası olur, ayrıca önbellek bellek hatırası, bellek hiyerarşisinde aşağı ve bir kayıt makinesine taşınır.
Hafıza erişimi
Belleğe erişim, performansın algoritmaların ötesindeki en kritik yönlerinden biridir. AAA oyun motorları gibi performans açısından kritik olan pek çok alan, enerjilerini büyük ölçüde, daha verimli bellek erişim düzenlerine ve düzenlerine kadar kaynayan veri odaklı optimizasyonlara odaklamaktadır.
Her kullanıcı tanımlı türü ayrı bir çöp toplayıcıyla ayrı ayrı tahsis etmek isteyen üst düzey dillerin en büyük performans zorluklarından biri, örneğin belleği biraz parçalayabilmeleridir. Bu, tüm nesnelerin bir kerede tahsis edilmemesi durumunda özellikle geçerli olabilir.
Bu durumlarda, bir kullanıcı tanımlı nesne türünün milyon örneğinin listesini saklarsanız, bu örneklere bir döngü içinde sırayla erişmek oldukça yavaş olabilir, çünkü belleğin farklı bölgelerini işaret eden milyonlarca işaretçi listesine benzemektedir. Bu durumlarda, mimari, büyük, hiyerarşinin daha üst, daha yavaş, daha büyük seviyelerinde bellek biçimini, bu parçalardaki çevreleyen verilere tahliye işleminden önce erişilmesi umuduyla hizalamak ister. Böyle bir listedeki her bir nesneye ayrı ayrı tahsis edildiğinde, çoğu zaman, sonraki her bir yinelemenin, tahliye işleminden önce bitişik nesnelere erişilmeksizin bellekteki tamamen farklı bir alandan yüklenmesi gerekebileceği zaman, önbellek hataları ile ödeme yapıyoruz.
Bu tür diller için derleyicilerin çoğu, bugünlerde talimat seçiminde ve kayıt tahsisinde gerçekten harika bir iş çıkarıyor, ancak burada bellek yönetimi üzerinde daha fazla doğrudan kontrol olmaması, katil olabilir (genellikle daha az hataya eğilimlidir) ve yine de C ve C ++ oldukça popüler.
İşaretçi Erişimini Dolaylı Olarak Optimize Etme
Performans açısından en kritik senaryolarda, uygulamalar genellikle referans konumunu geliştirmek için belleği bitişik parçalardan alan havuzları kullanır. Bu gibi durumlarda, bir ağaç veya bağlantılı bir liste gibi bağlantılı bir yapı bile, düğümlerinin hafıza düzeninin doğada bitişik olması şartıyla önbellek dostu hale getirilebilir. Bu, dolaylı olarak olsa da, başvuruları değiştirirken söz konusu olan referansın konumunu geliştirerek, işaretçi başvuruları daha ucuz hale getirmektedir.
İşaretçiler Etrafında Kovalamak
Tek bir bağlantılı listemiz olduğunu varsayalım:
Foo->Bar->Baz->null
Sorun şu ki, eğer bütün bu düğümleri genel amaçlı bir tahsisatçıya karşı ayrı ayrı tahsis edersek (ve muhtemelen hepsi bir seferde değil), gerçek hafızanın buna benzer bir şekilde dağılmış olması (basitleştirilmiş şema):
İşaretçileri kovalamaya ve Foo
düğüme erişmeye başladığımızda, bir yığınını bellek bölgesinden daha yavaş bellek bölgelerinden daha hızlı bellek bölgelerine götürmek için zorunlu bir özlemle (ve muhtemelen bir sayfa hatası) başlıyoruz:
Bu, yalnızca bir kısmına erişmek için bir bellek bölgesini önbelleğe almamıza (muhtemelen sayfa yapmamıza) ve bu listedeki işaretçileri kovalarken gerisini çıkarmamıza neden olur. Bununla birlikte, bellek ayırıcısı üzerinde kontrolü ele alarak, böyle bir listeyi sürekli olarak şöyle tahsis edebiliriz:
... ve böylece, bu işaretçilerden vazgeçme ve işaretçilerini işleme alma hızımızı önemli ölçüde artırır. Dolayısıyla, dolaylı olmasına rağmen, işaretçi erişimini bu şekilde hızlandırabiliriz. Elbette bunları sürekli bir dizide saklarsak, bu konuyu ilk etapta bulamazdık, ancak burada hafıza düzeni üzerinde açık bir kontrol sağlayan bize verilen bellek ayırıcı, bağlantılı bir yapı gerektiğinde günü kurtarabilir.
* Not: Bu, bellek hiyerarşisi ve referansın yeri hakkında çok basitleştirilmiş bir şema ve tartışmadır, ancak umarım sorunun seviyesi için uygundur.