Bellek sayfalarını işletim sistemine döndürme

Uygulamanızın kodundaki bir nesneyi serbest bırakmak veya geçici bir akışı kapatmak, fiziksel RAM'i her zaman işletim sistemine döndürmez. Özel yerel ayırıcılar, çöp toplayıcılı çalışma zamanları ve açık adresli karma tablolar, içlerindeki mantıksal veriler kaybolduktan uzun süre sonra fiziksel sayfalar tarafından desteklenmeye devam eden sanal bellek alanlarını sık sık korur. Benzer şekilde, büyük öğe arşivlerini okumak veya bellek eşlemek, uygulamanızın bellek kontrol grubuna (memcg) bağlı kalan çekirdek sayfa önbelleğini doldurur.

Bu sayfada, sanal adres rezervasyonlarını ve dosya tanımlayıcıları olduğu gibi korurken kullanılmayan anonim ve dosya destekli sayfaları işletim sistemine döndürmek için madvise ve posix_fadvise sistem çağrılarının nasıl kullanılacağı ve bu işlemin adb shell ile Perfetto üzerindeki etkisinin nasıl doğrulanacağı açıklanmaktadır.

Mantıksal ayırma kaldırma işlemi neden fiziksel RAM'i korur?

Linux çekirdeği, fiziksel belleği donanım sayfalarının ayrıntı düzeyinde izler. Bu sayfalar, Android cihazlarda 4 KB veya 16 KB'tır. Bir işlem, mmap ile sanal adres alanının bir bölgesini ayırdığında çekirdek, fiziksel RAM tahsis etmeden bir sanal bellek alanı (VMA) oluşturur. Çekirdek, fiziksel bir sayfayı yalnızca işlem o sayfaya ilk kez dokunduğunda tahsis eder ve ücretlendirir. Anonim bellek için ilk okuma, paylaşılan bir sıfır sayfasını eşler. Bu nedenle, yalnızca yazma işlemi RAM'i tahsis eder.

Bir sayfa RAM'e yüklendikten sonra çekirdek, bu sayfadaki baytların uygulamanızın çalışma zamanında hâlâ canlı nesneleri temsil edip etmediğini bilmez. Uygulamanız fiziksel sayfaları kullanmayı bıraktıktan sonra sayfaların yerleşik kalmasına neden olan iki yaygın model vardır:

  • Anonim alanlar ve havuzlar: Özel yerel ayırıcılar (ör. slab, havuz veya çerçeve alanı ayırıcıları), taşınmayan yığın alanlarına sahip yönetilen çalışma zamanları ve büyük karma tablolar, mmap kullanarak MAP_PRIVATE | MAP_ANONYMOUS ile bitişik sanal adres aralıklarını ayırır. Uygulamanız bir arenayı sıfırladığında, bir bloğu özel bir havuza geri bıraktığında veya meta verilerini sıfırlayarak bir karma tabloyu temizlediğinde temel sayfalar kirli kalır. Çekirdeğe açık bir ipucu verilmediği sürece bu sayfalar, işleminizin anonim yerleşik küme boyutuna (RSS) ve orantılı küme boyutuna (PSS) ve memory.current cgroup ücretine dahil edilmeye devam eder. Bellek baskısı altında çekirdek, bunları ZRAM'e sıkıştırır ve burada memory.swap.current için ücretlendirilen takas alanını kaplamaya devam ederler.
  • Öğe akışından dosya destekli sayfa önbelleği: Uygulamanız read, pread veya dosya destekli mmap kullanarak büyük öğe paketlerini, seviye arşivlerini, medya kapsayıcılarını ya da makine öğrenimi modellerini okuduğunda çekirdek, okunan her sayfayı sayfa önbelleğinde önbelleğe alır ve bu sayfaları işleminizin cgroup'unun memory.stat dosyasındaki file sayacına ekler. Uygulamanız bu öğeyi sıkıştırılmamış hale getiriyor veya anonim bir çalışma zamanı arabelleğine kopyalıyorsa ya da GPU'ya yüklüyorsa ham dosya sayfalarını sayfa önbelleğinde tutmak, öğeyi RAM'de kopyalar.
