বিটম্যাপ এবং মেমরি

একটি অ্যাপ্লিকেশনের মেমোরি ফুটপ্রিন্টে বিটম্যাপ অবজেক্টগুলোই প্রায়শই সবচেয়ে বড় অবদান রাখে। তা অ্যাপ আইকন, নোটিফিকেশন ইমেজ বা মিডিয়া কন্টেন্ট যাই হোক না কেন, বিটম্যাপের অদক্ষ হ্যান্ডলিং দ্রুত আউট-অফ-মেমোরি (OOM) এরর এবং সিস্টেম-ব্যাপী মেমোরির চাপ সৃষ্টি করতে পারে।

বিটম্যাপ কনফিগারেশন এবং পিক্সেল ডেটা

একটি বিটম্যাপ যে পরিমাণ মেমরি ব্যবহার করে, তা মূলত এর মাত্রা (প্রস্থ × উচ্চতা) এবং এর কনফিগারেশন ( Bitmap.Config ) দ্বারা নির্ধারিত হয়।

কনফিগারেশনটি নির্ধারণ করে যে প্রতিটি পিক্সেলকে উপস্থাপন করতে কত বাইট ব্যবহৃত হবে:

কনফিগারেশন প্রতি পিক্সেলে বাইট বর্ণনা
ALPHA_8 শুধুমাত্র আলফা (স্বচ্ছতা) চ্যানেল। মাস্কের জন্য উপযোগী।
RGB_565 লাল (৫ বিট), সবুজ (৬ বিট), নীল (৫ বিট)। কোনো আলফা নেই। অস্বচ্ছ ছবির জন্য ভালো, যেখানে রঙের উচ্চ নির্ভুলতা অতটা গুরুত্বপূর্ণ নয়।
ARGB_8888 আলফা, লাল, সবুজ, নীল (প্রত্যেকটি ৮ বিট)। ডিফল্ট এবং সর্বাধিক প্রচলিত।
RGBA_F16 হাফ-প্রিসিশন ফ্লোটিং পয়েন্ট। ওয়াইড-গ্যামুট এবং HDR কন্টেন্টের জন্য ব্যবহৃত হয়।
HARDWARE প্রযোজ্য নয় গ্রাফিক্স মেমরিতে (gralloc/DMABuf) সংরক্ষিত থাকে। হার্ডওয়্যার বিটম্যাপ দেখুন।

মেমরি সূত্র: Memory (Bytes) = Width × Height × Bytes Per Pixel

উদাহরণস্বরূপ, একটি 1080p ডিভাইসে ARGB_8888 ফরম্যাটের একটি ফুল-স্ক্রিন ছবির (1920x1080) জন্য প্রয়োজন হয়: 1920 × 1080 × 4 বাইট ≈ 8.3 মেগাবাইট।

হিপ বিটম্যাপ বনাম শেয়ার্ড বিটম্যাপ

হিপ বিটম্যাপ (নেটিভ হিপ)

আধুনিক অ্যান্ড্রয়েডে (৮.০+), বিটম্যাপ পিক্সেল ডেটা নেটিভ হিপ- এ সংরক্ষিত থাকে, এবং জাভা হিপ-এ কেবল একটি ছোট র‍্যাপার অবজেক্ট অবস্থান করে।

যখন কোনো অ্যাপকে একটি ছবি প্রদর্শন করতে হয়, তখন সাধারণত সেটিকে একটি কম্প্রেসড ইমেজ ফাইল থেকে ডিকোড করে বিটম্যাপে রূপান্তর করা হয় এবং হিপ-এ সংরক্ষণ করা হয়।

শেয়ার করা বিটম্যাপ (ashmem/memfd)

যখন কোনো বিটম্যাপ প্রসেসগুলোর মধ্যে স্থানান্তরিত হয় (যেমন, কোনো নোটিফিকেশনের জন্য বাইন্ডারের মাধ্যমে সিস্টেমইউআই-তে), তখন অ্যান্ড্রয়েড শেয়ার্ড মেমরি ( ashmem বা memfd ) ব্যবহার করে পিক্সেল ডেটা কপি করা এড়িয়ে চলে।

