আপনার লেখা কোড নিজেই এক ধরনের মেমরি ব্যবহার। আপনার অ্যাপ্লিকেশনের প্রতিটি ক্লাস, মেথড এবং স্ট্রিং কনস্ট্যান্ট এক্সিকিউট হওয়ার সময় অবশ্যই র্যামে লোড হতে হয়। আপনার অ্যাপ্লিকেশনের কোডবেস যত বড় হবে, শুধু টিকে থাকার জন্যই এটি তত বেশি মেমরি ব্যবহার করবে।
ফাইল-সমর্থিত মেমরি এবং চাহিদা পৃষ্ঠা
অ্যান্ড্রয়েড mmap ব্যবহার করে আপনার .apk (যেমন .oat বা .so ফাইল) থেকে এক্সিকিউটেবল কোড লোড করে। এর মানে হলো, কোডটি ফাইল-ভিত্তিক ।
গুরুত্বপূর্ণভাবে, অ্যান্ড্রয়েড ডিমান্ড পেজিং ব্যবহার করে। যখন আপনার অ্যাপ চালু হয়, কার্নেল সাথে সাথে পুরো APK ফাইলটিকে র্যামে লোড করে না। এর পরিবর্তে, এটি কেবল ফাইলটিকে প্রসেসের ভার্চুয়াল অ্যাড্রেস স্পেসে ম্যাপ করে। যখন আপনার অ্যাপ চলতে থাকে এবং সিপিইউ একটি নতুন ফাংশনে চলে যায়, তখন এটি একটি 'পেজ ফল্ট' ঘটায়। কার্নেল থ্রেডটিকে থামিয়ে দেয়, স্টোরেজ থেকে কোডের সেই নির্দিষ্ট ৪কেবি পেজটিকে ফিজিক্যাল র্যামে পড়ে নেয় এবং পুনরায় এক্সিকিউশন শুরু করে।