Sanal aralık ve dosya tanımlayıcı bozulmadan kalırken madvise ile yayınlanan kirli arena sayfaları ve posix_fadvise ile çıkarılan önbelleğe alınmış öğe sayfaları.
Şekil 1. Sanal arenalardan ve sayfa önbelleğinden fiziksel sayfaları geri alma.

Anonim sayfaları yayınlama

madvise sistem çağrısı, çekirdeğe işleminizin sanal adres aralığını nasıl kullanmayı beklediği hakkında tavsiye verir. Özel anonim eşlemeler (MAP_PRIVATE | MAP_ANONYMOUS) için MADV_DONTNEED parametresinin iletilmesi, çekirdeğe fiziksel sayfaları ve belirtilen sayfaya göre hizalanmış aralığı destekleyen tüm takas edilmiş ZRAM girişlerini hemen silmesini söyler.

madvise ile MADV_DONTNEED iadeden sonra:

  • Sanal adres aralığı, işleminizin adres alanında ayrılmış ve geçerli kalır (VmSize değişmez). Bu nedenle, işaretçiler ve alan sınırları munmap veya mmap çağrılmadan olduğu gibi kalır.
  • Fiziksel sayfaların eşlemesi hemen kaldırılır ve serbest bırakılır. Böylece RssAnon, Pss, memory.stat içindeki anon sayacı ve ZRAM'deki tüm sayfalar için memory.swap.current azaltılır.
  • Bu aralıktaki bir adrese yapılan sonraki okuma veya yazma işlemleri, isteğe bağlı olarak sıfırla doldurulmuş yeni bir sayfada hata verir.
Tavsiye işareti Hedef eşleme Android'de çekirdek davranışı
MADV_DONTNEED Gizli anonim (MAP_PRIVATE | MAP_ANONYMOUS) Fiziksel sayfaları ve ZRAM takas alanlarını hemen siler. Sonraki erişimlerde sıfırla doldurulmuş sayfalar hata verir. RssAnon ve memory.current için deterministik düşüş.
MADV_DONTNEED Dosya destekli (MAP_PRIVATE veya MAP_SHARED) Sayfaların işlem sayfası tablolarıyla eşlemesini kaldırır (RssFile bırakma). MAP_PRIVATE eşlemesindeki özel yazma sırasında kopyalama sayfaları silinir. Bu nedenle, değişiklikler kaybolur ve bir sonraki erişimde dosya yeniden okunur. Değiştirilmiş MAP_SHARED sayfalar daha sonra depolama alanına geri yazılır. Temiz sayfalar, çıkarılana kadar sayfa önbelleğinde kalır.
MADV_FREE Gizli anonim (MAP_PRIVATE | MAP_ANONYMOUS) Sayfaları geç yeniden kullanılabilir olarak işaretler. Çekirdek, bellek baskısı yoksa RssAnon içindeki sayfaları korur ve yalnızca genel veya memcg geri alma işlemi çalıştırıldığında geri alır.
MADV_WILLNEED Dosya destekli veya takas edilmiş anonim Dosya destekli eşlemeler için eşzamansız önceden okumayı başlatır veya takas edilen anonim sayfaları ZRAM'den takas önbelleğine önceden okur.

İç aralıkları çalışma zamanı sayfa boyutuna hizalama

madvise, tüm sayfalarda çalışır. Sayfaya göre hizalanmış bir başlangıç adresi gerektirir ve uzunluğu bir sonraki sayfa sınırına yuvarlar. Komşu baytların hâlâ canlı nesneler barındırdığı paylaşılan bir alanda bir aralığı serbest bırakırsanız aralığı dışa doğru yuvarlamak, sınır sayfalarını paylaşan canlı tahsisleri sıfırlayabilir.

Hem 4 KB hem de 16 KB sayfa boyutuna sahip cihazları desteklemek için çalışma zamanı sayfa boyutunu her zaman sysconf(_SC_PAGESIZE) ile sorgulayın ve iç başlangıç adresini bir sonraki sayfa sınırına, iç bitiş adresini ise önceki sayfa sınırına yuvarlayın. Sayfa boyutundan bağımsız ayırıcılar hakkında daha fazla bilgi için Özel bellek ayırıcıları optimize etme başlıklı makaleyi inceleyin.