একটি Bitmap ইনস্ট্যান্সকে শেয়ার্ড মেমরিতে স্পষ্টভাবে Bitmap.asShared() কল করে কপি করা যায়, অথবা পরোক্ষভাবেও করা যায় যদি একটি Bitmap কোনো Parcel ভেতরে রাখা হয় (সাধারণত Bundle মতো কোনো Parcelable এ Bitmap-টি যোগ করে) এবং Binder IPC-এর মাধ্যমে পাঠানো হয়।

যখন বাইন্ডার আইপিসি (Binder IPC)-র মাধ্যমে একটি শেয়ার্ড বিটম্যাপ পাঠানো হয়, তখন পিক্সেল ডেটা নিজে কপি করা হয় না, বরং একটি শেয়ার্ড মেমরি অঞ্চলকে নির্দেশকারী ফাইল ডেসক্রিপ্টরটি প্রাপক প্রসেসে ডুপ্লিকেট করা হয়। অন্তর্নিহিত মেমরি অঞ্চলটি একাধিক প্রসেসের মধ্যে শেয়ার করা থাকতে পারে, এবং এটিকে নির্দেশকারী সমস্ত ফাইল ডেসক্রিপ্টর বন্ধ না হওয়া পর্যন্ত এটি মুক্ত হয় না।

পরিবর্তনযোগ্য বনাম অপরিবর্তনযোগ্য বিটম্যাপ

  • পরিবর্তনযোগ্য বিটম্যাপ : তৈরির পরেও পরিবর্তন করা যায় (যেমন, একটি Canvas মাধ্যমে)। এগুলোর জন্য সর্বদা নিজস্ব মেমরি বরাদ্দের প্রয়োজন হয়। যদি একটি পরিবর্তনযোগ্য বিটম্যাপ কপি করা হয়, তবে একটি ডিপ কপি (সমস্ত পিক্সেল ডেটার দ্বিতীয় কপি) অবশ্যই তৈরি করতে হবে।
  • অপরিবর্তনশীল বিটম্যাপ : যা পরিবর্তন করা যায় না। এর ফলে বিভিন্ন Bitmap ইনস্ট্যান্সের মধ্যে একই অন্তর্নিহিত মেমরি বাফার শেয়ার করার মতো অপটিমাইজেশন করা সম্ভব হয়। APK রিসোর্স ( BitmapFactory ) থেকে লোড করা বিটম্যাপগুলো সাধারণত অপরিবর্তনশীল হয়ে থাকে।

দক্ষ বিটম্যাপ হ্যান্ডলিং

বিটম্যাপ পুলিং এবং পুনঃব্যবহার

ঘন ঘন বিটম্যাপ বরাদ্দ ও অবমুক্ত করার ফলে অ্যালোকেশন চার্ন সৃষ্টি হয়, যা গারবেজ কালেক্টরকে (GC) অনবরত চলতে বাধ্য করে। প্রচলিত ইমেজ লোডিং লাইব্রেরিগুলো একটি বিটম্যাপ পুল ব্যবহার করে।

গুগল জাভা-ভিত্তিক অ্যাপ্লিকেশনের জন্য সমাধান হিসেবে গ্লাইড এবং কোটলিন-ভিত্তিক অ্যাপ্লিকেশনের জন্য কয়েল-এর সুপারিশ করে (বিশেষ করে যখন জেটপ্যাক কম্পোজ ব্যবহার করা হয়)।

যখন একটি বিটম্যাপের আর প্রয়োজন হয় না, তখন সেটিকে গার্বেজ কালেক্ট (GC) হতে না দিয়ে, অ্যাপটি bitmap.recycle() কল করে অথবা সেটিকে একটি পুলে ফেরত পাঠায়। পরবর্তী সময়ে যখন একই মাপ ও কনফিগারেশনের একটি বিটম্যাপের প্রয়োজন হয়, তখন পুলটি বিদ্যমান বাফারটি সরবরাহ করে, ফলে নতুন করে মেমোরি বরাদ্দের প্রয়োজন হয় না।