এর মানে হলো, যে কোড আপনি প্যাকেজ করেন কিন্তু কখনো চালান না, তা কোড পেজগুলোর জন্য ফিজিক্যাল মেমরি ব্যবহার করে না। তবে, অব্যবহৃত লাইব্রেরিগুলো সামগ্রিক APK সাইজ বাড়িয়ে দেয় এবং সিস্টেমের অভ্যন্তরীণ মেটাডেটা (যেমন DEX ইনডেক্স এবং ক্লাস ডেসক্রিপ্টর) দ্বারা ব্যবহৃত মেমরি উল্লেখযোগ্যভাবে বাড়িয়ে দিতে পারে, যা কোডটির অস্তিত্ব জানার জন্যও পড়তে হয়। এছাড়াও, অনেক লাইব্রেরিতে স্ট্যাটিক ইনিশিয়ালাইজার থাকে অথবা অ্যাপ চালু হওয়ার সময় ডিপেন্ডেন্সি ইনজেকশন ফ্রেমওয়ার্ক দ্বারা প্রভাবিত হয়, যার ফলে সেগুলো এমনিতেই র্যামে পেজড হয়ে যায়।
পৃষ্ঠা উচ্ছেদ এবং গতি হ্রাস
যেহেতু ফাইল-ব্যাকড মেমরি স্টোরেজ থেকে সর্বদা পুনরায় পড়া যায়, তাই কার্নেল এই পেজগুলোকে "ক্লিন" বা পরিষ্কার বলে মনে করে। যখন সিস্টেমে মেমরির চাপ সৃষ্টি হয়, তখন কার্নেল অন্যান্য জিনিসের জন্য জায়গা তৈরি করতে র্যাম থেকে এই ক্লিন কোড পেজগুলোকে ইভিক্ট (বাদ) করে দেয়।
পরবর্তীতে যদি আপনার অ্যাপের সেই কোডটি আবার চালানোর প্রয়োজন হয়, তাহলে সিপিইউ-তে ত্রুটি দেখা দেবে এবং কার্নেলকে স্টোরেজ থেকে পেজটি পুনরায় পড়তে হবে। আপনার অ্যাপে যত বেশি কোড থাকবে, তার কোড অ্যাপ থেকে বাদ পড়ার ঝুঁকিও তত বেশি থাকবে। যখন কোনো ব্যবহারকারী অন্য অ্যাপ ব্যবহার করার পর আপনার এই বিশাল অ্যাপে ফিরে আসেন, তখন তিনি হঠাৎ করে অ্যাপে ঝাঁকুনি ও ধীরগতির সম্মুখীন হবেন, কারণ স্টোরেজ থেকে কোড পুনরায় পেজে আসার অপেক্ষায় সিপিইউ ক্রমাগত আটকে থাকে।
পেজ ফল্টের খরচ: যদিও এটি ডিভাইসের স্টোরেজের গতি (UFS বনাম eMMC) এবং কার্নেলের অবস্থার উপর ব্যাপকভাবে নির্ভর করে, একটি বড় পেজ ফল্ট (স্টোরেজ থেকে ৪ কিলোবাইট ডেটা পড়া) করতে ০.৫ মিলিসেকেন্ড থেকে ৫ মিলিসেকেন্ড পর্যন্ত সময় লাগতে পারে। যদি আপনার স্টার্টআপ পাথ অপটিমাইজ না করা কোডের ৫০০টি ভিন্ন পেজ স্পর্শ করে, তাহলে আপনার অ্যাপের স্টার্টআপ সময়ে সহজেই কয়েকশ মিলিসেকেন্ডের নিছক I/O ল্যাটেন্সি যুক্ত হতে পারে।
কম্পাইলার এক্সপ্লোরার দিয়ে কোডের আকার অন্বেষণ করা
আপনার জাভা বা কোটলিন কোড কীভাবে নেটিভ মেশিন কোডে (এবং ফলস্বরূপ মেমরি বাইটে) রূপান্তরিত হয়, সে সম্পর্কে একটি ধারণা তৈরি করতে আপনি কম্পাইলার এক্সপ্লোরার ব্যবহার করতে পারেন।
গডবোল্টে সরাসরি অ্যান্ড্রয়েড সাপোর্ট অন্তর্ভুক্ত করা আছে। এর মাধ্যমে আপনি দেখতে পারবেন, অ্যান্ড্রয়েড টুলচেইনের (D8, R8, এবং dex2oat) বিভিন্ন অংশ কীভাবে আপনার সোর্স কোডকে রূপান্তরিত করে।
অ্যান্ড্রয়েডের সাথে কম্পাইলার এক্সপ্লোরার কীভাবে ব্যবহার করবেন
- godbolt.org- এ যান।
- (উপরের বাম দিকের) ভাষা ড্রপডাউন থেকে অ্যান্ড্রয়েড জাভা অথবা অ্যান্ড্রয়েড কোটলিন নির্বাচন করুন।
- কম্পাইলার ড্রপডাউনে (কোড পেনের উপরের ডানদিকে) আপনি বিভিন্ন টুলের মধ্যে থেকে বেছে নিতে পারেন:
-
d8: ডালভিক বাইটকোড (.dex) দেখায়। এটি আপনার মূল কোডের সবচেয়ে কাছাকাছি উপস্থাপনা এবং এটি পড়া সহজ। -
r8: এটি দেখায় কিভাবে R8 অপটিমাইজার আপনার বাইটকোডকে সংকুচিত ও অপ্টিমাইজ করে। -
dex2oat: ডিভাইসে প্রকৃতপক্ষে কার্যকর হওয়া চূড়ান্ত ARM64 মেশিন কোডটি দেখায়। এখানেই আপনি এর প্রকৃত মেমোরি প্রভাব (প্রতি নির্দেশনায় ৪ বাইট) দেখতে পারেন।dex2oatবিভিন্ন ISA-কে টার্গেট করতে পারে, কিন্তু মোবাইল ফোনের জন্য ARM64-ই সবচেয়ে প্রচলিত।
-
- উৎস ও আউটপুট হাইলাইটিং : কোডের কোনো লাইনের উপর মাউস রাখলে সংশ্লিষ্ট বাইটকোড বা মেশিন কোড নির্দেশাবলী হাইলাইট হবে, যার ফলে নির্দিষ্ট স্টেটমেন্টের প্রভাব খুঁজে বের করা সহজ হয়।
- অপ্টিমাইজেশন পাইপলাইন : ডিসঅ্যাসেম্বলি ভিউতে, আপনি ‘Add new...’ -> ‘Opti Pipeline’-এ ক্লিক করতে পারেন। এর মাধ্যমে কম্পাইলারের অভ্যন্তরীণ ধাপগুলো দেখা যায়। চূড়ান্ত ARM64 মেশিন কোডে লোয়ার করার আগে, প্রতিটি ধাপে (যেমন, ‘Inliner (before)’ এবং ‘Inliner (after)’ ধাপগুলোর মধ্যে) ইন্টারনাল রিপ্রেজেন্টেশন (IR) কীভাবে রূপান্তরিত হয়, তা আপনি খতিয়ে দেখতে পারেন।