#include <sys/mman.h>
#include <unistd.h>
#include <cstdint>

// Reclaims physical pages strictly inside [region_start, region_start + length)
// without disturbing live objects on partially occupied boundary pages.
void ReleaseUnusedArenaSpan(void* region_start, size_t length) {
  const uintptr_t page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
  const uintptr_t page_mask = ~(page_size - 1);

  const uintptr_t raw_start = reinterpret_cast<uintptr_t>(region_start);
  const uintptr_t raw_end = raw_start + length;

  // Round start UP and end DOWN so only fully unused pages are discarded.
  const uintptr_t aligned_start = (raw_start + page_size - 1) & page_mask;
  const uintptr_t aligned_end = raw_end & page_mask;

  if (aligned_end > aligned_start) {
    madvise(reinterpret_cast<void*>(aligned_start),
            aligned_end - aligned_start,
            MADV_DONTNEED);
  }
}

mmap ile özel anonim eşlemeler oluştururken PR_SET_VMA_ANON_NAME kullanarak VMA'yı prctl ile etiketleyin. Böylece, showmap ve /proc/<pid>/smaps içinde barındırma konumunu ada göre takip edebilirsiniz. dumpsys meminfo, Unknown altındaki özel olarak adlandırılmış anonim bölgeleri sayar. Bazı eski çekirdekler, adı kopyalamak yerine adın işaretçisini saklar. Bu nedenle, eşlemenin kullanım ömrü boyunca geçerli kalacak bir dize (ör. dize sabiti) iletin:

#include <sys/mman.h>
#include <sys/prctl.h>

#ifndef PR_SET_VMA
#define PR_SET_VMA 0x53564d41
#define PR_SET_VMA_ANON_NAME 0
#endif

void* ReserveNamedArena(size_t capacity_bytes, const char* arena_name) {
  void* ptr = mmap(nullptr, capacity_bytes, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (ptr != MAP_FAILED) {
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME,
          reinterpret_cast<unsigned long>(ptr), capacity_bytes,
          reinterpret_cast<unsigned long>(arena_name));
  }
  return ptr;
}

Dosya önbelleği sayfalarını çıkarma

Uygulamanız bir dosya tanımlayıcıdan veri okuduğunda çekirdek, gelecekteki okuma işlemlerini öngörerek dosyanın sayfalarını sayfa önbelleğinde önbelleğe alır. Bu, küçük yapılandırma dosyalarına veya paylaşılan kitaplıklara tekrarlanan erişimleri hızlandırsa da büyük öğe arşivleri, doku paketleri veya bir kez ayrıştırılıp başka bir yerde kodu çözülmüş biçimde depolanan ses ve video parçaları akışla aktarılırken dezavantaj oluşturur.

posix_fadvise sistem çağrısı, uygulamanızın açık bir dosya tanımlayıcıda beklenen dosya erişim deseni hakkında çekirdeği bilgilendirmesini sağlar:

  • POSIX_FADV_DONTNEED: Çekirdeğe, belirtilen bayt aralığına yakın gelecekte erişilmeyeceğini bildirir. Çekirdek, bu sayfaya göre hizalanmış aralıktaki temiz, eşlenmemiş önbelleğe alınmış sayfaları sayfa önbelleğinden hemen çıkarır. Böylece memory.stat içindeki file sayacı ve cgroup'unuzun memory.current ücreti azalır.
  • POSIX_FADV_SEQUENTIAL: Dosya tanımlayıcı için çekirdeğin önceden okuma penceresini iki katına çıkararak büyük bir arşivin baştan sona akışında işleme hızını artırır. Sayfa önbelleği ayak izinin küçük bir kayan pencereyle sınırlı kalması için bunu, kullandığınız sırada sondaki parçalarda POSIX_FADV_DONTNEED ile eşleştirin.
  • POSIX_FADV_RANDOMDosya tanımlayıcıda tahmini çekirdek önceden okuma özelliğini devre dışı bırakır. Bu seçeneği, büyük bir arşiv veya veritabanı dosyasında bayt ofsetine göre seyrek, bitişik olmayan aramalar yaparken kullanın. Böylece her küçük okuma, bitişik kullanılmayan sayfaları sayfa önbelleğine çekmez. Bellekle eşlenen dosyalar için eşlemede MADV_RANDOM ile madvise yöntemini çağırın.

