बिटमैप और मेमोरी

बिटमैप ऑब्जेक्ट, अक्सर किसी ऐप्लिकेशन की मेमोरी में सबसे ज़्यादा जगह लेते हैं. चाहे वे ऐप्लिकेशन के आइकॉन हों, सूचनाओं में दिखने वाली इमेज हों या मीडिया कॉन्टेंट हो, बिटमैप को सही तरीके से मैनेज न करने पर, मेमोरी से जुड़ी गड़बड़ियां (OOM) आ सकती हैं. साथ ही, पूरे सिस्टम पर मेमोरी का दबाव बढ़ सकता है.

बिटमैप के कॉन्फ़िगरेशन और पिक्सल डेटा

कोई बिटमैप कितनी मेमोरी लेता है, यह मुख्य रूप से उसके डाइमेंशन (चौड़ाई × ऊंचाई) और कॉन्फ़िगरेशन (Bitmap.Config) से तय होता है.

कॉन्फ़िगरेशन से तय होता है कि हर पिक्सल को दिखाने के लिए कितने बाइट इस्तेमाल किए जाते हैं:

कॉन्फ़िगरेशन हर पिक्सल के लिए बाइट ब्यौरा
ALPHA_8 1 सिर्फ़ ऐल्फ़ा (ट्रांसपेरेंसी) चैनल. मास्क के लिए काम का है.
RGB_565 2 लाल (5 बिट), हरा (6 बिट), नीला (5 बिट). इसमें ऐल्फ़ा नहीं होता. यह अपारदर्शी इमेज के लिए अच्छा है. हालांकि, इसमें रंग की सटीक जानकारी होना ज़रूरी नहीं है.
ARGB_8888 4 ऐल्फ़ा, लाल, हरा, नीला (हर रंग के लिए 8 बिट). यह डिफ़ॉल्ट और सबसे आम कॉन्फ़िगरेशन है.
RGBA_F16 8 हाफ़-प्रिसिशन फ़्लोटिंग पॉइंट. इसका इस्तेमाल, वाइड-गैमट और एचडीआर कॉन्टेंट के लिए किया जाता है.
HARDWARE लागू नहीं यह ग्राफ़िक्स मेमोरी (gralloc/DMABuf) में सेव होता है. हार्डवेयर बिटमैप देखें.

मेमोरी का फ़ॉर्मूला: Memory (Bytes) = Width × Height × Bytes Per Pixel

उदाहरण के लिए, 1080p वाले डिवाइस (1920x1080) पर, ARGB_8888 में फ़ुल-स्क्रीन इमेज के लिए: 1920 × 1080 × 4 बाइट ≈ 8.3 एमबी.

हीप बिटमैप बनाम शेयर किए गए बिटमैप

हीप बिटमैप (नेटिव हीप)

Android के नए वर्शन (8.0+) में, बिटमैप पिक्सल डेटा नेटिव हीप में सेव होता है. वहीं, Java हीप में सिर्फ़ एक छोटा रैपर ऑब्जेक्ट सेव होता है.

जब किसी ऐप्लिकेशन को कोई इमेज दिखानी होती है, तो आम तौर पर उसे कंप्रेस की गई इमेज फ़ाइल से डिकोड करके बिटमैप में बदला जाता है. इसके बाद, उसे हीप में सेव किया जाता है.

शेयर किए गए बिटमैप (ashmem/memfd)

जब किसी बिटमैप को एक प्रोसेस से दूसरी प्रोसेस में ट्रांसफ़र किया जाता है (जैसे, सूचना के लिए Binder के ज़रिए SystemUI में), तो Android, शेयर की गई मेमोरी (ashmem या memfd) का इस्तेमाल करके पिक्सल डेटा को कॉपी करने से बचता है.

किसी Bitmap इंस्टेंस को शेयर की गई मेमोरी में साफ़ तौर पर कॉपी किया जा सकता है. इसके लिए, Bitmap.asShared() को कॉल करें. इसके अलावा, अगर किसी Bitmap को Parcel में रखा जाता है, तो उसे शेयर की गई मेमोरी में अपने-आप कॉपी कर लिया जाता है. आम तौर पर, ऐसा तब होता है, जब बिटमैप को Parcelable में जोड़ा जाता है. जैसे, Bundle. इसके बाद, इसे Binder IPC के ज़रिए भेजा जाता है.