হার্ডওয়্যার বিটম্যাপ

Bitmap.Config.HARDWARE আপনাকে পিক্সেল ডেটা সরাসরি গ্রাফিক্স মেমরিতে (DMABuf) সংরক্ষণ করার সুযোগ দেয়।

  • সুবিধা :
    • মেমরি সাশ্রয় : অ্যাপ্লিকেশন বা নেটিভ হিপ ব্যবহার করে না; জিপিইউ মেমরি ব্যবহার করে। প্রায়শই একটি অ্যাপের ইউআই-তে দেখানো বিটম্যাপগুলিকে জিপিইউ মেমরিতে কপি করার প্রয়োজন হয়, তাই এটি সেই কপি অপারেশন এবং অতিরিক্ত মেমরি খরচ বাঁচায়।
    • পারফরম্যান্স : ডেটা আগে থেকেই জিপিইউ-তে থাকায় এটি অত্যন্ত দ্রুত ড্র করে।
  • অসুবিধা :
    • অপরিবর্তনীয় : হার্ডওয়্যার বিটম্যাপ পরিবর্তন করা যায় না।
    • রিড-ব্যাক ধীরগতির : সিপিইউ থেকে পিক্সেল অ্যাক্সেস করা (যেমন, getPixel() ) অত্যন্ত ব্যয়বহুল।
    • উৎস নির্ণয় : AHAT-এর মতো প্রচলিত টুলগুলিতে এর উৎস খুঁজে বের করা কঠিন (নিচে দেখুন)।

হাতে-কলমে অনুশীলন: বিটম্যাপ অন্বেষণ

এই ধারণাগুলো অন্বেষণ করতে আমরা BitmapLab স্যাম্পল অ্যাপটি ব্যবহার করব।

১. dumpsys meminfo দিয়ে পরিমাপ করা

BitmapLab চালু করুন এবং ALLOCATE 10MB ARGB_8888 ট্যাপ করুন। তারপর চালান:

adb shell dumpsys meminfo -s com.android.bitmaplab

আধুনিক অ্যান্ড্রয়েড সংস্করণগুলিতে, নেটিভ অ্যালোকেশনস (Native Allocations) বিভাগটি খুঁজুন। এগুলি সাধারণ অ্যাপ সামারি (App Summary) -এর চেয়ে বিটম্যাপের জন্য অনেক ভালো অ্যাট্রিবিউশন প্রদান করে।

 Native Allocations
                         Count                       Total(kB)
                        ------                         ------
   Bitmap (malloced):        1                          10240  # <--- 10MB Bitmap data!
Bitmap (nonmalloced):        0                              0
  • বিটম্যাপ (ম্যালকড) : প্রসেসের নিজস্ব হিপে বরাদ্দ করা বিটম্যাপ। অ্যান্ড্রয়েড ৮.০+ এ বেশিরভাগ স্ট্যান্ডার্ড বিটম্যাপ এখানেই থাকে।
  • বিটম্যাপ (ননম্যালোকড) : যে সকল বিটম্যাপ হার্ডওয়্যার বিটম্যাপ বা শেয়ার্ড বিটম্যাপের ( ashmem বা memfd মাধ্যমে) মতো বিশেষায়িত মেমরি ব্যবহার করে।

আপনি যদি BitmapLab- এ একটি Shared Bitmap বরাদ্দ করেন, তাহলে আপনি এটি Bitmap (nonmalloced) এ প্রতিফলিত দেখতে পাবেন:

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

শেয়ার করা বিটম্যাপ ট্র্যাকিং

কিছু অ্যান্ড্রয়েড সংস্করণ এবং কার্নেল কনফিগারেশনে, dumpsys meminfo সেইসব বিটম্যাপের জন্যও উচ্চ-রেজোলিউশনের ট্র্যাকিং প্রদান করে, যেগুলো ফাইল ডেসক্রিপ্টরের মাধ্যমে প্রসেসের অ্যাড্রেস স্পেসে ম্যাপ করা থাকে।

