Burada dikkat çeken noktaların çoğunu kapsayan birçok iyi cevap var, bu yüzden doğrudan yukarıda ele alınmadığını gördüğüm birkaç konuyu ekleyeceğim. Yani, bu cevap artılarını ve eksilerini kapsamlı olarak değil, buradaki diğer cevaplara bir ek olarak düşünülmelidir.
mmap sihir gibi görünüyor
Dosyanın zaten temel olarak 2 önbelleğe alındığı durumda 1 , sihir gibi görünebilir :mmap
mmap
tüm dosyayı (potansiyel olarak) eşlemek için yalnızca 1 sistem çağrısı gerektirir, bundan sonra daha fazla sistem çağrısı gerekmez.
mmap
dosya verilerinin çekirdekten kullanıcı alanına kopyalanmasını gerektirmez.
mmap
otomatik olarak derleme otomatik vektörleştirme, SIMD intrinsics, önceden getirme, optimize edilmiş bellekte ayrıştırma rutinleri, OpenMP, vb.
Dosyanın zaten önbellekte olması durumunda yenilmesi imkansız gibi görünüyor: sadece çekirdek sayfası önbelleğine bellek olarak doğrudan erişiyorsunuz ve bundan daha hızlı olamazsınız.
Evet, olabilir.
mmap aslında sihir değil çünkü ...
mmap hala sayfa başına çalışıyor
Birincil gizli mmap
vs vs read(2)
(gerçekten okuma blokları için karşılaştırılabilir işletim sistemi düzeyinde sistem çağrısıdır) ) ile olmasıdır mmap
bunu tarafından gizlenmiş olabilir rağmen, kullanıcı uzayda her 4K sayfa için "bazı işler" yapmak gerekir sayfa hata mekanizması.
Bir örnek için mmap
, tüm dosyanın sadece 100 GB / 4K = 25 milyon hatayı 100 GB'lık bir dosyayı okumak için hatalı olması gerekir. Şimdi bunlar küçük hatalar, ancak 25 milyar sayfa hatası hala süper hızlı olmayacak. Küçük bir hatanın maliyeti muhtemelen en iyi durumda 100'lü yıllardadır.
mmap büyük ölçüde TLB performansına güveniyor
Şimdi, geçebilir MAP_POPULATE
için mmap
dönmeden önce tüm sayfa tabloları kurmak için söylemek, bu yüzden erişirken hiçbir sayfa hataları olmamalıdır. Şimdi, bu da tüm dosyayı RAM'e okuyor, 100GB'lık bir dosyayı eşleştirmeye çalışırsanız patlayacak - ama şimdilik bunu görmezden gelelim 3 . Çekirdeğin bu sayfa tablolarını ayarlamak için sayfa başına çalışma yapması gerekir (çekirdek zamanı olarak gösterilir). Bu, mmap
yaklaşımda büyük bir maliyettir ve dosya boyutu ile orantılıdır (yani, dosya boyutu büyüdükçe nispeten daha az önemli olmaz) 4 .
Son olarak, böyle bir eşlemeye kullanıcı alanına erişimde bile tam olarak ücretsiz değildir (dosya tabanlı olmayan büyük bellek arabellekleriyle karşılaştırıldığında mmap
) - sayfa tabloları ayarlandıktan sonra bile, yeni bir sayfaya her erişim, kavramsal olarak bir TLB özledim. Dan berimmap
sayfa önbellek ve onun 4K sayfaları kullanan bir dosya aracı ing, tekrar bir 100GB dosyası için Bu ücreti 25 milyon kez tabi.
Şimdi, bu TLB eksiklerinin gerçek maliyeti, donanımınızın en azından aşağıdaki yönlerine büyük ölçüde bağlıdır: (a) kaç adet 4K TLB'ye sahip olduğunuz ve çeviri önbelleğe alma işleminin geri kalanı nasıl performans gösterir (b) donanımın önceden getirilmesinin ne kadar iyi başa çıktığı TLB ile - ör. prefetch bir sayfa yürüyüşünü tetikleyebilir mi? (c) sayfa yürüyen donanımın ne kadar hızlı ve ne kadar paralel olduğu. Modern üst düzey x86 Intel işlemcilerde, sayfa yürüyüş donanımı genel olarak çok güçlüdür: en az 2 paralel sayfa yürüteç vardır, sürekli yürütme ile aynı anda bir sayfa yürüyüşü oluşabilir ve donanım önceden getirme sayfa yürüyüşünü tetikleyebilir. TLB'nin bir akış üzerindeki etkisi okuma yükü oldukça düşüktür ve bu tür bir yük, sayfa boyutundan bağımsız olarak genellikle benzer şekilde performans gösterir. Ancak diğer donanımlar genellikle çok daha kötüdür!
read () bu tuzaklardan kaçınır
read()
Genellikle de buradadır syscall, tip çağrıları C, C ++, örneğin teklif ve diğer diller herkesin iyi farkında olduğunu bir birincil dezavantajı vardır "blok okuma":
- Her
read()
N bayt çağrısı N baytını çekirdekten kullanıcı alanına kopyalamalıdır.
Öte yandan, yukarıdaki maliyetlerin çoğunu önler - 25 milyon 4K sayfada kullanıcı alanına eşlemenize gerek yoktur. Genellikle malloc
kullanıcı alanında tek bir arabellek küçük bir arabellek kullanabilir ve bunu tekrar tekrar tümread
aramalarınız kullanabilirsiniz. Çekirdek tarafında, 4K sayfalarda veya TLB eksiklerinde neredeyse hiçbir sorun yoktur, çünkü RAM'in tamamı genellikle birkaç çok büyük sayfa (örneğin, x86'da 1 GB sayfalar) kullanılarak doğrusal olarak eşleştirilir, bu nedenle sayfa önbelleğindeki temel sayfalar kapsanır çekirdek alanında çok verimli.
Temel olarak, büyük bir dosyanın tek bir okuması için hangisinin daha hızlı olduğunu belirlemek için aşağıdaki karşılaştırmaya sahipsiniz:
Sayfa başına fazladan çalışma, mmap
yaklaşımın ima ettiği dosya içeriklerinin çekirdekten kullanıcı alanına kopyalanmasıyla yapılan bayt başına çalışmalardan daha maliyetli read()
mi?
Birçok sistemde, aslında yaklaşık olarak dengelidirler. Her birinin donanım ve işletim sistemi yığınının tamamen farklı nitelikleriyle ölçeklendiğini unutmayın.
Özellikle, mmap
yaklaşım aşağıdaki durumlarda nispeten daha hızlı hale gelir:
- İşletim sistemi hızlı küçük hata işleme ve özellikle hata etrafı gibi küçük hata toplu optimizasyonlarına sahiptir.
- İşletim sistemi,
MAP_POPULATE
örneğin temel sayfaların fiziksel bellekte bitişik olduğu durumlarda büyük haritaları verimli bir şekilde işleyebilen iyi bir uygulamaya sahiptir.
- Donanım, büyük TLB'ler, hızlı ikinci seviye TLB'ler, hızlı ve paralel sayfa yürüteçleri, çeviri ile iyi önceden getirme etkileşimi gibi güçlü sayfa çeviri performansına sahiptir.
... read()
yaklaşım şu durumlarda nispeten daha hızlı olur:
- Sistem
read()
çağrısı iyi kopyalama performansına sahiptir. Örneğin, copy_to_user
çekirdek tarafında iyi performans.
- Çekirdeğin belleği eşleştirmek için etkili bir (kullanıcı alanına göre) yolu vardır, örneğin donanım desteğine sahip yalnızca birkaç büyük sayfa kullanarak.
- Çekirdeğin hızlı sistem çağrıları ve çekirdek TLB girişlerini sistem çağrıları arasında tutmanın bir yolu vardır.
Yukarıdaki donanım faktörleri , aynı aile içinde (örneğin, x86 nesiller ve özellikle pazar segmentleri içinde) ve kesinlikle mimariler arasında (örn., ARM vs x86 vs PPC) bile farklı platformlarda çılgınca değişir .
OS faktörleri de değişmeye devam ediyor, her iki tarafta çeşitli iyileştirmeler bir yaklaşım veya diğeri için göreceli hızda büyük bir sıçramaya neden oluyor. Yeni bir liste şunları içerir:
- Arızaya eklenmesi, yukarıda açıklanan, gerçekten
mmap
olmadan da yardımcı olur MAP_POPULATE
.
- Hızlı yol
copy_to_user
yöntemlerinin eklenmesi arch/x86/lib/copy_user_64.S
, örneğin, REP MOVQ
hızlı olduğunda kullanma , bu da gerçekten yardımcı olur read()
.
Spectre ve Meltdown'dan sonra güncelleme
Spectre ve Meltdown güvenlik açıklarının hafifletilmesi bir sistem çağrısının maliyetini önemli ölçüde artırdı. Ölçtüğüm sistemlerde, "hiçbir şey yapma" sistem çağrısının maliyeti (bu, çağrı tarafından yapılan herhangi bir gerçek işin dışında, sistem çağrısının saf ek yükünün bir tahmini), tipik olarak 100 ns'den gitti. yaklaşık 700 ns modern Linux sistemi. Ayrıca, sisteminize bağlı olarak, özellikle Meltdown için yapılan sayfa tablosu yalıtım düzeltmesi, TLB girişlerini yeniden yükleme gereği nedeniyle doğrudan sistem çağrı maliyeti dışında ek aşağı yönde etkilere neden olabilir.
Tüm bunlar, read()
temel yöntemlerle karşılaştırıldığında temel yöntemler için göreceli bir dezavantajdır mmap
, çünkü read()
yöntemler her "tampon boyutu" değeri için bir sistem çağrısı yapmalıdır. L1 boyutunu aştığınız ve bu nedenle sürekli olarak önbellek özledikleri için büyük arabelleklerin kullanılması genellikle daha kötü performans gösterdiğinden, bu maliyeti amorti etmek için arabellek boyutunu keyfi olarak artıramazsınız.
Öte yandan, ile mmap
, MAP_POPULATE
tek bir sistem çağrısı pahasına geniş bir bellek bölgesinde haritaya ve ona verimli bir şekilde erişebilirsiniz.
1 Bu az ya da çok, dosyanın başlaması için tam olarak önbelleğe alınmadığı, ancak işletim sisteminin önceden okunmasını sağlayacak kadar iyi olduğu durumu da içerir (yani, sayfa genellikle istiyor). Bununla birlikte, bu, ince bir konudur, çünkü ileri okuma çalışma şekilleri mmap
ve read
çağrılar arasında genellikle oldukça farklıdır ve 2'de açıklandığı gibi "tavsiye" çağrıları ile daha da ayarlanabilir .
2 ... dosya halinde çünkü değil önbelleğe, davranışların tamamen erişim deseni temel donanım için ne kadar sempatik dahil IO kaygıları, hakim olacak - ve böyle bir erişim sağlanması olmalıdır tüm çaba olarak sempatik gibidir örneğin, madvise
veya fadvise
çağrıları kullanarak (ve erişim kalıplarını iyileştirmek için ne tür uygulama düzeyi değişiklikleri yaparsanız).
3 Örneğin mmap
100 MB gibi daha küçük boyutlu pencerelere sırayla girerek bunun üstesinden gelebilirsiniz .
4 Aslında, MAP_POPULATE
yaklaşım, çekirdeğin hatalı kullanıldığından, muhtemelen kullanılmayandan biraz daha hızlı olduğu (en azından bir donanım / işletim sistemi kombinasyonu) olduğu anlaşılmaktadır - bu nedenle gerçek küçük hata sayısı 16 kat azaltılmıştır. ya da öylesine.
mmap()
, sistem çağrılarını kullanmaktan 2-6 kat daha hızlıdır, örnread()
.