স্মৃতির জন্য এটি কেন গুরুত্বপূর্ণ
dex2oat আউটপুটে ARM64 ISA-কে লক্ষ্য করে তৈরি করা প্রতিটি নির্দেশনা আপনার অ্যাপের এক্সিকিউটেবল ( .odex বা .oat ) ফাইলে ৪ বাইট জায়গা নেয়।
বিভিন্ন ল্যাঙ্গুয়েজ ফিচার ব্যবহার করে কোড লিখে কম্পাইলারের আউটপুটটি পর্যবেক্ষণ করুন:
- অ্যারে অ্যাক্সেস বনাম লিস্ট ইটারেটর
-
int[]এর উপর একটি সাধারণ অ্যারে লুপ কম্পাইল হতে প্রায় ১০টি ইন্সট্রাকশন (প্রায় ৪০ বাইট) লাগতে পারে। - একটি
Listএর উপর foreach লুপ পরোক্ষভাবে একটিIteratorব্যবহার করে। অতিরিক্ত মেথড কল (hasNext(),next()) এবং Iterator অবজেক্টটি অ্যালোকেশনের কারণে এর ফলে ৩০-৪০টি ইন্সট্রাকশন (~১৬০ বাইট) লাগতে পারে। - R8 অপ্টিমাইজেশন : সঠিক পরিস্থিতিতে (যেমন, যখন
ListএকটিArrayListহিসেবে প্রমাণিত হয়), R8 অপ্টিমাইজার একটি foreach লুপকে পুনরায় একটি সাধারণ ইনডেক্সড লুপে রূপান্তরিত করতে পারে, যা ইটারেটরের অতিরিক্ত কাজ দূর করে এবং কোডের আকার ও রানটাইম মেমরির ব্যবহার উভয়ই হ্রাস করে।
-
- ভার্চুয়াল মেথড কল : এর জন্য অবজেক্টের ক্লাস লোড করতে হয়,
vtableএ মেথডটি খুঁজে বের করতে হয় এবং তারপর ব্রাঞ্চিং করতে হয়। এতে সাধারণত ৪-৫টি ইন্সট্রাকশন (~২০ বাইট) লাগে। - ডাইরেক্ট/স্ট্যাটিক কল : প্রায়শই একটিমাত্র
bl(ব্রাঞ্চ উইথ লিঙ্ক) নির্দেশনায় (৪ বাইট) রূপান্তরিত হয়। - কোটলিন ল্যাম্বডা : একটি সাধারণ ফাংশনাল ব্লকের জন্য সম্পূর্ণ অ্যানোনিমাস ক্লাস এবং অতিরিক্ত ব্রিজ মেথড তৈরি করতে পারে, যা শত শত বাইট কোড এবং মেটাডেটা ওভারহেড যোগ করে।
কম্পাইলার এক্সপ্লোরার ব্যবহার করে আপনি দেখতে পারেন, কীভাবে কোটলিন ল্যাম্বডা, স্ট্রিম এপিআই বা জেনেরিক্সের ব্যাপক ব্যবহারের মতো অত্যাধুনিক ল্যাঙ্গুয়েজ ফিচারগুলো আপনার অ্যাপ্লিকেশনের চূড়ান্ত কম্পাইল করা সাইজকে প্রভাবিত করে এবং কীভাবে R8-এর মতো অপটিমাইজারগুলো কিছু ক্ষেত্রে ল্যাঙ্গুয়েজ অ্যাবস্ট্রাকশনের অসুবিধাগুলো কাটিয়ে উঠতে পারে। এই টুলটি আপনাকে একটি অ্যাপ ডিজাইন ও বাস্তবায়নের ক্ষেত্রে ভেবেচিন্তে সিদ্ধান্ত নিতে সাহায্য করতে পারে।
সাধারণভাবে বলতে গেলে, আপনার অ্যাপের কোড যত বেশি জটিল হয়, মেমরির ব্যবহারও তত বাড়ে। এর বিপরীতে, সরল কোড—অথবা R8 দ্বারা সরলীকৃত কোড—সিপিইউ নির্দেশাবলী এবং স্টোরেজ ও র্যামে বাইট হিসাবে কম জায়গা নেয়।
meminfo এবং showmap ব্যবহার করে কোডের প্রভাব পরিমাপ করা
আপনার অ্যাপের কোড কী পরিমাণ মেমরি ব্যবহার করছে তা দেখতে আপনি স্ট্যান্ডার্ড অ্যান্ড্রয়েড মেমরি টুলগুলো ব্যবহার করতে পারেন।
dumpsys meminfo
যখন আপনি adb shell dumpsys meminfo <package> চালান, তখন App Summary সেকশনের Code ক্যাটাগরিটি কোড-সম্পর্কিত মেমরির একটি সামগ্রিক চিত্র প্রদান করে:
App Summary
Pss(KB)
------
Java Heap: 3244
Native Heap: 5412
Code: 24512 # <--- Sum of .so, .dex, .oat, .art, etc.
showmap
আরও বিশদ তথ্যের জন্য showmap ব্যবহার করুন। এটি নির্দিষ্ট ফাইলের সেইসব অঞ্চল প্রকাশ করে যা মেমরিতে ম্যাপ করা হচ্ছে।
adb shell showmap $(pidof <package>) | grep -E "\.oat|\.odex|\.dex|\.apk"
আপনি আপনার অ্যাপ্লিকেশনের কম্পাইল করা কোডের এন্ট্রিগুলো দেখতে পাবেন:
size RSS PSS clean dirty clean dirty swap swapPSS object
------- -------- -------- -------- -------- -------- -------- -------- -------- ----------------
12288 8192 8192 8192 0 0 0 0 0 /data/app/.../base.odex
ডেড কোড এবং R8
যেহেতু প্রতিটি সম্পাদিত মেথড মেমরি ব্যবহার করে, তাই অপ্রয়োজনীয় ইনিশিয়ালাইজেশন বা অব্যবহৃত লাইব্রেরিযুক্ত একটি "ভারী" অ্যাপ স্টার্টআপ পারফরম্যান্স এবং স্বাভাবিক মেমরি ব্যবহারের উপর মারাত্মক প্রভাব ফেলতে পারে।
এই কারণেই R8 (ProGuard)-এর মতো টুলগুলো অত্যন্ত গুরুত্বপূর্ণ। R8 আপনার অ্যাপ্লিকেশনের বাইটকোড বিশ্লেষণ করে এবং এমন সব ক্লাস বা মেথড সরিয়ে দেয় যেগুলো কখনো কল করা হয় না ("ডেড কোড স্ট্রিপিং")।
হাতে-কলমে অনুশীলন: বাড়তি খরচের মূল্য
কোডের আকারের প্রভাব দেখানোর জন্য, এমন একটি পরীক্ষার কথা বিবেচনা করুন যেখানে ৩০০টি জেনারেটেড ক্লাস (প্রতিটিতে ৫০০টি মেথড সহ) সম্বলিত একটি অ্যাপ্লিকেশনের দুটি বিল্ডের তুলনা করা হচ্ছে:
- কোডব্লোট (অপ্টিমাইজবিহীন) : এটি হলো স্ট্যান্ডার্ড, অপ্টিমাইজ না করা বিল্ড, যাতে সমস্ত জেনারেটেড ক্লাস এবং স্বতন্ত্র স্ট্রিং অন্তর্ভুক্ত থাকে।
- CodeBloatOptimized : একই সোর্স কোড, কিন্তু R8 শ্রিংকিং সক্রিয় করে কম্পাইল করা হয়েছে।
১. সময়ের আগে (AOT) সংকলন
ফাইল-ব্যাকড মেমোরির উপর প্রভাব সর্বাধিক করার জন্য, আমরা cmd package compile টুল ব্যবহার করে অ্যাপগুলোকে অ্যাহেড-অফ-টাইম (AOT) কম্পাইল করে .oat ফাইলে পরিণত করব।
adb shell cmd package compile -m speed -f com.android.codebloat
adb shell cmd package compile -m speed -f com.android.codebloat.optimized
অনুগ্রহ করে মনে রাখবেন যে এটি একটি কৃত্রিম উদাহরণ। সাধারণত, অ্যাপগুলো speed-profile কম্পাইলেশন মোড ব্যবহার করে (আরও নিচে দেখুন)।
২. চালু করুন এবং তুলনা করুন
সত্যিকারের কোল্ড স্টার্টের জন্য, যেখানে সিস্টেমকে স্টোরেজ থেকে কোড পড়তে হয়, আমরা প্রতিটি অ্যাপ চালু করার আগে কার্নেলের পেজ ক্যাশে ড্রপ করব। এর জন্য রুট অ্যাক্সেস প্রয়োজন।
অপ্টিমাইজ না করা অ্যাপটি চালু করুন:
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
এখন অপ্টিমাইজ করা অ্যাপটির জন্যও একই কাজ করুন:
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
ফলাফল
আপনি যদি App Summary সেকশনের কোড রো-টি দেখেন, তাহলে একটি বিশাল পার্থক্য দেখতে পাবেন:
- অপ্টিমাইজ না করা
Code: ~৩০,০০০ কেবি (৩০ এমবি) - অপ্টিমাইজ করা
Code: ~২,০০০ কেবি (২ এমবি)
যেহেতু R8 দেখেছে যে ওই ক্লাসগুলোর ভেতরের ৫০০টি মেথড আসলে কখনোই কোনো দরকারি কাজ করছিল না ( doSomething() মেথডটি শুধু method0() কল করে এবং এর ফলাফল উপেক্ষা করা হয়), তাই এটি চূড়ান্ত APK থেকে কৃত্রিমভাবে তৈরি প্রায় সমস্ত কোড বাদ দিয়ে দিয়েছে।
৩. পারফেত্তোতে এর প্রভাব দেখুন।
অ্যাপ্লিকেশনটির প্রাথমিক লোডিং পর্যায়ে কোড ব্লটের প্রভাব স্পষ্টভাবে দেখা যায়। বিশেষ করে, মেইন থ্রেডে bindApplication স্লাইস এবং madvising দিয়ে শুরু হওয়া নেস্টেড স্লাইসগুলো খুঁজুন, যা নির্দেশ করে যে সিস্টেমটি APK এবং এর কম্পাইল করা কোড ( .odex ) থেকে ফাইল লোড করার জন্য প্রস্তুত হচ্ছে।
একটি ইন্টারেক্টিভ কোল্ড স্টার্টের সময়, সিস্টেমটি অ্যাপটি লোড ও রান করার জন্য প্রয়োজনীয় কোড এবং অন্যান্য ডেটা এই ফাইলগুলি থেকে mmap() এবং madvise() পদ্ধতিতে সংগ্রহ করে। madvising স্লাইসগুলিতে "size=" এর পরের মানটি নির্দেশ করে যে কী পরিমাণ ডেটা লোড করতে হবে। অ্যাপের স্টার্টআপকে ত্বরান্বিত করার জন্য এর কোডের এই প্রিফেচিং করা হয়।
তুলনা থেকে আমরা দেখতে পাই যে, স্ফীত অ্যাপটির ক্ষেত্রে স্টোরেজ থেকে র্যামে অ্যাপ কোড লোড করার পরিমাণ অনেক বেশি ছিল, যার ফলে লোড হতে বেশি সময় লেগেছে এবং এটি অ্যাপটি চালু হতে ধীরগতির কারণ হয়েছে। এছাড়াও, স্ফীত অ্যাপটির স্টার্ট ট্রেসে সেকেন্ডারি DEX ফাইল ( classes2.dex , classes3.dex ) লোড করার জন্য স্লাইস দেখা যায়, যেগুলোতে স্ফীত অ্যাপটিকে কোড "স্পিল" করতে বাধ্য হতে হয়েছিল কারণ তা একটি DEX ফাইলে ধরছিল না।
তুলনামূলকভাবে (পিক্সেল ১০এ-তে কোল্ড স্টার্ট)
| মেট্রিক | অপ্টিমাইজ না করা (কোডব্লোট) | অপ্টিমাইজড (কোডব্লোটঅপ্টিমাইজড) |
|---|---|---|
base.odex madvise size | ~৭.৯ এমবি (২.০ মিলিসেকেন্ড) | ~১৬ কেবি (০.০০৩ মিলিসেকেন্ড) |
base.apk madvise size | ~২.৪ এমবি (২.৪ মিলিসেকেন্ড) | ~৪ কেবি (০.০০১ মিলিসেকেন্ড) |
classes2.dex ম্যাডভাইস সাইজ | ~৭.৩ এমবি (৮.৬ মিলিসেকেন্ড) | প্রযোজ্য নয় |
classes3.dex ম্যাডভাইস সাইজ | ~৭.৩ এমবি (৮.০ মিলিসেকেন্ড) | প্রযোজ্য নয় |
মোট madvising সময়কাল | ~২১ মিলিসেকেন্ড | ~০.০০৪ মিলিসেকেন্ড |
অপ্টিমাইজ না করা অ্যাপ লোডিং পারফরম্যান্স