Binder IPC के ज़रिए शेयर किया गया बिटमैप भेजने पर, पिक्सल डेटा को कॉपी नहीं किया जाता. इसके बजाय, शेयर की गई मेमोरी वाले इलाके का रेफ़रंस देने वाले फ़ाइल डिस्क्रिप्टर को पाने वाली प्रोसेस में डुप्लीकेट किया जाता है. एक से ज़्यादा प्रोसेस के बीच, मेमोरी के बुनियादी इलाके को शेयर किया जा सकता है. साथ ही, जब तक रेफ़रंस देने वाले सभी फ़ाइल डिस्क्रिप्टर बंद नहीं किए जाते, तब तक इसे खाली नहीं किया जाता.

बदले जा सकने वाले बिटमैप बनाम नहीं बदले जा सकने वाले बिटमैप

  • बदले जा सकने वाले बिटमैप: इन्हें बनाने के बाद बदला जा सकता है. जैसे, Canvas के ज़रिए. इनके लिए, हमेशा अपनी निजी मेमोरी का बंटवारा करना ज़रूरी होता है. अगर बदले जा सकने वाले बिटमैप को कॉपी किया जाता है, तो उसकी डीप कॉपी (सभी पिक्सल डेटा की दूसरी कॉपी) बनानी ज़रूरी है.
  • नहीं बदले जा सकने वाले बिटमैप: इन्हें बदला नहीं जा सकता. इससे ऑप्टिमाइज़ेशन किया जा सकता है. जैसे, अलग-अलग Bitmap इंस्टेंस के बीच, एक ही बुनियादी मेमोरी बफ़र शेयर करना. APK रिसोर्स (BitmapFactory) से लोड किए गए बिटमैप, आम तौर पर नहीं बदले जा सकते.

बिटमैप को सही तरीके से मैनेज करना

बिटमैप पूलिंग और फिर से इस्तेमाल करना

बिटमैप को बार-बार बांटने और खाली करने से एलोकेशन चर्न होता है. इसकी वजह से, जीसी को लगातार काम करना पड़ता है. इमेज लोड करने वाली सामान्य लाइब्रेरी, बिटमैप पूल का इस्तेमाल करती हैं.

Google, Java पर आधारित ऐप्लिकेशन के लिए Glide और Kotlin पर आधारित ऐप्लिकेशन के लिए Coil इस्तेमाल करने का सुझाव देता है. खास तौर पर, Jetpack Compose का इस्तेमाल करते समय.

जब किसी बिटमैप की ज़रूरत नहीं होती, तो ऐप्लिकेशन, bitmap.recycle() को कॉल करता है या उसे पूल में वापस भेज देता है. ऐसा करने से, जीसी को उसे खाली करने की ज़रूरत नहीं पड़ती. अगली बार, जब एक ही डाइमेंशन और कॉन्फ़िगरेशन वाले बिटमैप की ज़रूरत होती है, तो पूल, मौजूदा बफ़र उपलब्ध कराता है. इससे, नया बिटमैप बनाने की ज़रूरत नहीं पड़ती.

हार्डवेयर बिटमैप

Bitmap.Config.HARDWARE की मदद से, पिक्सल डेटा को सीधे ग्राफ़िक्स मेमोरी (DMABuf) में सेव किया जा सकता है.

  • फ़ायदे:
    • मेमोरी की बचत: यह ऐप्लिकेशन या नेटिव हीप का इस्तेमाल नहीं करता. इसके बजाय, जीपीयू मेमोरी का इस्तेमाल करता है. अक्सर, किसी ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) में दिखने वाले बिटमैप को जीपीयू मेमोरी में कॉपी करना पड़ता है. इसलिए, इससे कॉपी करने की कार्रवाई और मेमोरी का अतिरिक्त खर्च बच जाता है.
    • परफ़ॉर्मेंस: इसे ड्रा करने में बहुत कम समय लगता है, क्योंकि डेटा पहले से ही जीपीयू पर मौजूद होता है.
  • नुकसान:
    • नहीं बदले जा सकते: हार्डवेयर बिटमैप में बदलाव नहीं किया जा सकता.
    • रीड-बैक की प्रोसेस धीमी होती है: सीपीयू से पिक्सल ऐक्सेस करना (जैसे, getPixel()) बहुत महंगा होता है.
    • एट्रिब्यूशन: AHAT जैसे स्टैंडर्ड टूल में इसे ट्रैक करना मुश्किल होता है (इसके बारे में नीचे बताया गया है).