Aşağıdaki örnekte, büyük bir öğe arşivi parçalar halinde aktarılır. Bu sayede, sıralı önceden okuma etkinleştirilir ve işlenen her sayfa hizalı aralık, sayfa önbelleğinden çıkarılır:

#include <fcntl.h>
#include <unistd.h>
#include <cstdint>
#include <vector>

void StreamAssetArchive(int fd, size_t total_size) {
  const size_t page_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
  constexpr size_t kChunkSize = 4 * 1024 * 1024;  // 4 MB streaming window
  std::vector<uint8_t> buffer(kChunkSize);

  // Hint sequential access so the kernel prefetches upcoming chunks.
  posix_fadvise(fd, 0, total_size, POSIX_FADV_SEQUENTIAL);

  off_t offset = 0;
  while (offset < static_cast<off_t>(total_size)) {
    ssize_t bytes_read = pread(fd, buffer.data(), buffer.size(), offset);
    if (bytes_read <= 0) break;

    // Decode or upload buffer[0 .. bytes_read) into runtime structures here.

    // Evict full pages that have been completely consumed.
    const size_t aligned_len =
        (static_cast<size_t>(bytes_read) / page_size) * page_size;
    if (aligned_len > 0) {
      posix_fadvise(fd, offset, static_cast<off_t>(aligned_len),
                    POSIX_FADV_DONTNEED);
    }
    offset += bytes_read;
  }
}

Dosya destekli mmap bölgelerini geri alma

Uygulamanız büyük bir öğe dosyasını pread ile okumak yerine mmap ile belleğe eşliyorsa posix_fadvise işlevini yalnızca POSIX_FADV_DONTNEED ile çağırmak, işleminizin sayfa tablolarıyla eşlenmiş sayfaları kaldırmaz. Tüm VMA'nın eşlemesini kaldırmadan salt okunur bir dosya eşlemesinin tüketilen aralığını bırakmak için:

  1. Eşlenen sanal adres aralığında madvise(addr, length, MADV_DONTNEED) işlevini çağırarak bu sayfaların işleminizin sayfa tablolarıyla eşlemesini kaldırın (RssFile düşer).
  2. İlgili dosya ofsetinde posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED) işlevini çağırın. Böylece çekirdek, eşlenmemiş temiz sayfaları da sayfa önbelleğinden çıkarır (memory.stat düşüşte file).

Sayfa önerileriyle ilgili kurallara uyma

madvise ve posix_fadvise öğelerini ayırıcılara, öğe yükleyicilere ve yaşam döngüsü işleyicilere entegre ederken şu uygulamalardan yararlanın:

  • Çağrıları kaba bölgelere toplu olarak işleme: Her madvise çağrısı, işleminizin bellek haritası kilidini (mmap_lock) alır, sayfa tablolarını tarar ve diğer CPU çekirdeklerindeki eşleşen çeviri arama arabelleği (TLB) girişlerini geçersiz kılar (Arm'da yayın TLB bakım talimatları ve x86'da işlemciler arası kesintilerle). Her küçük ayırma kaldırma işleminde madvise yöntemini çağırmayın. Boş alanları birleştirin ve çekirdeğe 64 KB ila 1 MB veya daha büyük parçalar halinde tavsiyede bulunun ya da kare sınırları ve arka plan geçişleri (onStop ve TRIM_MEMORY_UI_HIDDEN) sırasında boş alanları tarayın.
  • Karma tablolarında ve havuzlarda boş kapasiteyi geri alma: Sahne geçişi veya veri senkronizasyonundan sonra büyük bir karma tablosu, nesne havuzu ya da hazırlama arabelleği temizlendiğinde, destekleyici tahsisini küçültün veya temizlenen destekleyici dizi genelinde MADV_DONTNEED yayınlayın. Mezar taşlarıyla ayrılmış 32 MB'lık açık adresli bir tablo tutmak, 32 MB'lık fiziksel sayfaların tamamının işleminizden ücretlendirilmesini sağlar.
  • Kirli dosya verilerini çıkarmadan önce temizleyin: POSIX_FADV_DONTNEED yalnızca temiz sayfa önbelleği sayfalarını çıkarır. Uygulamanız hemen geri okumayacağı büyük bir geçici dosya veya önbellek dosyası yazıyorsa posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) işleminden önce fdatasync yöntemini çağırın. Böylece kirli sayfalar depolama alanına yazılır ve ardından RAM'den silinir.
  • Sık kullanılan paylaşılan kodları veya öğeleri kaldırmayın: DEX, ODEX, VDEX, paylaşılan kitaplıklar (.so) veya tekrar tekrar erişilen kullanıcı arayüzü öğelerinde POSIX_FADV_DONTNEED işlevini çağırmaktan kaçının. Etkin kod veya öğe sayfalarının çıkarılması, iş parçacıklarını durduran ve G/Ç baskısını artıran eşzamanlı depolama hatalarına neden olur. Bu durum, baskı durma bilgileri (PSI) tarafından raporlanır.