ডিফল্টরূপে, শেয়ার করা বিটম্যাপগুলো একটি জেনেরিক নাম ("bitmap") ব্যবহার করে। বিস্তারিত অ্যাট্রিবিউশন এবং অনন্য বিটম্যাপ ট্র্যাকিং (বিভিন্ন প্রসেসের মধ্যে শেয়ার করা বিটম্যাপ শনাক্ত করা) সক্ষম করতে, আপনাকে অবশ্যই নিম্নলিখিত সিস্টেম প্রপার্টিটি সক্রিয় করতে হবে:

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
  • ম্যাপড : সকল বিটম্যাপ-সম্পর্কিত মেমরি ম্যাপিংয়ের মোট আকার।
  • অনন্য : শুধুমাত্র অনন্য বিষয়গুলো বিবেচনা করে বিটম্যাপের আকার (অর্থাৎ একই অন্তর্নিহিত শেয়ার্ড বিটম্যাপ পিক্সেল ডেটার দুই বা ততোধিক ম্যাপিং কেবল একবার গণনা করা হয়)।

২. 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 খুলুন এবং সাইডবারে Bitmaps লিঙ্কটি খুঁজুন অথবা Bitmap ক্লাসটি অনুসন্ধান করুন।

  4. AHAT প্রকৃতপক্ষে ব্রাউজারে বিটম্যাপগুলো রেন্ডার করবে, ফলে কোন ছবিগুলো বেশি মেমরি ব্যবহার করছে তা সহজেই শনাক্ত করা যাবে।

AHAT রেন্ডার করা বিটম্যাপ প্রদর্শন করছে

৩. পারফেটোতে বিটম্যাপ ট্র্যাক

পারফেটটো সময়ের সাথে সাথে বিটম্যাপ অ্যালোকেশন এবং তার সংখ্যা ট্র্যাক করতে পারে। যখন কোনো নির্দিষ্ট অ্যাপ্লিকেশনের জন্য 'gfx atrace' ক্যাটাগরিটি সক্রিয় করা হয়, তখন অ্যান্ড্রয়েড ফ্রেমওয়ার্ক এই কাউন্টারগুলো নির্গত করে।

  1. একটি ট্রেস শুরু করুন। আপনাকে অবশ্যই gfx ক্যাটাগরিটি অন্তর্ভুক্ত করতে হবে এবং -a ফ্ল্যাগ ব্যবহার করে নির্দিষ্ট অ্যাপ প্যাকেজটিকে টার্গেট করতে হবে:

    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-Allocate এবং Clear বাটনগুলো বারবার চাপুন।

  3. এছাড়াও পার্সেল/আনপার্সেল বিটম্যাপ-এ ট্যাপ করুন।

  4. ui.perfetto.dev- এর ট্রেসটি বিশ্লেষণ করুন।

com.android.bitmaplab এর প্রসেস সেকশনে আপনি দেখতে পাবেন: * বিটম্যাপ কাউন্ট : একটি কাউন্টার যা সক্রিয় বিটম্যাপের সংখ্যা দেখায়। * বিটম্যাপ মেমরি : একটি কাউন্টার যা বিটম্যাপ দ্বারা ব্যবহৃত মোট বাইট দেখায়।

উচ্চ-স্তরের স্লাইস (পারফেটটো এসডিকে)

বিটম্যাপ অপারেশনের জন্য উচ্চ-স্তরের স্লাইস তৈরি করতে BitmapLab , Perfetto SDK- ও ব্যবহার করে। ট্রেস-এ BitmapLab_ খুঁজে দেখুন: * BitmapLab_parcelUnparcel : পার্সেলিং এবং আনপার্সেলিং লজিক সম্পর্কিত স্লাইস। * BitmapLab_postNotification : নোটিফিকেশন পোস্টিং ফ্লো সম্পর্কিত স্লাইস।

বিজ্ঞপ্তি প্রবাহ ট্র্যাক করা

