Kısa versiyon: Her zaman calloc()
yerine kullanın malloc()+memset()
. Çoğu durumda, bunlar aynı olacaktır. Bazı durumlarda, tamamen calloc()
atlayabileceğinden daha az iş yapar memset()
. Diğer durumlarda, calloc()
hile yapabilir ve herhangi bir bellek tahsis edemez! Ancak, malloc()+memset()
her zaman işin tamamını yapacak.
Bunu anlamak için kısa bir bellek sistemi turu gerekir.
Hızlı bellek turu
Burada dört ana bölüm vardır: programınız, standart kütüphane, çekirdek ve sayfa tabloları. Programınızı zaten biliyorsunuz, yani ...
Gibi Bellek ayırıcılarına malloc()
ve calloc()
çoğunlukla orada belleğin daha büyük havuzlarına ve grubun onları (KB 100s 1 byte bir şey) küçük tahsisler almak. Örneğin, 16 bayt ayırırsanız malloc()
, önce havuzlarından birinden 16 bayt almaya çalışır ve havuz kuru çalıştığında çekirdekten daha fazla bellek ister. Ancak, programın beri yaklaşık seferde büyük bir hafızaya miktarda ayırdığından soruyoruz, malloc()
ve calloc()
sadece doğrudan çekirdekten bu bellek için soracaktır. Bu davranışın eşiği sisteminize bağlıdır, ancak eşik olarak 1 MiB kullanıldığını gördüm.
Çekirdek, her işleme gerçek RAM ayırmaktan ve işlemlerin diğer işlemlerin belleğine müdahale etmediğinden emin olmaktan sorumludur. Buna bellek koruması denir , 1990'lardan beri kir yaygındır ve bir programın tüm sistemi yıkmadan çökmesinin nedeni budur. Bir program daha fazla belleğe ihtiyaç duyduğunda, sadece belleği almakla kalmaz, bunun yerine mmap()
veya gibi bir sistem çağrısı kullanarak çekirdekten bellek ister sbrk()
. Çekirdek, sayfa tablosunu değiştirerek her işleme RAM verecektir.
Sayfa tablosu bellek adreslerini gerçek fiziksel RAM ile eşler. 32 bitlik bir sistemde işleminizin adresleri, 0x00000000 - 0xFFFFFFFF, gerçek bellek değil, sanal bellekteki adreslerdir . İşlemci bu adresleri 4 KiB sayfaya böler ve her sayfa, sayfa tablosunu değiştirerek farklı bir fiziksel RAM parçasına atanabilir. Yalnızca çekirdeğin sayfa tablosunu değiştirmesine izin verilir.
Nasıl çalışmıyor
256 MiB gelmez tahsis şekli şöyledir değil çalışır:
calloc()
İşleminiz çağırır ve 256 MiB ister.
Standart kütüphane çağırır mmap()
ve 256 MiB ister.
Çekirdek 256 MiB kullanılmayan RAM bulur ve sayfa tablosunu değiştirerek işleminize verir.
Standart kütüphane RAM'i sıfırlar memset()
ve içinden döner calloc()
.
İşleminiz sonunda çıkar ve çekirdek RAM'i geri alır, böylece başka bir işlem tarafından kullanılabilir.
Aslında nasıl çalışır
Yukarıdaki süreç işe yarayacaktır, ancak bu böyle olmaz. Üç büyük fark vardır.
İşleminiz çekirdekten yeni bellek aldığında, bu bellek muhtemelen daha önce başka bir işlem tarafından kullanılmıştı. Bu bir güvenlik riskidir. Bu bellekte parolalar, şifreleme anahtarları veya gizli salsa tarifleri varsa ne olur? Hassas verilerin sızmasını önlemek için, çekirdek bir işleme başlamadan önce her zaman belleği temizler. Belleği sıfırlayarak da temizleyebiliriz ve eğer yeni bellek sıfırlanırsa, bir garanti de verebiliriz, bu yüzden mmap()
döndürdüğü yeni belleğin her zaman sıfırlandığını garanti eder.
Dışarıda bellek ayıran ancak belleği hemen kullanmayan birçok program var. Bazı zamanlar bellek ayrılır ancak hiç kullanılmaz. Çekirdek bunu biliyor ve tembel. Yeni bellek ayırdığınızda, çekirdek sayfa tablosuna hiç dokunmaz ve işleminize RAM vermez. Bunun yerine, işleminizde bazı adres alanı bulur, oraya ne gitmesi gerektiğini not eder ve programınız gerçekten kullanıyorsa RAM'i oraya koyacağına dair bir söz verir. Programınız bu adresleri okumaya veya bu adreslerden yazmaya çalıştığında, işlemci bir sayfa hatasını tetikler ve çekirdek bu adreslere RAM atama adımlarını uygular ve programınıza devam eder. Belleği asla kullanmazsanız, sayfa hatası asla olmaz ve programınız RAM'i asla almaz.
Bazı işlemler hafızayı tahsis eder ve değiştirmeden hafızadan okur. Bu, farklı işlemlerde bellekteki birçok sayfanın döndürülen bozulmamış sıfırlarla doldurulabileceği anlamına gelir mmap()
. Bu sayfaların hepsi aynı olduğundan, çekirdek tüm bu sanal adresleri sıfırlarla dolu tek bir paylaşılan 4 KiB bellek sayfasını işaret eder. Bu belleğe yazmaya çalışırsanız, işlemci başka bir sayfa hatasını tetikler ve çekirdek size başka bir programla paylaşılmayan yeni bir sayfa sıfırlamak için devreye girer.
Son süreç daha çok şöyle görünür:
calloc()
İşleminiz çağırır ve 256 MiB ister.
Standart kütüphane çağırır mmap()
ve 256 MiB ister.
Çekirdek, 256 MiB kullanılmayan adres alanı bulur , bu adres alanının şu anda ne için kullanıldığını not eder ve geri döner.
Standart kütüphane sonucunun mmap()
her zaman sıfırlarla doldurulduğunu bilir (veya gerçekte biraz RAM aldığında olacaktır ), bu yüzden belleğe dokunmaz, bu nedenle sayfa hatası yoktur ve RAM asla işleminize verilmez .
İşleminiz sonunda çıkar ve çekirdeğin RAM'i geri almasına gerek yoktur çünkü asla ilk etapta tahsis edilmemiştir.
memset()
Sayfayı sıfırlamak için kullanırsanız memset()
, sayfa hatasını tetikler, RAM'in ayrılmasına neden olur ve daha önce sıfırlarla doldurulmuş olsa bile sıfırlar. Bu ekstra iş çok büyük miktarda olduğu ve açıklıyor calloc()
daha hızlı olduğunu malloc()
ve memset()
. Eğer yine de hafızayı kullanmaya son verirseniz calloc()
, yine de daha hızlıdır malloc()
, memset()
ancak fark o kadar saçma değildir.
Bu her zaman işe yaramaz
Tüm sistemlerde disk belleği sanal bellek bulunmadığından, tüm sistemler bu optimizasyonları kullanamaz. Bu, 80286 gibi çok eski işlemcilerin yanı sıra gelişmiş bir bellek yönetim birimi için çok küçük olan tümleşik işlemciler için de geçerlidir.
Bu, her zaman daha küçük ayırmalarla da çalışmayacaktır. Daha küçük ayırmalarla, calloc()
doğrudan çekirdeğe gitmek yerine paylaşılan bir havuzdan bellek alır. Genel olarak, paylaşılan havuzda kullanılan ve serbest bırakılan eski bellekten önemsiz veriler depolanabilir, bu free()
nedenle calloc()
bu belleği alıp memset()
temizlemek için çağrılabilir. Ortak uygulamalar, paylaşılan havuzun hangi bölümlerinin bozulmamış ve hala sıfırlarla dolu olduğunu izler, ancak tüm uygulamalar bunu yapmaz.
Bazı yanlış cevapları gönderme
İşletim sistemine bağlı olarak, çekirdek daha sonra sıfırlanmış bellek almanız gerektiğinde boş zamanlarında belleği sıfırlayabilir veya sıfırlamayabilir. Linux vaktinden önce belleği sıfırlamaz ve Dragonfly BSD yakın zamanda bu özelliği çekirdekten de sildi . Diğer bazı çekirdekler ise önceden sıfır bellek kullanır. Boşta dururken sayfaları sıfırlamak, büyük performans farklılıklarını zaten açıklamak için yeterli değildir.
calloc()
Fonksiyon bazı özel hafıza hizalı sürümünü kullanmıyor memset()
ve bu çok daha hızlı yine de yapmazdım. memset()
Modern işlemciler için çoğu uygulama şöyle görünür:
function memset(dest, c, len)
// one byte at a time, until the dest is aligned...
while (len > 0 && ((unsigned int)dest & 15))
*dest++ = c
len -= 1
// now write big chunks at a time (processor-specific)...
// block size might not be 16, it's just pseudocode
while (len >= 16)
// some optimized vector code goes here
// glibc uses SSE2 when available
dest += 16
len -= 16
// the end is not aligned, so one byte at a time
while (len > 0)
*dest++ = c
len -= 1
Gördüğünüz gibi memset()
, çok hızlı ve büyük bellek blokları için daha iyi bir şey elde edemeyeceksiniz.
Aslında memset()
zaten sıfırlanır bellek sıfırlar hafıza iki kez sıfırlanmasını alır Bu ne anlama geliyor, ama bu sadece bir 2x performans farkı açıklar. Buradaki performans farkı çok daha büyük (sistemimde malloc()+memset()
ve arasında üçten fazla büyüklük sırasını ölçtüm calloc()
).
Parti hilesi
10 kez döngü yerine, NULL değerine kadar bellek ayıran malloc()
veya calloc()
NULL döndüren bir program yazın .
Eklerseniz ne olur memset()
?