অ্যাপ লোডিং পারফরম্যান্স অপ্টিমাইজ করা হয়েছে

অ্যাপের আকার, ব্যবহারকারীর ডিভাইসের বৈশিষ্ট্য এবং সিস্টেম লোডের উপর কোড ব্লটের প্রভাব নির্ভর করে।
লোড বিশ্লেষণের জন্য পারফেটটোএসকিউএল
আপনার ট্রেস থেকে এই মেট্রিকগুলো বের করতে আপনি নিম্নলিখিত কোয়েরিগুলো ব্যবহার করতে পারেন।
১. অ্যাপ চালু হওয়ার সময়কাল
এটি কোনো অ্যাপের অ্যাক্টিভিটি চালু হওয়ার মুহূর্ত থেকে শুরু করে অ্যাক্টিভিটিটি প্রথম ফ্রেমটি অঙ্কন করা পর্যন্ত সময় নির্দেশ করে।
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT package, dur, startup_type
FROM android_startups
WHERE package LIKE 'com.android.codebloat%';
দেখুন: অ্যাপের বিভিন্ন স্টার্টআপ অবস্থা বুঝুন।
এই নির্দেশিকায় আলোচিত বিষয়গুলো ছাড়াও একটি অ্যাপের চালু হওয়ার সময়কাল আরও অনেক বিষয়ের উপর নির্ভর করে!
২. madvising আকার এবং সময়কাল বের করুন
এই কোয়েরিটি উপরে দেখা madvising অংশটিকে আরও বিশদভাবে তুলে ধরে।
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 %';
৩. প্রধান থ্রেডের অবস্থার বিভাজন (প্রতিটি অবস্থার মোট সময়কাল)
এই কোয়েরিটি দেখায় যে অ্যাপটির প্রধান থ্রেড বিভিন্ন অবস্থায় কত সময় ব্যয় করেছে।
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;
আপনি কোয়েরিটিকে এমনভাবে পরিমার্জন করতে পারেন যাতে এটি শুধুমাত্র অ্যাপটি চালু হওয়ার সময়কালের প্রধান থ্রেডের অবস্থাগুলো পরীক্ষা করে।
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;
এর ফলে কিছু আকর্ষণীয় সমস্যা সামনে আসতে পারে, যেমন:
- অনেক সময় ব্যয় হয়েছে রানযোগ্য (R) কিন্তু চলছে না : এটি নির্দেশ করে যে সিপিইউ-এর সাথে প্রতিযোগিতার কারণে অ্যাপটির চালু হতে দেরি হয়েছে, অর্থাৎ অ্যাপটির প্রধান থ্রেডটি চলতে পারেনি কারণ অন্যান্য থ্রেড (সম্ভবত অন্য অ্যাপের) সিপিইউ দখল করে রেখেছিল।
- ইন্টারাপ্টেবল স্লিপ (D)-এ দীর্ঘ সময় অতিবাহিত হওয়া : এটি সাধারণত ধীরগতির I/O অথবা মেমোরির উপর অতিরিক্ত চাপ নির্দেশ করে, যা অ্যাপটির চালু হওয়াকে বাধাগ্রস্ত করছে।
- দীর্ঘ সময় ধরে ঘুমানো (S) : এর অর্থ হলো, প্রধান থ্রেডটি অন্য থ্রেডগুলোর কাজ করার জন্য অপেক্ষা করছিল। কখনও কখনও এটি অ্যাপের স্টার্টআপ পাথে লক কনটেনশন নির্দেশ করে (অর্থাৎ, প্রধান থ্রেডটি অ্যাপের অন্য কোনো থ্রেড দ্বারা দখলকৃত একটি এক্সক্লুসিভ রিসোর্সে আটকে ছিল)।
৪. ফাইল-সমর্থিত সর্বোচ্চ মেমরি (আরএসএস ফাইল)
এই মেট্রিকটি অ্যাপটি চালু হওয়ার সময় কী পরিমাণ কোড এবং ডেটা লোড করে তার সাথে ভালোভাবে সম্পর্কযুক্ত। একটি বেশি "ভারী" অ্যাপের ক্ষেত্রে এই সংখ্যাটি বেশি হয়, যা সিস্টেমের মেমোরির উপর চাপ সৃষ্টি করে। এই ধরনের চাপ ফলস্বরূপ অ্যাপটির চালু হতে দেরি ঘটাতে পারে, কারণ সিস্টেম অ্যালোকেশনের অনুরোধগুলো মেটাতে হিমশিম খায়, অথবা অ্যাপটি চালু করার পরিবর্তে সিপিইউ-এর সময় অন্য প্রসেস থেকে মেমোরি পুনরুদ্ধারের দিকে চলে যায়, যাতে চালু হতে চলা অ্যাপটির তাৎক্ষণিক চাহিদা মেটানো যায়।
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 সংকলন মোড এবং মেমরি
অ্যান্ড্রয়েড রানটাইম (ART) আপনার অ্যাপ্লিকেশন কোডকে বিভিন্ন মোডের যেকোনো একটিতে কম্পাইল করতে পারে, যা কম্পাইলার ফিল্টার নামেও পরিচিত। নির্বাচিত কম্পাইলার ফিল্টারটি আপনার অ্যাপের মেমরি ফুটপ্রিন্টের উপর সরাসরি প্রভাব ফেলে।
-
verify: ART শুধুমাত্র বাইটকোড যাচাই করে। কোনো AOT কম্পাইলেশন করা হয় না। কোড ইন্টারপ্রেটারের মাধ্যমে এক্সিকিউট করা হয় অথবা রানটাইমে JIT কম্পাইলার দ্বারা কম্পাইল করা হয়।- মেমরি ইমপ্যাক্ট : ডিস্কে সর্বনিম্ন আকার। নেটিভ কোডের মেমরি ব্যবহার
JIT Cache(অনামী ডার্টি মেমরি) স্থানান্তরিত হয়।
- মেমরি ইমপ্যাক্ট : ডিস্কে সর্বনিম্ন আকার। নেটিভ কোডের মেমরি ব্যবহার
-
speed: ART সকল মেথডের সম্পূর্ণ AOT কম্পাইলেশন সম্পাদন করে।- মেমরির উপর প্রভাব : বৃহত্তম
.odexসাইজ। ফাইল-সমর্থিত (পরিষ্কার) মেমরি ব্যবহার সর্বাধিক করে।
- মেমরির উপর প্রভাব : বৃহত্তম
-
speed-profile: ART শুধুমাত্র সেইসব মেথড কম্পাইল করে যেগুলোকে একটি JIT প্রোফাইলে "হট" হিসেবে চিহ্নিত করা হয়েছে।- মেমোরির উপর প্রভাব : ভারসাম্যপূর্ণ পদ্ধতি। শুধুমাত্র সবচেয়ে গুরুত্বপূর্ণ কোডই AOT কম্পাইল করা হয়।
সবচেয়ে প্রচলিত ফিল্টার হলো speed-profile , যা ইউজার অ্যাপ ইনস্টল করার সময় ব্যবহৃত হয়। এটি সিস্টেম প্রপার্টিজ pm.dexopt.install এবং pm.dexopt.bg-dexopt এ কনফিগার করা হয় এবং সাধারণত build/make/target/product/runtime_libart.mk এ সেট করা থাকে।
কিছু সিস্টেম অ্যাপ speed কম্পাইলেশন ব্যবহার করে এবং সিস্টেম ইমেজ বিল্ড করার সময়েই কম্পাইল হয়। verify সাধারণত শুধু ডেভেলপমেন্টের ক্ষেত্রেই ব্যবহৃত হয়।
| ব্যবহারের ক্ষেত্র | সাধারণ কম্পাইলার ফিল্টার |
|---|---|
| উন্নয়ন | verify |
| সিস্টেম ইমেজ | speed |
| ব্যবহারকারী অ্যাপস | speed-profile |
হাতে-কলমে অনুশীলন: কম্পাইলেশন মোড এবং মেমরি
এই ফিল্টারগুলো মেমরিকে কীভাবে প্রভাবিত করে তা দেখতে আমরা CodeBloat অ্যাপটি ব্যবহার করতে পারি। এই পরিমাপগুলো পুনরায় করতে:
- অ্যাপটিকে জোরপূর্বক টার্গেট মোডে পুনরায় কম্পাইল করুন।
- অ্যাপটি জোর করে বন্ধ করুন এবং পরে আবার চালু করুন।
- ব্যাকগ্রাউন্ড থ্রেডটি ক্লাসগুলোর কাজ শেষ করা পর্যন্ত অপেক্ষা করুন (লগক্যাট দেখুন অথবা ৫ সেকেন্ড অপেক্ষা করুন)।
-
adb shell dumpsys meminfo com.android.codebloat. কমান্ডটি চালান।
মোড: verify (AOT ছাড়া)
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 মোডে, অ্যাপের সারাংশে দেখাচ্ছে: * কোড পিএসএস : ~৮,০০০ কেবি * ডালভিক আদার (জেআইটি) : ~২৫,০০০ কেবি
যেহেতু কোনো কোড AOT তে কম্পাইল করা হয় না, তাই রানটাইমকে অবশ্যই হট মেথডগুলোকে JIT ক্যাশে JIT-কম্পাইল করতে হয়, যা ডার্টি অ্যানোনিমাস মেমরি ( Dalvik Other ) হিসেবে দেখা যায়।
মোড: speed (সম্পূর্ণ 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 মোডে, ফলাফলে নাটকীয় পরিবর্তন আসে: * কোড পিএসএস : ~২৪,০০০ কেবি * ডালভিক আদার (জেআইটি) : ~৫,০০০ কেবি
অ্যাপ্লিকেশনটির কোড এখন .odex ফাইল থেকে ক্লিন ফাইল-ব্যাকড মেমরি হিসেবে ম্যাপ করা হয়। এর ফলে JIT ক্যাশের উপর চাপ কমে এবং মেমরিটি ডার্টি র্যাম হিসেবে "আটকে" না থেকে, চাপের মুখে খালি করার জন্য উপযুক্ত হয়ে ওঠে।
মোড: speed-profile (সিলেক্টিভ AOT)
আধুনিক অ্যাপগুলো একটি baseline.prof বেসলাইন প্রোফাইল অন্তর্ভুক্ত করতে পারে। ART এটি ব্যবহার করে দ্রুত এবং মেমরি-সাশ্রয়ী স্টার্টআপের জন্য শুধুমাত্র প্রয়োজনীয় কোড বেছে বেছে কম্পাইল করে।
এই অনুশীলনে আমরা অ্যাপটির স্টার্টআপ ক্লাসগুলো তালিকাভুক্ত করার জন্য একটি বেসলাইন প্রোফাইল তৈরি করব। তবে বাস্তবে কম্পাইলার অ্যাপ্লিকেশন স্টোরের ("ক্লাউড প্রোফাইল") মতো বাহ্যিক উৎস থেকেও প্রোফাইল পেতে পারে, যা ডেভেলপার নিজে তৈরি করা বেসলাইন প্রোফাইল অন্তর্ভুক্ত করেছে কি না, তা নির্বিশেষে অ্যাপগুলোর জন্য ক্রাউডসোর্সড JIT প্রোফাইল সরবরাহ করতে পারে।
ডিভাইসে প্রোফাইল তৈরি এবং ব্যবহার করা
speed-profile প্রভাব দেখতে, আপনি ডিভাইসেই আপনার নিজস্ব প্রোফাইল তৈরি করতে পারেন:
রিসেট এবং শুরু করুন :
adb shell am force-stop com.android.codebloatইন্টারঅ্যাক্ট করুন : অ্যাপটি চালু করুন এবং এটিকে এর স্টার্টআপ সিকোয়েন্সটি চলতে দিন।
ডাম্প প্রোফাইল :
adb shell kill -s SIGUSR1 $(pidof com.android.codebloat)(এর ফলে অ্যাপটি তার বর্তমান প্রোফাইল ডিস্কে লিখে রাখতে বাধ্য হয়)।
প্রোফাইল ইনস্টল করুন :
adb shell cp /data/misc/profiles/cur/0/com.android.codebloat/primary.prof \ /data/misc/profiles/ref/com.android.codebloat/primary.profসংকলন করুন :
adb shell cmd package compile -m speed-profile -f com.android.codebloat
আপনি যখন আবার চালু করবেন, তখন একটি ভারসাম্য দেখতে পাবেন: কোড PSS speed চেয়ে কম হবে (যেমন, ~১৬,০০০ KB), কারণ শুধুমাত্র 'হট' স্টার্টআপ মেথডগুলো কম্পাইল করা হয়েছিল, এবং বাকি অংশগুলো ইন্টারপ্রেটার বা JIT দ্বারা পরিচালিত হবে, যদি সেগুলো কখনও বাস্তবে ব্যবহৃত হয়।
দেখুন:
কম্পাইল করা কোডের গভীরে অনুসন্ধান
ART ঠিক কী নির্দেশনা তৈরি করছে তা দেখতে চাইলে art/DISASSEMBLY_GUIDE.md দেখুন।
এটি ব্যবহারের বিষয়ে বিস্তারিত নির্দেশাবলী প্রদান করে:
-
oatdump: বিদ্যমান কোনো.odexফাইলের ভেতরের ARM64 নির্দেশাবলী দেখার জন্য। -
dex2oat: বিশদ ডিবাগ ফ্ল্যাগ সহ কম্পাইলেশন অনুকরণ করতে।
অনুশীলন: কোড ইনলাইনিং
কম্পাইল করা কোড অপ্রত্যাশিতভাবে বড় হয়ে যাওয়ার একটি কারণ হলো মেথড ইনলাইনিং । কম্পাইলার কোনো ছোট এবং ঘন ঘন কল করা মেথডের বডি সরাসরি তার কলারদের মধ্যে কপি করে দেওয়ার সিদ্ধান্ত নিতে পারে।
আমাদের CodeBloat অ্যাপে, প্রতিটি জেনারেটেড ক্লাসের doSomething() মেথডটি কেবল method0() মেথডটিকে কল করে। speed মোডে কম্পাইল করা হলে, ART-এর অপটিমাইজিং কম্পাইলার সম্ভবত method0() doSomething() মেথডের ভেতরে ইনলাইন করে দেবে।
অনুশীলন: আপনার ডিভাইসে oatdump ব্যবহার করে এটি যাচাই করুন:
# 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
আউটপুটে doSomething মেথডটি খুঁজুন। যদি এটি ইনলাইন করা থাকে, তাহলে আপনি method0 টার্গেট করা কোনো bl ইন্সট্রাকশনের পরিবর্তে, doSomething ভেতরে সরাসরি লং স্ট্রিং কনস্ট্যান্ট লোড করার নির্দেশনাগুলো দেখতে পাবেন।
অপ্টিমাইজেশনের দৃশ্যায়ন (CFG)
কম্পাইলার ঠিক কখন মেথডটিকে ইনলাইন করার সিদ্ধান্ত নিয়েছে তা দেখতে, আপনি একটি কন্ট্রোল ফ্লো গ্রাফ (CFG) তৈরি করতে পারেন। এটি অপটিমাইজেশন পাইপলাইনের প্রতিটি পর্যায়ে কোডের অবস্থা দেখায়, যেখানে কম্পাইলারের ইন্টারমিডিয়েট রিপ্রেজেন্টেশন (IR)-এর প্রতিটি রূপান্তর অন্তর্ভুক্ত থাকে, যতক্ষণ না কোডটিকে টার্গেট ISA (যেমন ARM64)-তে লোয়ার করা হয়।
ডাম্প ফ্ল্যাগ সহ
dex2oatচালান : আউটপুটকে নির্দিষ্ট মেথডগুলিতে সীমাবদ্ধ করতে--verbose-methodsফ্ল্যাগটি ব্যবহার করুন; অন্যথায়, একটি বড় অ্যাপের.cfgফাইলের আকার কয়েক গিগাবাইট পর্যন্ত বেড়ে যেতে পারে।# 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পুল করুন এবং দেখুন :
.cfgফাইলটি আপনার ওয়ার্কস্টেশনে পুল করুন এবং IR Hydra দিয়ে খুলুন।ইনলাইনারটি খুঁজুন : IR Hydra-তে, কম্পাইলেশন আর্টিফ্যাক্টগুলো লোড করুন এবং
doSomethingঅনুসন্ধান করুন। ইনলাইনার পাসের আগে ও পরের রিপ্রেজেন্টেশন তুলনা করুন। আপনি দেখবেন যে,method0এর নির্দেশাবলী কলারে একীভূত হওয়ার সাথে সাথে গ্রাফটি প্রসারিত হচ্ছে।
বিকল্পভাবে, কম্পাইলার এক্সপ্লোরারে (উপরের বিভাগে বর্ণিত) অপ্ট পাইপলাইন টুলটি ব্যবহার করুন এবং ইনলাইনার পাসে অনুরূপ রূপান্তর দেখতে একই রকম কোড লিখুন।
অনুশীলন: উদ্বায়ী ক্ষেত্র এবং স্মৃতি প্রতিবন্ধকতা
MemoryLab অ্যাপে, mGarbageSink ফিল্ডটিকে volatile হিসেবে চিহ্নিত করা হয়েছে। এটি নিশ্চিত করে যে কম্পাইলার আমাদের গার্বেজ অ্যালোকেশনগুলোকে অপ্টিমাইজ করে বাদ দিয়ে দেবে না।
public volatile byte[] mGarbageSink;
ARM64 ডিসঅ্যাসেম্বলিতে আপনি দেখতে পাবেন যে, এই ফিল্ডে প্রতিটি স্টোর অপারেশনের সাথে একটি মেমোরি ব্যারিয়ার ( dmb ish ) অথবা লোড-অ্যাকোয়ার/স্টোর-রিলিজ ইনস্ট্রাকশন ( ldar / stlr ) ব্যবহার করা হয়। এটি থ্রেডের দৃশ্যমানতা নিশ্চিত করে, কিন্তু প্রতিটি অ্যাক্সেসে কয়েকটি অতিরিক্ত ইনস্ট্রাকশন যোগ করে, যা একটি সাধারণ ফিল্ডের তুলনায় কোডের আকার সামান্য বাড়িয়ে দেয়।
অনুশীলন: ডিসঅ্যাসেম্বলিতে ফিল্ড অ্যাক্সেস এবং সংশ্লিষ্ট মেমোরি ব্যারিয়ারগুলো খুঁজে বের করুন।
অনুশীলন: অন্তর্নিহিত স্থগিতকরণ পরীক্ষা
আপনি যদি generateAllocationChurn এর মতো কোনো লুপকে ডিসঅ্যাসেম্বল করেন, তাহলে লুপ বডির শেষে একটি অদ্ভুত ইনস্ট্রাকশন আপনার চোখে পড়বে:
ldr x21, [x21]
এটি একটি ইমপ্লিসিট সাসপেন্ড চেক । ART এটি ব্যবহার করে গার্বেজ কালেক্টরকে নিরাপদে থ্রেড পজ করার সুযোগ দেয়। রেজিস্টার x21 সাধারণত নিজেকেই নির্দেশ করে। যখন GC-কে থ্রেডটি সাসপেন্ড করতে হয়, তখন এটি ঐ মেমোরি লোকেশনটিকে "পয়জন" করে। পরের বার যখন থ্রেডটি ঐ ldr এক্সিকিউট করবে, তখন এটি একটি ফল্ট ট্রিগার করবে, যা রানটাইম ধরে ফেলে এবং থ্রেডটিকে সাসপেন্ডেড অবস্থায় নিয়ে যেতে ব্যবহার করে।
এই প্যাটার্নটি প্রতিটি লুপে এবং প্রতিটি মেথডের শুরুতে পুনরাবৃত্তি হয়, যা আপনার অ্যাপ্লিকেশনের মোট কোডের আকার বাড়াতে অবদান রাখে।
অনুশীলন: মেথড ডিসঅ্যাসেম্বলিতে থাকা সমস্ত ইমপ্লিসিট সাসপেন্ড চেকগুলো খুঁজে বের করুন এবং সেগুলোকে মূল সোর্স কোডের সাথে মিলিয়ে দেখার চেষ্টা করুন।
← ওয়েবভিউ | ↑ উপরে | থ্রেডস →