प्रैक्टिकल गतिविधि: बिटमैप एक्सप्लोर करना

हम इन सिद्धांतों को एक्सप्लोर करने के लिए, BitmapLab सैंपल के तौर पर मिले ऐप्लिकेशन का इस्तेमाल करेंगे.

1. dumpsys meminfo की मदद से मेज़र करना

BitmapLab लॉन्च करें और ALLOCATE 10MB ARGB_8888 पर टैप करें. इसके बाद, यह कमांड चलाएं:

adb shell dumpsys meminfo -s com.android.bitmaplab

Android के नए वर्शन पर, नेटिव एलोकेशन सेक्शन देखें. इनमें बिटमैप के लिए, ऐप्लिकेशन की खास जानकारी की तुलना में बेहतर एट्रिब्यूशन मिलता है:

 Native Allocations
                         Count                       Total(kB)
                        ------                         ------
   Bitmap (malloced):        1                          10240  # <--- 10MB Bitmap data!
Bitmap (nonmalloced):        0                              0
  • बिटमैप (malloced): प्रोसेस के नेटिव हीप में बांटे गए बिटमैप. Android 8.0+ में, ज़्यादातर स्टैंडर्ड बिटमैप यहीं सेव होते हैं.
  • बिटमैप (nonmalloced): ऐसे बिटमैप जो हार्डवेयर बिटमैप या शेयर किए गए बिटमैप (जैसे, ashmem या memfd) जैसी खास मेमोरी का इस्तेमाल करते हैं.

अगर BitmapLab में शेयर किया गया बिटमैप बांटा जाता है, तो आपको यह Bitmap (nonmalloced) में दिखेगा:

 Native Allocations
                         Count                       Total(kB)
                        ------                         ------
   Bitmap (malloced):        1                          10240
Bitmap (nonmalloced):        1                          10240  # <--- Shared Bitmap!

शेयर किए गए बिटमैप को ट्रैक करना

Android के कुछ वर्शन और कर्नल कॉन्फ़िगरेशन पर, dumpsys meminfo की मदद से, उन बिटमैप को भी हाई-रिज़ॉल्यूशन में ट्रैक किया जा सकता है जिन्हें फ़ाइल डिस्क्रिप्टर के ज़रिए, प्रोसेस के पता स्पेस में मैप किया गया है.

डिफ़ॉल्ट रूप से, शेयर किए गए बिटमैप के लिए सामान्य नाम ("बिटमैप") का इस्तेमाल किया जाता है. विस्तार से एट्रिब्यूशन और यूनीक बिटमैप ट्रैकिंग (अलग-अलग प्रोसेस में शेयर किए गए बिटमैप की पहचान करना) की सुविधा चालू करने के लिए, आपको सिस्टम की यह प्रॉपर्टी चालू करनी होगी:

adb shell setprop debug.hwui.bitmap_ashmem_long_name true

इसे चालू करने पर, /proc/<pid>/smaps में मौजूद ashmem इलाकों के नाम ज़्यादा जानकारी देने वाले होंगे. meminfo इसका फ़ायदा उठाएगा. साथ ही, नतीजे इस तरह दिखेंगे:

 Shared Bitmaps
                         Count                       Size(KB)
                        ------                         ------
              Mapped:        1                          10240
              Unique:        1                          10240
  • मैप्ड: बिटमैप से जुड़े सभी मेमोरी मैपिंग का कुल साइज़.
  • यूनीक: सिर्फ़ यूनीक बिटमैप का साइज़. इसका मतलब है कि एक ही बुनियादी शेयर किए गए बिटमैप पिक्सल डेटा की दो या उससे ज़्यादा मैपिंग को सिर्फ़ एक बार गिना जाता है.