Kabukta sayfa geri kazanımını doğrulama

showmap ve uygulamanızın cgroup v2 hesap dosyalarını kullanarak madvise ve posix_fadvise'nin fiziksel RAM'i geri aldığını doğrulayın.

Showmap ile sanal ve fiziksel boyutu inceleme

Anonim bir arenayı PR_SET_VMA_ANON_NAME ile adlandırdığınızda showmap, hem sanal rezervasyonunu (virtual size) hem de yerleşik fiziksel sayfalarını (RSS ve PSS) gösterir. Aşağıdaki örnek çıkış, arenasını memorylab-arena olarak adlandıran AOSP'deki MemoryLab örnek uygulamasından alınmıştır. com.example.app ve memorylab-arena ifadelerini paket adınız ve arena adınızla değiştirin. Dokunulan 64 MB'lık bir arenada MADV_DONTNEED ile madvise işlevini çağırmadan önce şunu çalıştırın:

adb shell "showmap \$(pidof com.example.app) | grep -E '^ +virtual|^ +size|memorylab-arena' | head -3"
 virtual                     shared   shared  private  private                   Anon      Shmem     File      Shared   Private
    size      RSS      PSS    clean    dirty    clean    dirty     swap  swapPSS HugePages PmdMapped PmdMapped Hugetlb  Hugetlb    Locked    # object
   65536    65536    65536        0        0        0    65536        0        0         0         0         0        0        0        0    1 [anon:memorylab-arena]

madvise(arena_ptr, 64 * 1024 * 1024, MADV_DONTNEED) işlevini çağırdıktan sonra showmap işlevini tekrar çalıştırın:

 virtual                     shared   shared  private  private                   Anon      Shmem     File      Shared   Private
    size      RSS      PSS    clean    dirty    clean    dirty     swap  swapPSS HugePages PmdMapped PmdMapped Hugetlb  Hugetlb    Locked    # object
   65536        0        0        0        0        0        0        0        0         0         0         0        0        0        0    1 [anon:memorylab-arena]

virtual size, 65.536 KB (64 MB) olarak kalır ve sanal adres aralığının hâlâ ayrılmış olduğunu onaylar. RSS, PSS ve private dirty ise 0 KB'a düşer.

cgroup memory.current ve memory.stat'ı inceleme

Hem anonim sayfa geri kazanımını hem de eşlenmemiş sayfa önbelleği tahliyesini (pread ile okunursa işlem sayfa tablolarında görünmez) doğrulamak için işlemin cgroup v2 dizinini inceleyin. Android'de her uygulama işleminin kendi cgroup'u vardır (cihaza bağlı olarak /sys/fs/cgroup/apps/uid_<UID>/pid_<PID>/ gibi). Aşağıdaki komut, /proc/<PID>/cgroup dosyasının 0:: satırından dizini bulur ve sayaçları yazdırır:

adb shell '
  PID=$(pidof com.example.app)
  CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
  echo "memory.current: $(cat $CG/memory.current)"
  echo "memory.swap.current: $(cat $CG/memory.swap.current)"
  grep -E "^(anon|file) " $CG/memory.stat
'

64 MB'lık anonim bir alanı serbest bırakmadan ve 128 MB'lık bellekle eşlenmiş bir öğe dosyasını sayfa önbelleğinden çıkarmadan önce:

memory.current: 242962432
memory.swap.current: 16633856
anon 100671488
file 140009472

Arena ve öğe dosyası eşlemesinde MADV_DONTNEED ile madvise işlevini, ardından öğe dosya tanımlayıcısında POSIX_FADV_DONTNEED ile posix_fadvise işlevini çağırdıktan hemen sonra:

memory.current: 41766912
memory.swap.current: 16502784
anon 33722368
file 5914624

anon 64 MB, file ise 128 MB düşer. Böylece dosya tanımlayıcı kapatılmadan veya alanın sanal adres alanının eşlemesi kaldırılmadan memory.current yaklaşık 192 MB azalır. memory.swap.current neredeyse hiç değişmez çünkü arena sayfalarının neredeyse hiçbiri ZRAM'e taşınmamıştır. Değerleriniz, cihaza ve çekirdeğin dosyanın ne kadarını kendi başına çıkardığına bağlıdır. Çekirdeğin cgroup başına belleği nasıl ücretlendirdiği ve geri aldığı hakkında daha fazla bilgi için Bellek geri alma: çıkarma ve takas bölümüne bakın.

Perfetto'da sayfa geri kazanımını analiz etme

Perfetto, madvise ve posix_fadvise çağrılarını birleştirilmiş bir zaman çizelgesinde çekirdek sayfa tablosu ve sayfa önbelleği sayaçlarıyla ilişkilendirmenize olanak tanır.

İz kaydetme

Periyodik işlem sayaçları için linux.process_stats ve anlık RSS ve sayfa önbelleği etkinlikleri için linux.ftrace ile bir izleme yakalayın, ardından bunu bilgisayarınıza çekin:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/page_advice.perfetto-trace <<EOF
buffers: {
    size_kb: 65536
    fill_policy: RING_BUFFER
}
data_sources: {
    config {
        name: "linux.process_stats"
        process_stats_config {
            scan_all_processes_on_start: true
            proc_stats_poll_ms: 100
        }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config {
            ftrace_events: "kmem/rss_stat"
            ftrace_events: "filemap/mm_filemap_add_to_page_cache"
            ftrace_events: "filemap/mm_filemap_delete_from_page_cache"
        }
    }
}
duration_ms: 20000
EOF
adb pull /data/misc/perfetto-traces/page_advice.perfetto-trace

Perfetto kullanıcı arayüzünde izleri inceleme

İzlemeyi Perfetto kullanıcı arayüzünde açın, uygulamanızın işlem grubunu genişletin ve aşağıdaki sayaç parçalarını sabitleyerek zaman çizelgesinin en üstünde kalmalarını sağlayın:

  • mem.virt: MADV_DONTNEED çağrı boyunca sabit kalır ve sanal adres rezervasyonunun (mmap) değişmediğini doğrular.
  • mem.rss.anon: Uygulamanızın kirli anonim arenalarda veya yığın bölgelerinde MADV_DONTNEED ile madvise çağrısı yaptığı tam zaman damgasında keskin bir şekilde düşer.
  • mem.rss.file: Uygulamanız, dosya destekli bir mmap bölgesinde MADV_DONTNEED ile madvise işlevini çağırdığında ve POSIX_FADV_DONTNEED ile posix_fadvise, temiz sayfaları sayfa önbelleğinden çıkardığında bir dizi mm_filemap_delete_from_page_cache ftrace etkinliğiyle birlikte azalır.
  • mem.swap: MADV_DONTNEED, daha önce ZRAM'e sıkıştırılmış anonim sayfaları sildiğinde düşer.
Tek bir işlem için Perfetto sayaç izleri. Bir arena ve öğe dosyası doldurulduğunda mem.rss.anon ve mem.rss.file artar, madvise ve posix_fadvise'dan sonra tekrar azalır. mem.swap ve mem.virt ise sabit kalır.
Şekil 2. Sayfa önerisi aramalarından sonra RSS düşüyor.

