Yazdığınız kod da bir bellek kullanımı biçimidir. Uygulamanızdaki her sınıf, yöntem ve dize sabiti yürütüldüğünde RAM'e yüklenmelidir. Uygulamanızın kod tabanı ne kadar büyükse var olmak için o kadar fazla bellek tüketir.
Dosya destekli bellek ve talep üzerine sayfalama
Android, .apk (ör. .oat veya .so dosyaları) içindeki yürütülebilir kodu mmap kullanarak yükler. Bu, kodun dosya destekli olduğu anlamına gelir.
Android'in talep üzerine sayfalama kullandığını belirtmek önemlidir. Uygulamanız başlatıldığında çekirdek, APK'nın tamamını hemen RAM'e yüklemez. Bunun yerine, yalnızca dosyayı işlemin sanal adres alanına eşler. Uygulamanız yürütülürken ve CPU yeni bir işleve geçtiğinde "sayfa hatası" tetiklenir. Çekirdek, iş parçacığını duraklatır, depolama alanından fiziksel RAM'e 4 KB'lık kodu okur ve yürütmeye devam eder.

Bu, paketlediğiniz ancak hiçbir zaman yürütmediğiniz kodun, kod sayfaları için fiziksel bellek kullanmadığı anlamına gelir. Ancak kullanılmayan kitaplıklar yine de genel APK boyutunu artırır ve sistemin dahili meta verileri (ör. DEX dizinleri ve sınıf tanımlayıcıları) tarafından kullanılan belleği önemli ölçüde artırabilir. Bu meta verilerin, kodun varlığını bilmek için bile okunması gerekir. Ayrıca, birçok kitaplık statik başlatıcılar içerir veya uygulama başlatılırken bağımlılık ekleme çerçeveleri tarafından dokunulur. Bu durum, kitaplıkların yine de RAM'e sayfalara ayrılmasına neden olur.
Sayfa kaldırma ve yavaşlamalar
Dosya destekli bellek her zaman depolama alanından yeniden okunabildiğinden çekirdek, bu sayfaları "temiz" olarak kabul eder. Sistem bellek baskısı yaşadığında çekirdek, diğer öğelere yer açmak için bu temiz kod sayfalarını RAM'den çıkarır (bırakır).
Uygulamanızın daha sonra bu kodu tekrar yürütmesi gerekirse CPU arızalanır ve çekirdeğin sayfayı depolama alanından yeniden okuması gerekir. Uygulamanızda ne kadar çok kod varsa kodunun kaldırılmasına karşı o kadar savunmasız olur. Kullanıcılar diğer uygulamaları kullandıktan sonra şişirilmiş uygulamanıza döndüğünde, CPU sürekli olarak kodun depolamadan geri çağrılmasını beklediği için rastgele duraklamalar ve yavaşlamalar yaşar.
Sayfa Hatasının Maliyeti: Cihazın depolama hızı (UFS ve eMMC) ve çekirdek durumuna göre büyük ölçüde değişse de büyük bir sayfa hatası (depolamadan 4 KB okuma) 0, 5 ms ile 5 ms arasında bir maliyete neden olabilir. Başlangıç yolunuz 500 farklı optimize edilmemiş kod sayfasına dokunuyorsa uygulamanızın başlangıç süresine kolayca birkaç yüz milisaniye saf G/Ç gecikmesi ekleyebilirsiniz.
Compiler Explorer ile kod boyutunu keşfetme
Java veya Kotlin kodunuzun yerel makine koduna (ve dolayısıyla bellek baytlarına) nasıl çevrildiği konusunda fikir edinmek için Compiler Explorer'ı kullanabilirsiniz.
Android desteği doğrudan Godbolt'a yerleştirilmiştir. Bu araç, Android araç zincirinin (D8, R8 ve dex2oat) farklı bölümlerinin kaynak kodunuzu nasıl dönüştürdüğünü görmenizi sağlar.
Android ile Compiler Explorer'ı kullanma
- godbolt.org adresine gidin.
- Dil açılır listesinden (sol üst) Android Java veya Android Kotlin'i seçin.
- Derleyici açılır listesinde (kod bölmesinin sağ üst kısmı) farklı araçlar arasından seçim yapabilirsiniz:
d8: Dalvik bayt kodunu (.dex) gösterir. Bu, orijinal kodunuza en yakın gösterimdir ve okunması daha kolaydır.r8: R8 optimize edicinin, bayt kodunuzu nasıl küçülttüğünü ve optimize ettiğini gösterir.dex2oat: Cihazda gerçekten yürütülen nihai ARM64 makine dilini gösterir. Burada gerçek bellek etkisini (talimat başına 4 bayt) görebilirsiniz.dex2oatfarklı ISA'ları hedefleyebilir ancak ARM64, cep telefonlarında en yaygın olanıdır.
- Kaynak<>Çıkış Vurgulama: Bir kod satırının üzerine gelindiğinde ilgili bayt kodu veya makine kodu talimatları vurgulanır. Böylece belirli ifadelerin etkisini izlemek kolaylaşır.
- Optimizasyon ardışık düzeni: Sökme görünümünde Yeni ekle...'yi tıklayabilirsiniz. -> Fırsat Aşaması. Bu, derleyicinin gerçekleştirdiği dahili adımları görmenize olanak tanır. Dahili Temsil'in (IR) nihai ARM64 makine diline dönüştürülmeden önce her aşamada (ör. "Inliner (önce)" ve "Inliner (sonra)" adımları arasında) nasıl dönüştürüldüğünü inceleyebilirsiniz.