2. AHAT में बिटमैप

AHAT में बिटमैप को विज़ुअलाइज़ करने की बेहतरीन सुविधा मिलती है.

  1. BitmapLab में, कुछ बिटमैप बांटें.
  2. नेटिव बिटमैप डेटा शामिल करने के लिए, -b फ़्लैग के साथ हीप डंप कैप्चर करें:

    adb shell am dumpheap -b png com.android.bitmaplab /data/local/tmp/bitmaps.hprof
    adb pull /data/local/tmp/bitmaps.hprof .
    ahat bitmaps.hprof
    
  3. localhost:7100 खोलें और साइडबार में बिटमैप लिंक देखें या Bitmap क्लास खोजें.

  4. AHAT, ब्राउज़र में बिटमैप रेंडर करेगा. इससे यह पहचानना आसान हो जाएगा कि कौनसी इमेज, मेमोरी में ज़्यादा जगह ले रही हैं.

रेंडर किए गए बिटमैप दिखाने वाला AHAT

3. Perfetto में बिटमैप ट्रैक

Perfetto, समय के साथ बिटमैप के बंटवारे और उनकी संख्या को ट्रैक कर सकता है. जब किसी खास ऐप्लिकेशन के लिए gfx atrace कैटगरी चालू की जाती है, तो Android फ़्रेमवर्क इन काउंटर को एमिट करता है.

  1. ट्रेस करना शुरू करें. आपको -a फ़्लैग का इस्तेमाल करके, gfx कैटगरी शामिल करनी होगी और खास ऐप्लिकेशन पैकेज को टारगेट करना होगा:

    external/perfetto/tools/record_android_trace -o bitmaps.perfetto-trace \
        -t 15s -b 64mb view gfx dalvik am res memory -a com.android.bitmaplab
    
  2. BitmapLab में, बांटें और साफ़ करें बटन पर बार-बार टैप करें.

  3. पार्सल/अनपार्सल बिटमैप पर भी टैप करें.

  4. ui.perfetto.dev में ट्रेस का विश्लेषण करें.

com.android.bitmaplab के लिए प्रोसेस सेक्शन में, आपको ये दिखेंगे: * बिटमैप की संख्या: यह काउंटर, चालू बिटमैप की संख्या दिखाता है. * बिटमैप मेमोरी: यह काउंटर, बिटमैप के लिए इस्तेमाल किए गए कुल बाइट दिखाता है.

हाई-लेवल स्लाइस (Perfetto SDK)

BitmapLab , बिटमैप की कार्रवाइयों के लिए हाई-लेवल स्लाइस एमिट करने के लिए, Perfetto SDK का भी इस्तेमाल करता है. ट्रेस में BitmapLab_ खोजकर, ये चीज़ें ढूंढें: * BitmapLab_parcelUnparcel: पार्सल करने और अनपार्सल करने की लॉजिक को कवर करने वाले स्लाइस. * BitmapLab_postNotification: सूचना पोस्ट करने के फ़्लो को कवर करने वाले स्लाइस.

सूचना के फ़्लो को ट्रैक करना

**सूचना पोस्ट करें** पर टैप करने पर, ऐप्लिकेशन एक सूचना बनाता है. इसमें मौजूदा बिटमैप शामिल होता है. इसके बाद, यह सूचना सिस्टम को भेजी जाती है. इसके लिए ज़िम्मेदार फ़्रेमवर्क कोड, Perfetto स्लाइस एमिट करता है. इनमें फ़्लो इवेंट शामिल होते हैं. ये इवेंट, पार्सल करने (Binder IPC के ज़रिए भेजने के लिए, बिटमैप को पार्सल में लिखना) और अनपार्सल करने (पाने वाले एंड पर, पार्सल से बिटमैप पढ़ना) को कनेक्ट करते हैं.

नीचे दिए गए स्क्रीनशॉट में, आपको दिख रहा होगा कि ऐप्लिकेशन, सूचना पोस्ट करने के लिए Binder ट्रांज़ैक्शन में इस्तेमाल किए जाने वाले बड़े बिटमैप को पार्सल कर रहा है. साथ ही, system_server प्रोसेस में, इससे जुड़ा अनपार्सल भी दिख रहा है.