Şekil 2'de, MemoryLab örnek uygulaması için bu izler gösterilmektedir. Aynı görünümü incelemek için Perfetto kullanıcı arayüzünde örnek izi açın.

PerfettoSQL ile en yüksek ve nihai bellek kullanımını sorgulama

Hedef işleminiz için mem.virt, mem.rss.anon, mem.rss.file ve mem.swap değerlerinin en yüksek ve son değerlerini karşılaştırmak üzere Perfetto kullanıcı arayüzünde (Sorgu (SQL) sekmesi) aşağıdaki PerfettoSQL sorgusunu çalıştırın:

WITH counters AS (
  SELECT t.name AS counter_name, c.ts, c.value
  FROM counter c
  JOIN process_counter_track t ON c.track_id = t.id
  JOIN process p USING (upid)
  WHERE p.name = 'com.example.app'
    AND t.name IN ('mem.virt', 'mem.rss.anon', 'mem.rss.file', 'mem.swap')
)
SELECT
  counter_name,
  CAST(MAX(value) / 1048576 AS INT) AS peak_mb,
  CAST((
    SELECT value FROM counters AS last
    WHERE last.counter_name = counters.counter_name
    ORDER BY ts DESC LIMIT 1
  ) / 1048576 AS INT) AS final_mb
FROM counters
GROUP BY counter_name
ORDER BY counter_name;

Şekil 2'deki iz için örnek çıkış:

counter_name  peak_mb  final_mb
mem.rss.anon      111        48
mem.rss.file      212        84
mem.swap           36        36
mem.virt        13952     13952

mem.rss.anon, zirve noktasından 63 MB, mem.rss.file ise 128 MB düşerken mem.virt, sanal rezervasyonlar değişmediği için zirve noktasında biter.

Kaldırılan sayfaları dosyalara çözümleme

mm_filemap_add_to_page_cache ve mm_filemap_delete_from_page_cache etkinlikleri, dosyaları yol yerine inode numarasına (i_ino) göre tanımlar. Çıkarılan sayfaların önerdiğiniz dosyaya ait olduğunu doğrulamak için inode başına çıkarma sayısını hesaplayın:

SELECT
  EXTRACT_ARG(arg_set_id, 'i_ino') AS inode,
  COUNT(*) AS evicted_pages
FROM ftrace_event
WHERE name = 'mm_filemap_delete_from_page_cache'
GROUP BY inode
ORDER BY evicted_pages DESC
LIMIT 5;

Şekil 2'deki iz için örnek çıkış:

inode     evicted_pages
9761              35781
76792192            767
126                 668
0                   612
11587712            514

İzi kaydeden cihaz hâlâ bağlıyken her inode'u bir yola çözümleyin. Inode numaraları yalnızca bir dosya sisteminde benzersizdir. Bu nedenle, aramayı uygulamanızın verilerini barındıran dosya sistemiyle sınırlayın:

INODE=<inode from the query>
adb shell "find /data -xdev -inum $INODE 2>/dev/null"

9761 inode'u için örnek çıkış:

/data/user/0/com.android.memorylab/files/asset_pack.bin
/data/data/com.android.memorylab/files/asset_pack.bin

/data/user/0 ve /data/data aynı uygulama verileri dizininin iki adı olduğundan her iki yol da aynı dosyayı ifade eder. Öğe dosyası için çıkarma sayısı (35.781 sayfa), dosyanın kendisinden (4 KB'ta 32.768 sayfa) daha büyük. Bunun nedeni,uygulama dosyayı tekrar okuyup posix_fadvise işlevini çağırmadan önce kswapd işlevinin, bellek baskısı altında dosyanın bir kısmını zaten çıkarmış olmasıdır.

Aynı anda birçok inode'u çözen bir komut dosyası için Önce inode'lardaki, ardından dosyalardaki hataları çözme bölümüne bakın.


← kswapd ve lmkd etkileşimi | ↑ Yukarı | Yalın arka plan işlemi →