Bu neden önemli?
ARM64 ISA'yı hedefleyen dex2oat çıkışında gördüğünüz her talimat, uygulamanızın yürütülebilir (.odex veya .oat) dosyasında 4 bayt yer kaplar.
Farklı dil özelliklerinden yararlanan bir kod girmeyi deneyin ve derleyicinin çıkışını inceleyin:
- Dizi erişimi ve liste yineleyicileri:
int[]üzerinde basit bir dizi döngüsü, yaklaşık 10 talimata (~40 bayt) derlenebilir.Listüzerinde foreach döngüsü örtülü olarakIteratorkullanır. Bu durum, ek yöntem çağrıları (hasNext(),next()) ve yineleyici nesnenin kendisinin ayrılması nedeniyle 30-40 talimata (~160 bayt) yol açabilir.- R8 Optimizasyonu: Doğru koşullar altında (ör.
ListArrayListolduğu kanıtlandığında) R8 optimizasyon aracı, bir foreach döngüsünü basit bir dizinlenmiş döngüye dönüştürebilir. Böylece, yineleyici ek yükünü ortadan kaldırarak hem kod boyutunu hem de çalışma zamanı bellek karmaşıklığını azaltır.
- Sanal Yöntem Çağrıları: Nesnenin sınıfını yüklemeyi,
vtableiçinde yöntemi bulmayı ve ardından dallanmayı içerir. Bu işlem genellikle 4-5 talimat (~20 bayt) sürer. - Doğrudan/Statik Çağrılar: Genellikle tek bir
bl(Bağlantılı Dal) talimatına (4 bayt) çevrilir. - Kotlin Lambdaları: Basit bir işlevsel blok için yüzlerce bayt kod ve meta veri ekleyerek anonim sınıfların tamamını ve ek köprü yöntemlerini oluşturabilir.
Compiler Explorer'ı kullanarak gelişmiş dil özelliklerinin (ör. Kotlin lambda'ları, akış API'leri veya jeneriklerin yoğun kullanımı) uygulamanızın nihai derlenmiş boyutunu nasıl etkilediğini ve R8 gibi optimize edicilerin bazı durumlarda dil soyutlamalarının maliyetini nasıl telafi edebileceğini görebilirsiniz. Bu araç, uygulama tasarlama ve uygulama konusunda bilinçli seçimler yapmanıza yardımcı olabilir.
Genel olarak, uygulamanızın kodundaki karmaşıklık arttıkça bellek kullanımı da artar. Buna karşılık, daha basit kod veya R8 tarafından basitleştirilmiş kod, depolama ve RAM'deki CPU talimatları ve baytlar olarak daha küçük bir gösterime neden olur.
meminfo ve showmap ile kod etkisini ölçme
Uygulamanızın kodunun ne kadar bellek kullandığını görmek için standart Android bellek araçlarını kullanabilirsiniz.
dumpsys meminfo
adb shell dumpsys meminfo <package> komutunu çalıştırdığınızda, Uygulama Özeti bölümündeki Kod kategorisinde kodla ilgili belleğe dair genel bir görünüm sunulur:
App Summary
Pss(KB)
------
Java Heap: 3244
Native Heap: 5412
Code: 24512 # <--- Sum of .so, .dex, .oat, .art, etc.
showmap
Daha ayrıntılı bir görünüm için showmap kullanın. Belirli dosyaların belleğe eşlenmediği bölgeleri gösterir.
adb shell showmap $(pidof <package>) | grep -E "\.oat|\.odex|\.dex|\.apk"
Uygulamanızın derlenmiş koduyla ilgili girişleri görürsünüz:
size RSS PSS clean dirty clean dirty swap swapPSS object
------- -------- -------- -------- -------- -------- -------- -------- -------- ----------------
12288 8192 8192 8192 0 0 0 0 0 /data/app/.../base.odex
Ölü kod ve R8
Yürütülen her yöntem bellek kullandığından, gereksiz başlatmalar veya kullanılmayan kitaplıklar içeren "şişirilmiş" bir uygulama, başlangıç performansını ve temel bellek kullanımını ciddi şekilde etkileyebilir.
Bu nedenle R8 (ProGuard) gibi araçlar çok önemlidir. R8, uygulamanızın bayt kodunu analiz eder ve hiçbir zaman çağrılmayan sınıfları veya yöntemleri kaldırır ("kullanılmayan kodu temizleme").
Uygulamalı alıştırma: şişkinliğin maliyeti
Kod boyutunun etkisini göstermek için 300 oluşturulmuş sınıf (her biri 500 yöntemle) içeren bir uygulamanın iki derlemesini karşılaştıran bir deneyi ele alalım:
- CodeBloat (Unoptimized): Oluşturulan tüm sınıfları ve benzersiz dizeleri içeren standart, optimize edilmemiş derleme.
- CodeBloatOptimized: Aynı kaynak kodu, ancak R8 küçültme etkinleştirilerek derlenmiş.
1. Önceden (AOT) derleme
Dosya destekli bellek etkisini en üst düzeye çıkarmak için uygulamaları .oat dosyalarına önceden derlemek üzere cmd package compile aracını kullanacağız.
adb shell cmd package compile -m speed -f com.android.codebloat
adb shell cmd package compile -m speed -f com.android.codebloat.optimized
Bunun sentetik bir örnek olduğunu lütfen unutmayın. Uygulamalar genellikle speed-profile derleme modunu kullanır (aşağıda daha fazla bilgi verilmiştir).
2. Yayınlama ve karşılaştırma
Sistemin kodu depolama alanından okuması gereken gerçekten soğuk bir başlangıç görmek için her uygulamayı başlatmadan önce çekirdeğin sayfa önbelleğini bırakırız. Bu işlem için kök erişimi gerekir.
Optimum hale getirilmemiş uygulamayı başlatın:
adb shell am force-stop com.android.codebloat
# Drop page cache to ensure the start is truly cold
adb shell "echo 3 > /proc/sys/vm/drop_caches"
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5 # Wait for the background thread to load classes
adb shell dumpsys meminfo -s com.android.codebloat
Şimdi de optimize edilmiş uygulama için aynı işlemi yapın:
adb shell am force-stop com.android.codebloat.optimized
# Drop page cache to ensure the start is truly cold
adb shell "echo 3 > /proc/sys/vm/drop_caches"
adb shell am start -W -n com.android.codebloat.optimized/com.android.codebloat.MainActivity
sleep 5
adb shell dumpsys meminfo -s com.android.codebloat.optimized
Sonuçlar
App Summary bölümündeki Kod satırına baktığınızda büyük bir fark görürsünüz:
- Optimize edilmemiş
Code: ~30.000 KB (30 MB) - Optimize edilmiş
Code: ~2.000 KB (2 MB)
R8, bu sınıfların içindeki 500 yöntemin hiçbir zaman gerçekten yararlı bir şey yapmadığını (doSomething() yöntemi yalnızca method0() yöntemini çağırıyor ve sonuçlar yoksayılıyor) belirlediğinden, yapay olarak oluşturulan kodun neredeyse tamamını nihai APK'dan kaldırdı.
3. Etkiyi Perfetto'da görme
Kod şişkinliğinin etkisi, uygulamanın ilk yükleme aşamasında açıkça görülür. Özellikle ana iş parçacığındaki bindApplication dilimini ve madvising ile başlayan iç içe dilimleri arayın. Bu dilimler, sistemin APK'dan ve derlenmiş kodundan (.odex) dosya yüklemeye hazırlandığını gösterir.
Sistem, etkileşimli bir baştan başlatma işleminde mmap() ve madvise() kodunu ve uygulamanın yüklenip çalışması için gerekli olan bu dosyalardaki diğer verileri kullanır. madvising dilimlerindeki "size=" ifadesinden sonraki değer, ne kadar veri yüklenmesi gerektiğini gösterir. Uygulamanın kodunun önceden getirilmesi, uygulamanın başlatılmasını hızlandırmak için yapılır.
Karşılaştırmadan, depolamadan RAM'e yüklenmesi gereken uygulama kodu miktarının şişirilmiş uygulama durumunda çok daha fazla olduğunu görebiliriz. Bu durum, uygulamanın daha yavaş başlatılmasına katkıda bulunan daha uzun sürelerle sonuçlanır. Ayrıca, şişirilmiş uygulamanın başlangıcına ait izde, şişirilmiş uygulamanın tek bir DEX dosyasına sığmadığı için "dökülmeye" zorlandığı ikincil DEX dosyalarını yüklemeye yönelik dilimler (classes2.dex, classes3.dex) gösterilir.
Karşılaştırma (Pixel 10a'da baştan başlatma)
| Metrik | Optimize edilmemiş (CodeBloat) | Optimize edilmiş (CodeBloatOptimized) |
|---|---|---|
base.odex madvise size |
~7,9 MB (2,0 ms) | ~16 KB (0,003 ms) |
base.apk madvise size |
~2,4 MB (2,4 ms) | ~4 KB (0,001 ms) |
classes2.dex madvise size |
~7,3 MB (8,6 ms) | Yok |
classes3.dex madvise size |
~7,3 MB (8,0 ms) | Yok |
Toplam madvising süresi |
~21 ms | ~0,004 ms |
Optimize edilmemiş uygulama yükleme performansı

Optimize edilmiş uygulama yükleme performansı

Kod şişkinliğinin etkisi; uygulamanın boyutuna, kullanıcının cihazının özelliklerine ve sistem yüküne göre değişir.
Yükleme analizi için PerfettoSQL
İzlerinizden bu metrikleri ayıklamak için aşağıdaki sorguları kullanabilirsiniz.
1. Uygulama başlatma süresi
Bu metrik, bir uygulamanın Etkinliği başlatıldığında ilk karenin çizilmesine kadar geçen süreyi gösterir.
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT package, dur, startup_type
FROM android_startups
WHERE package LIKE 'com.android.codebloat%';
Bkz: Farklı uygulama başlatma durumlarını anlama
Bir uygulamanın başlatma süresi, bu kılavuzda ele alınanların dışında birçok faktörden etkilenir.
2. madvising boyutlarını ve sürelerini ayıklama
Bu sorgu, yukarıda gördüğümüz madvising bölümüne odaklanıyor.
INCLUDE PERFETTO MODULE slices.with_context;
SELECT
name,
dur/1e6 AS dur_ms
FROM thread_slice
WHERE process_name LIKE 'com.android.codebloat%'
AND name LIKE 'madvising %';
3. Ana iş parçacığı durumu dökümü (durum başına toplam süre)
Bu sorgu, uygulamanın ana iş parçacığının farklı durumlarda ne kadar zaman geçirdiğini gösterir.
SELECT
p.name AS process_name,
state,
sum(dur)/1e6 AS total_dur_ms
FROM thread_state ts
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.codebloat%'
AND t.is_main_thread = 1
GROUP BY p.name, state;
Uygulamanın başlangıç süresi boyunca yalnızca ana iş parçacığı durumlarına bakmak için sorguyu hassaslaştırabilirsiniz.
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT
p.name AS process_name,
ts.state,
-- Calculate only the duration that falls within the startup window
SUM(
MAX(0,
MIN(ts.ts + ts.dur, s.ts + s.dur) - MAX(ts.ts, s.ts)
)
) / 1e6 AS startup_dur_ms
FROM thread_state ts
JOIN thread t USING (utid)
JOIN process p USING (upid)
-- Join on the package name to align thread states with the correct startup
JOIN android_startups s ON s.package = p.name
WHERE p.name LIKE 'com.android.codebloat%'
AND t.is_main_thread = 1
-- Only select thread states that overlap with the startup interval
AND ts.ts + ts.dur > s.ts
AND ts.ts < s.ts + s.dur
GROUP BY 1, 2
ORDER BY startup_dur_ms DESC;
Bu durum, aşağıdakiler gibi bazı ilginç sorunları ortaya çıkarabilir:
- Çalıştırılabilir (R) durumda yüksek süre geçirilmiş ancak çalışmıyor: Bu, uygulamanın başlatılmasının CPU çekişmesi nedeniyle geciktiğini gösterir. Yani uygulamanın ana iş parçacığı, diğer iş parçacıkları (muhtemelen diğer uygulamalardan) CPU'ları kullandığı için çalışamamıştır.
- Kesintiye uğratılabilir uyku modunda (D) uzun süre kalma: Bu durum genellikle yavaş G/Ç veya uygulamanın başlatılmasını engelleyen bellek baskısı olduğunu gösterir.
- Uyku (S) durumunda uzun süre geçirilmesi: Bu, ana iş parçacığının diğer iş parçacıklarının iş yapmasını beklediği anlamına gelir. Bazen bu, uygulamanın başlangıç yolunda kilit çekişmesi olduğunu gösterir (ör. ana iş parçacığı, uygulamadaki başka bir iş parçacığı tarafından kullanılan özel bir kaynakta engellendi).
4. Maksimum dosya destekli bellek (RSS dosyası)
Bu metrik, uygulamanın başlangıçta ne kadar kod ve veri yüklediğiyle yakından ilişkilidir. Daha "şişkin" bir uygulama burada daha yüksek bir sayıya ulaşarak sistemde bellek baskısına neden olur. Bu tür bir baskı, sistemin tahsis isteklerini karşılamakta zorlanması veya CPU süresini uygulamayı başlatmaya odaklanmaktan alıp diğer işlemlerden bellek geri kazanmaya yönlendirerek uygulamanın başlatılmasını geciktirebilir. Bu durum, başlatılan uygulamanın acil ihtiyaçlarını karşılamak için yapılır.
SELECT
p.name AS process_name,
max(c.value)/1024.0/1024.0 AS max_rss_file_mb
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.codebloat%'
AND t.name = 'mem.rss.file'
GROUP BY p.name;
ART derleme modları ve bellek
Android Çalışma Zamanı (ART), uygulama kodunuzu derleyici filtreleri olarak da bilinen çeşitli modlardan birinde derleyebilir. Seçilen derleyici filtresi, uygulamanızın bellekte kapladığı yeri doğrudan etkiler.
verify: ART yalnızca bayt kodu doğrulaması yapar. AOT derlemesi yapılmaz. Kod, yorumlayıcı aracılığıyla yürütülür veya çalışma zamanında JIT derleyicisi tarafından derlenir.- Bellek Etkisi: En küçük disk boyutu. Yerel kod bellek kullanımı,
JIT Cache(anonim kirli bellek) içine gönderilir.
- Bellek Etkisi: En küçük disk boyutu. Yerel kod bellek kullanımı,
speed: ART, tüm yöntemlerin tam AOT derlemesini gerçekleştirir.- Bellek Etkisi: En büyük
.odexboyutu. Dosya destekli (temiz) bellek kullanımını en üst düzeye çıkarır.
- Bellek Etkisi: En büyük
speed-profile: ART yalnızca JIT profilinde "hot" olarak işaretlenmiş yöntemleri derler.- Bellek Etkisi: Dengeli yaklaşım. Yalnızca en kritik kod AOT ile derlenir.
En yaygın filtre speed-profile'dır. Bu filtre, kullanıcı uygulamaları yüklenirken kullanılır. Bu, pm.dexopt.install ve pm.dexopt.bg-dexopt sistem özelliklerinde yapılandırılır ve genellikle build/make/target/product/runtime_libart.mk içinde ayarlanır.
Bazı sistem uygulamaları speed derlemesini kullanır ve sistem görüntüsü derleme süresi sırasında da derlenir. verify genellikle yalnızca geliştirme kullanım alanlarında kullanılır.
| Kullanım alanı | Tipik derleyici filtresi |
|---|---|
| Geliştirme | verify |
| Sistem görüntüsü | speed |
| Kullanıcı uygulamaları | speed-profile |
Uygulamalı alıştırma: derleme modları ve bellek
Bu filtrelerin belleği nasıl etkilediğini görmek için CodeBloat uygulamasını kullanabiliriz. Bu ölçümleri yeniden oluşturmak için:
- Uygulamayı hedef modda yeniden derlemeye zorlayın.
- Uygulamayı zorla durdurup baştan başlatın.
- Arka plan iş parçacığının sınıflara dokunmayı bitirmesini bekleyin (logcat'i izleyin veya 5 saniye bekleyin).
adb shell dumpsys meminfo com.android.codebloatçalıştırın.
Mod: verify (AOT yok)
adb shell cmd package compile -m verify -f com.android.codebloat
adb shell am force-stop com.android.codebloat
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5
adb shell dumpsys meminfo com.android.codebloat
verify modunda, uygulama özeti şunları gösterir: * Code PSS: ~8.000 KB * Dalvik
Other (JIT): ~25.000 KB
AOT derlemesi yapılmadığından, çalışma zamanı sık kullanılan yöntemleri JIT önbelleğine JIT derlemesiyle eklemelidir. Bu da kirli anonim bellek (Dalvik
Other) olarak görünür.
Mod: speed (tam AOT)
adb shell cmd package compile -m speed -f com.android.codebloat
adb shell am force-stop com.android.codebloat
adb shell am start -W -n com.android.codebloat/.MainActivity
sleep 5
adb shell dumpsys meminfo com.android.codebloat
speed modunda sonuçlar önemli ölçüde değişir: * Kod PSS: ~24.000 KB *
Dalvik Diğer (JIT): ~5.000 KB
Uygulamanın kodu artık .odex dosyasından clean
file-backed memory olarak eşleniyor. Bu, JIT önbelleği üzerindeki baskıyı azaltır ve belleğin kirli RAM olarak "takılıp kalmak" yerine baskı altında çıkarılmaya uygun olmasını sağlar.
Mod: speed-profile (seçici AOT)
Modern uygulamalar, baseline.prof temel profiliyle birlikte gelebilir. ART, bunu yalnızca hızlı ve bellek açısından verimli bir başlangıç için gereken kodu seçerek derlemek amacıyla kullanır.
Bu alıştırmada, uygulamanın başlangıç sınıflarını listeleyen bir temel profil oluşturacağız. Ancak gerçekte derleyici, uygulama mağazası gibi harici kaynaklardan da profiller alabilir ("bulut profilleri"). Bu profiller, geliştiricinin oluşturduğu bir temel profili paketleyip paketlemediğine bakılmaksızın uygulamalar için kitle kaynaklı JIT profilleri sağlayabilir.
Cihaz üzerinde profiller oluşturma ve kullanma
speed-profile özelliğinin etkisini görmek için cihaz üzerinde kendi profilinizi oluşturabilirsiniz:
Sıfırla ve Başlat:
adb shell am force-stop com.android.codebloatEtkileşim: Uygulamayı başlatın ve başlangıç sırasını çalıştırmasına izin verin.
Dump Profili:
adb shell kill -s SIGUSR1 $(pidof com.android.codebloat)(Bu işlem, uygulamanın mevcut profilini diske yazmasını zorlar.)
Yükleme profili:
adb shell cp /data/misc/profiles/cur/0/com.android.codebloat/primary.prof \ /data/misc/profiles/ref/com.android.codebloat/primary.profDerleme:
adb shell cmd package compile -m speed-profile -f com.android.codebloat
Yeniden başlattığınızda bir denge görürsünüz: PSS kodu, speed değerinden daha düşük olur (ör. ~16.000 KB). Bunun nedeni, yalnızca "sık kullanılan" başlatma yöntemlerinin derlenmiş olmasıdır. Geri kalanlar, yalnızca gerçekten kullanılırlarsa yorumlayıcı veya JIT tarafından işlenir.
Şu konulara göz atın:
Derlenmiş kodu ayrıntılı olarak inceleme
ART'nin tam olarak hangi talimatları oluşturduğunu görmek istiyorsanız art/DISASSEMBLY_GUIDE.md bölümüne bakın.
Aşağıdakilerin kullanımıyla ilgili ayrıntılı talimatlar sağlar:
oatdump: Mevcut bir.odexdosyasında ARM64 talimatlarını görmek için.dex2oat: Ayrıntılı hata ayıklama işaretleriyle derlemeyi simüle etmek için.
Egzersiz: Kod satır içi yapma
Derlenmiş kodun beklenmedik şekilde büyümesinin bir nedeni yöntem satır içi genişletmedir. Derleyici, küçük ve sık çağrılan bir yöntemin gövdesini doğrudan çağıranlara kopyalamaya karar verebilir.
CodeBloat uygulamamızda, oluşturulan her sınıftaki doSomething() yöntemi yalnızca method0() yöntemini çağırır. speed modunda derlendiğinde ART'nin Optimizing derleyicisi, method0() işlevini doSomething() işlevinin içine yerleştirir.
Alıştırma: Cihazınızda oatdump kullanarak bunu doğrulayın:
# 1. Find the path to the application's APK and compiled .odex file
adb shell pm path com.android.codebloat
# Output: package:/data/app/~~.../base.apk
adb shell "dumpsys package com.android.codebloat | grep 'location is' | head -n 1"
# Example output: [location is /data/app/~~.../oat/arm64/base.odex]
# 2. Run oatdump (substituting the correct path to base.odex)
adb shell oatdump --oat-file=/data/app/~~.../oat/arm64/base.odex \
--class-filter=com.android.codebloat.GeneratedClass0
Çıkışta doSomething yöntemini bulun. Dize satır içine yerleştirilmişse method0'ı hedefleyen bir bl talimatı yerine, uzun dize sabitini doğrudan doSomething içine yükleme talimatlarını görürsünüz.
Optimizasyonu (CFG) görselleştirme
Derleyicinin yöntemi ne zaman satır içi yapmaya karar verdiğini tam olarak görmek için Kontrol Akışı Grafiği (CFG) oluşturabilirsiniz. Bu, kodun hedef ISA'ya (ör. ARM64) indirgenmesine kadar derleyicinin Ara Gösterimi (IR) üzerindeki her dönüşümle birlikte, optimizasyon işlem hattının her aşamasındaki durumunu gösterir.
Boşaltma işaretleriyle
dex2oatkomutunu çalıştırma: Çıkışı belirli yöntemlerle sınırlamak için--verbose-methodsişaretini kullanın. Aksi takdirde, büyük bir uygulama için.cfgdosyası birkaç gigabayta kadar büyüyebilir.# Substitution of actual paths required: adb shell dex2oat64 --dex-file=/data/app/~~.../base.apk \ --oat-file=/data/local/tmp/dump.odex \ --compiler-filter=speed \ --dump-cfg=/data/local/tmp/codebloat.cfg \ --verbose-methods=doSomethingÇekme ve Görüntüleme:
.cfgdosyasını iş istasyonunuza çekin ve IR Hydra ile açın.Satır içi öğeyi bulma: IR Hydra'da derleme yapılarını yükleyin ve
doSomethingiçin arama yapın. Inliner geçişinden önceki ve sonraki gösterimi karşılaştırın.method0'daki talimatlar arayanla birleştirildikçe grafiğin genişlediğini görürsünüz.
Alternatif olarak, Compiler Explorer'daki Opt Pipeline aracını (yukarıdaki bölümde açıklandığı gibi) kullanabilir ve Inliner geçişinde benzer bir dönüşümün gerçekleştirildiğini görmek için benzer bir kod girebilirsiniz.
Alıştırma: değişken alanlar ve bellek engelleri
MemoryLab uygulamasında mGarbageSink alanı volatile olarak işaretlenir. Bu, derleyicinin çöp ayırmalarımızı optimize etmemesini sağlar.
public volatile byte[] mGarbageSink;
ARM64 ayrıştırmasında, bu alana yapılan her depolama işleminin Memory Barrier (dmb ish) ile birlikte yapıldığını veya Load-Acquire/Store-Release talimatlarının (ldar/stlr) kullanıldığını görürsünüz. Bu, iş parçacığı görünürlüğünü sağlar ancak her erişime birkaç ek talimat ekleyerek normal bir alana kıyasla kod boyutunu biraz artırır.
Alıştırma: Sökme işleminde alan erişimlerini ve ilişkili bellek engellerini bulun.
Alıştırma: Örtülü askıya alma kontrolleri
generateAllocationChurn'daki gibi bir döngüyü söktüğünüzde döngü gövdesinin sonunda merak uyandıran bir talimat görürsünüz:
ldr x21, [x21]
Bu, örtülü askıya alma kontrolüdür. ART, çöp toplayıcının iş parçacıklarını güvenli bir şekilde duraklatmasına izin vermek için bunu kullanır. Register x21 genellikle kendisini işaret eder.
GC'nin iş parçacığını askıya alması gerektiğinde bu bellek konumunu "zehirler". İş parçacığı bir sonraki ldr yürütme işleminde bir hata tetiklenir. Bu hata, çalışma zamanı tarafından yakalanır ve iş parçacığını askıya alınmış duruma geçirmek için kullanılır.
Bu kalıp, her döngüde ve her yöntemin başında tekrarlanarak uygulamanızın toplam kod boyutuna katkıda bulunur.
Alıştırma: Yöntem ayrıştırmasında tüm örtülü askıya alma kontrollerini bulun ve bunları orijinal kaynak koduyla ilişkilendirmeye çalışın.
← WebView | ↑ Yukarı | İş parçacıkları →