Perfetto में, सूचना के ज़रिए BitmapLab से system_server तक का फ़्लो दिखाया गया है

Perfetto का इस्तेमाल करके, उसी सूचना के बिटमैप को थ्रेड और प्रोसेस में आगे बढ़ने पर भी ट्रैक किया जा सकता है. उदाहरण के लिए, system_server में मौजूद Binder थ्रेड (जो INotificationManager Binder सर्वर को लागू करता है) से system_server वर्कर थ्रेड तक. इसके बाद, ये थ्रेड उसी बिटमैप को com.android.systemui को फ़ॉरवर्ड कर सकते हैं, ताकि इसे सूचनाओं के शेड में दिखाया जा सके.

सिस्टम ऐप्लिकेशन से जुड़ी चुनौतियां

SystemUI (सूचनाएं) और Launcher जैसे सिस्टम ऐप्लिकेशन के सामने, ये खास चुनौतियां आती हैं:

  1. अनबाउंड कॉन्टेंट: सूचनाएं और विजेट की संख्या ज़्यादा हो सकती है. अगर हर सूचना और विजेट में बड़ा बिटमैप शामिल है, तो सिस्टम की मेमोरी तेज़ी से खत्म हो सकती है.
  2. डुप्लीकेशन: एक ही ऐप्लिकेशन का आइकॉन, Launcher की कैश मेमोरी, SystemUI के सूचना वाले इलाके, और सेटिंग ऐप्लिकेशन में सेव हो सकता है.
  3. हार्डवेयर बफ़र के ज़रिए शेयर करना: इस समस्या को कम करने के लिए, सिस्टम कॉम्पोनेंट, "इमेज ऑफ़लोड" की केंद्रीकृत सेवा की ओर बढ़ रहे हैं. यह सेवा, प्रोसेस के बीच HardwareBuffer इंस्टेंस शेयर करती है.
  4. DMABuf एट्रिब्यूशन: हार्डवेयर बिटमैप, हीप स्पेस बचाते हैं, लेकिन DMABuf मेमोरी का इस्तेमाल करते हैं. स्टैंडर्ड मेमोरी टूल में, इसे किसी खास प्रोसेस के लिए एट्रिब्यूट करना मुश्किल होता है.

    सिस्टम-वाइड DMABuf एलोकेशन देखने के लिए, adb shell dmabuf_dump का इस्तेमाल करें. यह टूल, बफ़र का पर-प्रोसेस ब्रेकडाउन उपलब्ध कराता है:

     droid.bitmaplab:19562
                      Name              Rss              Pss         nr_procs            Inode               Exporter
                 <unknown>          3840 kB          1280 kB                3             3397              virtio_gpu
                    system            12 kB             4 kB                3             3398                  system
                 <unknown>          3840 kB          1920 kB                2             3399              virtio_gpu
                    system            12 kB             6 kB                2             3400                  system
             PROCESS TOTAL         11556 kB          5136 kB
    
    • आरएसएस: अगर बफ़र को प्रोसेस में मैप किया गया है, तो उसका कुल साइज़.
    • पीएसएस: आनुपातिक साइज़ (आरएसएस को, बफ़र शेयर करने वाली प्रोसेस की संख्या से भाग देने पर मिलने वाला साइज़). यह, हिसाब-किताब के लिए सबसे अच्छी मेट्रिक है.
    • nr_procs: फ़िलहाल, इस बफ़र का रेफ़रंस रखने वाली प्रोसेस की संख्या.
    • एक्सपोर्टर: वह ड्राइवर जिसने बफ़र बांटा है. जैसे, virtio_gpu Cuttlefish पर या हार्डवेयर पर वेंडर के हिसाब से Ion/DMA-BUF हीप.

    सभी बफ़र और सिस्टम-वाइड DMA-BUF के कुल इस्तेमाल की खास जानकारी पाने के लिए, adb shell dmabuf_dump -b का भी इस्तेमाल किया जा सकता है.


← Java | ↑ ऊपर जाएं | Native →