আপনি যখন 'পোস্ট নোটিফিকেশন' ট্যাপ করেন, তখন অ্যাপটি বর্তমান বিটম্যাপ সম্বলিত একটি নোটিফিকেশন তৈরি করে এবং সেটি সিস্টেমে পাঠিয়ে দেয়। এর জন্য দায়ী ফ্রেমওয়ার্ক কোডটি পারফেটটো স্লাইস নির্গত করে, যেগুলোতে ফ্লো ইভেন্ট থাকে এবং যা পার্সেলিং (বাইন্ডার আইপিসি-র মাধ্যমে পাঠানোর জন্য বিটম্যাপটিকে একটি পার্সেল-এ লেখা) এবং আনপার্সেলিং (প্রাপক প্রান্তে একটি পার্সেল থেকে বিটম্যাপটি পড়া)-কে সংযুক্ত করে।

নিচের স্ক্রিনশটে আপনি দেখতে পাচ্ছেন, অ্যাপটি নোটিফিকেশন পোস্ট করার জন্য একটি বাইন্ডার ট্রানজ্যাকশনে ব্যবহার করার উদ্দেশ্যে বড় বিটম্যাপটিকে পার্সেল করছে এবং system_server প্রসেসে এর সংশ্লিষ্ট আনপার্সেলিং প্রক্রিয়াটি সম্পন্ন হচ্ছে।

পারফেট্টো নোটিফিকেশনের মাধ্যমে বিটম্যাপল্যাব থেকে সিস্টেম_সার্ভারে একটি প্রবাহ দেখাচ্ছে।

Perfetto ব্যবহার করে আপনি একই নোটিফিকেশন বিটম্যাপকে বিভিন্ন থ্রেড ও প্রসেসের মধ্যে ছড়িয়ে পড়া পর্যন্ত অনুসরণ করতে পারেন; যেমন, system_server এর একটি বাইন্ডার থ্রেড (যা INotificationManager বাইন্ডার সার্ভারটি ইমপ্লিমেন্ট করে) থেকে system_server ওয়ার্কার থ্রেডগুলোতে, যেগুলো পরবর্তীতে নোটিফিকেশন শেডে দেখানোর জন্য সেই একই বিটম্যাপ com.android.systemui তে ফরোয়ার্ড করতে পারে।

সিস্টেম অ্যাপের চ্যালেঞ্জ

সিস্টেমইউআই (নোটিফিকেশন) এবং লঞ্চারের মতো সিস্টেম অ্যাপগুলো কিছু স্বতন্ত্র চ্যালেঞ্জের সম্মুখীন হয়:

  1. অসীম কন্টেন্ট : নোটিফিকেশন এবং উইজেটের সংখ্যা অনেক হতে পারে। যদি প্রতিটিতে একটি বড় বিটম্যাপ থাকে, তাহলে সিস্টেমের মেমোরি দ্রুত শেষ হয়ে যেতে পারে।
  2. পুনরাবৃত্তি : একই অ্যাপ আইকন লঞ্চারের ক্যাশে, সিস্টেমইউআই-এর নোটিফিকেশন এরিয়া এবং সেটিংস অ্যাপে থাকতে পারে।
  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
    
    • RSS : প্রসেসে ম্যাপ করা হলে বাফারের মোট আকার।
    • Pss : আনুপাতিক আকার (RSS-কে বাফার শেয়ারকারী প্রসেসের সংখ্যা দিয়ে ভাগ করে)। হিসাবরক্ষণের জন্য এটিই সর্বোত্তম পরিমাপক।
    • nr_procs : বর্তমানে এই বাফারটির রেফারেন্স ধরে রাখা প্রসেসের সংখ্যা।
    • এক্সপোর্টার : যে ড্রাইভারটি বাফারটি বরাদ্দ করেছে (যেমন, কাটলফিশ-এ virtio_gpu , অথবা হার্ডওয়্যারে বিক্রেতা-নির্দিষ্ট Ion/DMA-BUF হিপ)।

    এছাড়াও, সমস্ত বাফারের সারাংশ এবং সিস্টেম-ব্যাপী মোট DMA-BUF ব্যবহারের বিবরণ জানতে আপনি adb shell dmabuf_dump -b ব্যবহার করতে পারেন।


← জাভা | ↑ উপরে | নেটিভ →