পণ্যের খবর

নেপথ্যে: অ্যান্ড্রয়েড ১৭-এর লক-মুক্ত মেসেজকিউ

১৬ মিনিটের পাঠ
Charles Munger এবং Shai Barack

অ্যান্ড্রয়েড ১৭-এ, SDK ৩৭ বা তার উচ্চতর সংস্করণকে টার্গেট করা অ্যাপগুলো MessageQueue-এর একটি নতুন ইমপ্লিমেন্টেশন পাবে, যা লক-ফ্রি। এই নতুন ইমপ্লিমেন্টেশন পারফরম্যান্স উন্নত করে এবং মিসড ফ্রেম কমায়, কিন্তু এটি সেইসব ক্লায়েন্টকে ভেঙে দিতে পারে যারা MessageQueue-এর প্রাইভেট ফিল্ড এবং মেথড রিফ্লেক্ট করে। এই আচরণগত পরিবর্তন এবং এর প্রভাব কীভাবে কমানো যায় সে সম্পর্কে আরও জানতে, MessageQueue-এর আচরণগত পরিবর্তন সংক্রান্ত ডকুমেন্টেশন দেখুন । এই টেকনিক্যাল ব্লগ পোস্টে MessageQueue-এর রিআর্কিটেকচারের একটি ওভারভিউ এবং Perfetto ব্যবহার করে কীভাবে লক কনটেনশন সমস্যা বিশ্লেষণ করা যায়, তা দেওয়া হয়েছে।

লুপার প্রতিটি অ্যান্ড্রয়েড অ্যাপ্লিকেশনের UI থ্রেডকে চালনা করে। এটি একটি MessageQueue থেকে কাজ গ্রহণ করে, সেটিকে একটি Handler-এর কাছে প্রেরণ করে এবং এই প্রক্রিয়াটি পুনরাবৃত্তি করে। দুই দশক ধরে, MessageQueue তার অবস্থা সুরক্ষিত রাখতে একটিমাত্র মনিটর লক (অর্থাৎ একটি synchronized কোড ব্লক) ব্যবহার করত।

অ্যান্ড্রয়েড ১৭ এই কম্পোনেন্টটিতে একটি গুরুত্বপূর্ণ আপডেট এনেছে: DeliQueue নামের একটি লক-মুক্ত ইমপ্লিমেন্টেশন।

এই পোস্টে ব্যাখ্যা করা হয়েছে কীভাবে লক UI পারফরম্যান্সকে প্রভাবিত করে, পারফেট্টো (Perfetto) ব্যবহার করে কীভাবে এই সমস্যাগুলো বিশ্লেষণ করা যায় এবং অ্যান্ড্রয়েড মেইন থ্রেড উন্নত করতে ব্যবহৃত নির্দিষ্ট অ্যালগরিদম ও অপটিমাইজেশনগুলো কী কী।

সমস্যাটি হলো: লক কনটেনশন এবং প্রায়োরিটি ইনভার্সন।

পুরোনো MessageQueue একটি একক লক দ্বারা সুরক্ষিত প্রায়োরিটি কিউ হিসেবে কাজ করত। যদি প্রধান থ্রেড কিউ রক্ষণাবেক্ষণ করার সময় কোনো ব্যাকগ্রাউন্ড থ্রেড একটি মেসেজ পোস্ট করে, তবে ব্যাকগ্রাউন্ড থ্রেডটি প্রধান থ্রেডকে ব্লক করে দেয়।

যখন দুই বা ততোধিক থ্রেড একই লকের একচেটিয়া ব্যবহারের জন্য প্রতিযোগিতা করে, তখন তাকে লক কনটেনশন (Lock contention ) বলা হয়। এই কনটেনশনের কারণে প্রায়োরিটি ইনভার্সন (Priority Inversion) হতে পারে, যা ইউআই জ্যাঙ্ক (UI jank) এবং অন্যান্য পারফরম্যান্স সমস্যার কারণ হয়।

প্রায়োরিটি ইনভার্সন ঘটতে পারে যখন একটি উচ্চ-প্রায়োরিটির থ্রেডকে (যেমন UI থ্রেড) একটি নিম্ন-প্রায়োরিটির থ্রেডের জন্য অপেক্ষা করানো হয়। এই ক্রমটি বিবেচনা করুন:

  1. একটি নিম্ন অগ্রাধিকারের ব্যাকগ্রাউন্ড থ্রেড তার সম্পাদিত কাজের ফলাফল পোস্ট করার জন্য MessageQueue লকটি অধিগ্রহণ করে।
  2. একটি মাঝারি অগ্রাধিকারের থ্রেড চালানোর যোগ্য হয়ে উঠলে, কার্নেলের শিডিউলার নিম্ন অগ্রাধিকারের থ্রেডটিকে সরিয়ে দিয়ে সেটির জন্য সিপিইউ সময় বরাদ্দ করে।
  3. উচ্চ অগ্রাধিকারের UI থ্রেডটি তার বর্তমান কাজটি শেষ করে কিউ থেকে পড়ার চেষ্টা করে, কিন্তু নিম্ন অগ্রাধিকারের থ্রেডটি লকটি ধরে রাখায় এটি আটকে যায়।

নিম্ন-অগ্রাধিকারের থ্রেডটি UI থ্রেডকে ব্লক করে, এবং মধ্যম-অগ্রাধিকারের কাজটি এটিকে আরও বিলম্বিত করে।

perfetto1.png

পারফেটোর সাথে বিরোধ বিশ্লেষণ

আপনিপারফেটটো ব্যবহার করে এই সমস্যাগুলো নির্ণয় করতে পারেন। একটি স্ট্যান্ডার্ড ট্রেসে, মনিটর লকে ব্লক থাকা একটি থ্রেড স্লিপিং স্টেটে প্রবেশ করে, এবং পারফেটটো একটি স্লাইস দেখায় যা লকটির মালিককে নির্দেশ করে।

ট্রেস ডেটা কোয়েরি করার সময়, “monitor contention with …” নামের স্লাইসগুলো খুঁজুন, যার পরে লকটির মালিক থ্রেডের নাম এবং যে কোড সাইট থেকে লকটি অর্জিত হয়েছিল তার নাম থাকবে।

কেস স্টাডি: লঞ্চারের জ্যাঙ্ক

উদাহরণস্বরূপ, আসুন এমন একটি ট্রেস বিশ্লেষণ করি যেখানে একজন ব্যবহারকারী ক্যামেরা অ্যাপে ছবি তোলার ঠিক পরেই একটি পিক্সেল ফোনে হোম স্ক্রিনে যাওয়ার সময় জ্যাঙ্কের সম্মুখীন হয়েছিলেন। নিচে আমরা পারফেটোর একটি স্ক্রিনশট দেখতে পাচ্ছি, যেখানে মিস হওয়া ফ্রেমটির পূর্ববর্তী ঘটনাগুলো দেখানো হয়েছে:

launcherJ.png
  • সমস্যা: লঞ্চারের প্রধান থ্রেড তার ফ্রেম ডেডলাইন মিস করেছে। এটি ১৮ মিলিসেকেন্ডের জন্য ব্লক হয়ে গিয়েছিল, যা ৬০ হার্টজ রেন্ডারিংয়ের জন্য প্রয়োজনীয় ১৬ মিলিসেকেন্ডের ডেডলাইন অতিক্রম করেছে।
  • নির্ণয়: পারফেট্টো দেখিয়েছে যে প্রধান থ্রেডটি MessageQueue লকে আটকে ছিল। একটি “BackgroundExecutor” থ্রেড লকটির মালিক ছিল।
  • মূল কারণ: BackgroundExecutor থ্রেডটি Process.THREAD_PRIORITY_BACKGROUND (অত্যন্ত নিম্ন প্রায়োরিটি)-তে চলে। এটি একটি অ-জরুরি কাজ ( অ্যাপ ব্যবহারের সীমা পরীক্ষা করা) সম্পাদন করছিল। একই সময়ে, মাঝারি প্রায়োরিটির থ্রেডগুলো ক্যামেরা থেকে আসা ডেটা প্রসেস করার জন্য সিপিইউ-এর সময় ব্যবহার করছিল। অপারেটিং সিস্টেমের শিডিউলার ক্যামেরা থ্রেডগুলো চালানোর জন্য BackgroundExecutor থ্রেডটিকে সরিয়ে দেয়।

এই ঘটনার ক্রমটির ফলে লঞ্চারের UI থ্রেড (উচ্চ অগ্রাধিকার) ক্যামেরা ওয়ার্কার থ্রেড (মাঝারি অগ্রাধিকার) দ্বারা পরোক্ষভাবে অবরুদ্ধ হয়ে পড়ে, যেটি লঞ্চারের ব্যাকগ্রাউন্ড থ্রেডকে (নিম্ন অগ্রাধিকার) লকটি মুক্ত করতে বাধা দিচ্ছিল।

PerfettoSQL দিয়ে ট্রেস কোয়েরি করা

আপনি নির্দিষ্ট প্যাটার্ন খুঁজে বের করার জন্য ট্রেস ডেটা কোয়েরি করতে PerfettoSQL ব্যবহার করতে পারেন। এটি তখন কাজে আসে যখন আপনার কাছে ব্যবহারকারীর ডিভাইস বা পরীক্ষা থেকে পাওয়া বিপুল পরিমাণ ট্রেস থাকে এবং আপনি এমন নির্দিষ্ট ট্রেস খুঁজছেন যা কোনো সমস্যা তুলে ধরে।

উদাহরণস্বরূপ, এই কোয়েরিটি ড্রপ হওয়া ফ্রেমের (জ্যাঙ্ক) সাথে একই সময়ে ঘটা MessageQueue কনটেনশন খুঁজে বের করে:

INCLUDE PERFETTO MODULE android.monitor_contention;
INCLUDE PERFETTO MODULE android.frames.jank_type;

SELECT
  process_name,
  -- Convert duration from nanoseconds to milliseconds
  SUM(dur) / 1000000 AS sum_dur_ms,
  COUNT(*) AS count_contention
FROM android_monitor_contention
WHERE is_blocked_thread_main
AND short_blocked_method LIKE "%MessageQueue%" 

-- Only look at app processes that had jank
AND upid IN (
  SELECT DISTINCT(upid)
  FROM actual_frame_timeline_slice
  WHERE android_is_app_jank_type(jank_type) = TRUE
)
GROUP BY process_name
ORDER BY SUM(dur) DESC;

এই আরও জটিল উদাহরণে, অ্যাপ চালু হওয়ার সময় MessageQueue-এর দ্বন্দ্ব শনাক্ত করতে একাধিক টেবিল জুড়ে থাকা ট্রেস ডেটা যুক্ত করুন:

INCLUDE PERFETTO MODULE android.monitor_contention; 
INCLUDE PERFETTO MODULE android.startup.startups; 

-- Join package and process information for startups
DROP VIEW IF EXISTS startups; 
CREATE VIEW startups AS 
SELECT startup_id, ts, dur, upid 
FROM android_startups 
JOIN android_startup_processes USING(startup_id); 

-- Intersect monitor contention with startups in the same process.
DROP TABLE IF EXISTS monitor_contention_during_startup; 
CREATE VIRTUAL TABLE monitor_contention_during_startup 
USING SPAN_JOIN(android_monitor_contention PARTITIONED upid, startups PARTITIONED upid); 

SELECT 
  process_name, 
  SUM(dur) / 1000000 AS sum_dur_ms, 
  COUNT(*) AS count_contention 
FROM monitor_contention_during_startup 
WHERE is_blocked_thread_main 
AND short_blocked_method LIKE "%MessageQueue%" 
GROUP BY process_name 
ORDER BY SUM(dur) DESC;

অন্যান্য প্যাটার্ন খুঁজে বের করার জন্য আপনি আপনার পছন্দের এলএলএম (LLM) ব্যবহার করে পারফেটটোএসকিউএল (PerfettoSQL) কোয়েরি লিখতে পারেন।

গুগলে আমরা লক্ষ লক্ষ ট্রেসের উপর পারফেটটোএসকিউএল (PerfettoSQL) কোয়েরি চালানোর জন্য বিগট্রেস (BigTrace) ব্যবহার করি। এর মাধ্যমে আমরা নিশ্চিত হয়েছি যে, আমরা বিচ্ছিন্নভাবে যা দেখছিলাম তা আসলে একটি সিস্টেমিক সমস্যা ছিল। ডেটা থেকে প্রকাশ পেয়েছে যে, MessageQueue লক কনটেনশন পুরো ইকোসিস্টেম জুড়ে ব্যবহারকারীদের প্রভাবিত করে, যা একটি মৌলিক আর্কিটেকচারাল পরিবর্তনের প্রয়োজনীয়তাকে প্রমাণ করে।

সমাধান: লক-ফ্রি কনকারেন্সি

আমরা শেয়ার্ড স্টেটে অ্যাক্সেস সিনক্রোনাইজ করার জন্য এক্সক্লুসিভ লকের পরিবর্তে অ্যাটমিক মেমরি অপারেশন ব্যবহার করে একটি লক-ফ্রি ডেটা স্ট্রাকচার বাস্তবায়নের মাধ্যমে MessageQueue কনটেনশন সমস্যার সমাধান করেছি। একটি ডেটা স্ট্রাকচার বা অ্যালগরিদমকে লক-ফ্রি বলা হয় যদি অন্য থ্রেডগুলোর শিডিউলিং আচরণ নির্বিশেষে অন্তত একটি থ্রেড সর্বদা তার কাজ এগিয়ে নিয়ে যেতে পারে। এই বৈশিষ্ট্যটি অর্জন করা সাধারণত কঠিন, এবং বেশিরভাগ কোডের জন্য এটি অর্জনের চেষ্টা করা সাধারণত লাভজনক নয়

পারমাণবিক আদিম

লক-ফ্রি সফটওয়্যার প্রায়শই হার্ডওয়্যার দ্বারা প্রদত্ত অ্যাটমিক রিড-মডিফাই-রাইট প্রিমিটিভগুলোর উপর নির্ভর করে।

পুরোনো প্রজন্মের ARM64 সিপিইউ-তে অ্যাটমিকস একটি লোড-লিঙ্ক/স্টোর-কন্ডিশনাল (LL/SC) লুপ ব্যবহার করত। সিপিইউ একটি ভ্যালু লোড করে এবং অ্যাড্রেসটি চিহ্নিত করে। যদি অন্য কোনো থ্রেড সেই অ্যাড্রেসে লেখে, তাহলে স্টোর প্রক্রিয়াটি ব্যর্থ হয় এবং লুপটি পুনরায় চেষ্টা করে। যেহেতু থ্রেডগুলো অন্য কোনো থ্রেডের জন্য অপেক্ষা না করেই বারবার চেষ্টা করে সফল হতে পারে, তাই এই অপারেশনটি লক-ফ্রি।

ARM64 LL/SC loop example
retry:
    ldxr    x0, [x1]        // Load exclusive from address x1 to x0
    add     x0, x0, #1      // Increment value by 1
    stxr    w2, x0, [x1]    // Store exclusive.
                            // w2 gets 0 on success, 1 on failure
    cbnz    w2, retry       // If w2 is non-zero (failed), branch to retr

( কম্পাইলার এক্সপ্লোরারে দেখুন )

নতুন ARM আর্কিটেকচার (ARMv8.1) লার্জ সিস্টেম এক্সটেনশন (LSE) সমর্থন করে, যার মধ্যে কম্পেয়ার-অ্যান্ড-সোয়াপ (CAS) বা লোড-অ্যান্ড-অ্যাড (নিচে দেখানো হয়েছে) ধরনের নির্দেশাবলী অন্তর্ভুক্ত থাকে। অ্যান্ড্রয়েড ১৭-এ আমরা অ্যান্ড্রয়েড রানটাইম (ART) কম্পাইলারে এই বৈশিষ্ট্যটি যুক্ত করেছি যে, কখন LSE সমর্থিত তা শনাক্ত করে অপ্টিমাইজ করা নির্দেশাবলী তৈরি করা যায়।

/ ARMv8.1 LSE atomic example
ldadd   x0, x1, [x2]    // Atomic load-add.
                        // Faster, no loop required.

আমাদের বেঞ্চমার্কে, CAS ব্যবহারকারী হাই-কন্টেনশন কোড LL/SC ভ্যারিয়েন্টের তুলনায় প্রায় ৩ গুণ বেশি গতি অর্জন করে।

জাভা প্রোগ্রামিং ভাষা java.util.concurrent.atomic- এর মাধ্যমে অ্যাটমিক প্রিমিটিভ প্রদান করে, যা এই এবং অন্যান্য বিশেষায়িত সিপিইউ নির্দেশাবলীর উপর নির্ভর করে।

ডেটা স্ট্রাকচার: ডেলিকিউ

MessageQueue থেকে লক কনটেনশন দূর করার জন্য, আমাদের ইঞ্জিনিয়াররা DeliQueue নামক একটি অভিনব ডেটা স্ট্রাকচার ডিজাইন করেছেন। DeliQueue Message ইনসারশন এবং Message প্রসেসিংকে পৃথক করে:

  1. Messages তালিকা (ট্রাইবার স্ট্যাক): একটি লক-মুক্ত স্ট্যাক। যেকোনো থ্রেড কোনো প্রতিদ্বন্দ্বিতা ছাড়াই এখানে নতুন Messages পুশ করতে পারে।
  2. প্রায়োরিটি কিউ (মিন-হিপ): এটি হ্যান্ডেল করার জন্য Messages একটি হিপ, যা শুধুমাত্র লুপার থ্রেডের মালিকানাধীন (ফলে এটি অ্যাক্সেস করার জন্য কোনো সিনক্রোনাইজেশন বা লকের প্রয়োজন হয় না)।

এনকিউ: একটি ট্রেইবার স্ট্যাকে পুশ করা

Messages তালিকা একটি ট্রাইবার স্ট্যাকে [1] রাখা হয়, একটি লক-ফ্রি স্ট্যাক যা হেড পয়েন্টার আপডেট করার জন্য একটি CAS লুপ ব্যবহার করে।

public class TreiberStack <E> {
    AtomicReference<Node<E>> top =
            new AtomicReference<Node<E>>();
    public void push(E item) {
        Node<E> newHead = new Node<E>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }

    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = top.get();
            if (oldHead == null) return null;
            newHead = oldHead.next;
        } while (!top.compareAndSet(oldHead, newHead));
        return oldHead.item;
    }
}

Java Concurrency in Practice [2] এর উপর ভিত্তি করে সোর্স কোড, অনলাইনে উপলব্ধ এবং পাবলিক ডোমেনে প্রকাশিত।

যেকোনো প্রডিউসার যেকোনো সময় স্ট্যাকে নতুন Message পুশ করতে পারে। এটা অনেকটা ডেলি কাউন্টার থেকে টিকিট তোলার মতো — আপনি কখন এসেছেন তার উপর ভিত্তি করে আপনার নম্বর নির্ধারিত হয়, কিন্তু আপনি যে ক্রমে খাবার নিচ্ছেন তার সাথে মিল থাকার কোনো বাধ্যবাধকতা নেই। যেহেতু এটি একটি লিঙ্কড স্ট্যাক, তাই প্রতিটি Message একটি সাব-স্ট্যাক — আপনি হেডকে ট্র্যাক করে এবং সামনের দিকে ইটারেট করে যেকোনো সময়ে Message কিউ-এর অবস্থা দেখতে পারেন — আপনি উপরে পুশ করা কোনো নতুন Message দেখতে পাবেন না, এমনকি আপনার ট্রাভার্সালের সময় সেগুলো যোগ করা হলেও।

ডিকিউ: মিন-হিপে বাল্ক স্থানান্তর

হ্যান্ডেল করার জন্য পরবর্তী Message খুঁজে বের করতে, Looper ট্রাইবার স্ট্যাকের উপর থেকে যাত্রা শুরু করে এবং পূর্বে প্রসেস করা সর্বশেষ Message না পাওয়া পর্যন্ত পুনরাবৃত্তি করতে থাকে। Looper স্ট্যাকের নিচ দিয়ে যাওয়ার সময়, Message ডেডলাইন-অর্ডারড Message -হিপে প্রবেশ করায়। যেহেতু হিপটি Looper একচেটিয়া মালিকানাধীন, তাই এটি কোনো লক বা অ্যাটমিক ব্যবহার না করেই Message অর্ডার ও প্রসেস করে।

dequeue.png

স্ট্যাকের নিচে নামার সময়, Looper স্ট্যাক করা Message থেকে তাদের পূর্বসূরীদের দিকে লিঙ্ক তৈরি করে, যার ফলে একটি ডাবলি-লিঙ্কড লিস্ট গঠিত হয়। লিঙ্কড লিস্ট তৈরি করা নিরাপদ কারণ স্ট্যাকের নিচের দিকে নির্দেশকারী লিঙ্কগুলো CAS সহ ট্রেইবার স্ট্যাক অ্যালগরিদমের মাধ্যমে যোগ করা হয়, এবং স্ট্যাকের উপরের দিকে নির্দেশকারী লিঙ্কগুলো শুধুমাত্র Looper থ্রেড দ্বারা পড়া এবং পরিবর্তন করা হয়। এরপর এই ব্যাক লিঙ্কগুলো O(1) সময়ে স্ট্যাকের যেকোনো স্থান থেকে Message সরানোর জন্য ব্যবহার করা হয়।

এই ডিজাইনটি প্রডিউসারদের (কিউতে কাজ পোস্টকারী থ্রেড) জন্য O (1) ইনসারশন এবং কনজিউমারদের ( Looper ) জন্য অ্যামোর্টাইজড O (log N) প্রসেসিং প্রদান করে।

Message সাজানোর জন্য মিন-হিপ ব্যবহার করা লিগ্যাসি MessageQueue এর একটি মৌলিক ত্রুটিরও সমাধান করে, যেখানে Message একটি সিঙ্গলি-লিঙ্কড লিস্টে (যার রুট শীর্ষে ছিল) রাখা হতো। লিগ্যাসি ইমপ্লিমেন্টেশনে, হেড থেকে রিমুভালের ল্যাটেন্সি ছিল O (1), কিন্তু ইনসারশনের ওর্স্ট কেস ছিল O(N) – যা ওভারলোডেড কিউ-এর জন্য খুবই দুর্বল ছিল! এর বিপরীতে, মিন-হিপে ইনসারশন এবং রিমুভাল লগারিদমিকভাবে স্কেল করে, যা প্রতিযোগিতামূলক গড় পারফরম্যান্স প্রদান করে কিন্তু টেইল ল্যাটেন্সির ক্ষেত্রে সত্যিই চমৎকার কাজ করে।


লিগ্যাসি (লক করা) MessageQueue ডেলি কিউ
ঢোকান ও(এন)

থ্রেড কল করার জন্য O (1)

Looper থ্রেডের জন্য O(logN)

মাথা থেকে অপসারণ করুন (1) O(logN)

ঐতিহ্যবাহী কিউ বাস্তবায়নে, প্রডিউসার এবং কনজিউমার অন্তর্নিহিত সিঙ্গলি-লিঙ্কড লিস্টে এক্সক্লুসিভ অ্যাক্সেস সমন্বয় করার জন্য একটি লক ব্যবহার করত। ডেলি-কিউতে, ট্রাইবার স্ট্যাক যুগপৎ অ্যাক্সেস পরিচালনা করে এবং একক কনজিউমার তার ওয়ার্ক কিউ-এর ক্রম নির্ধারণ করে।

অপসারণ: সমাধিফলকের মাধ্যমে সামঞ্জস্য

ডেলি-কিউ (DeliQueue) একটি হাইব্রিড ডেটা স্ট্রাকচার, যা একটি লক-ফ্রি ট্রাইবার স্ট্যাক (lock-free Treiber stack) এবং একটি সিঙ্গেল-থ্রেডেড মিন-হিপ (single-threaded min-heap)-কে একত্রিত করে। গ্লোবাল লক ছাড়া এই দুটি স্ট্রাকচারকে সিঙ্কে রাখা একটি অনন্য চ্যালেঞ্জ তৈরি করে: একটি মেসেজ হয়তো ফিজিক্যালি স্ট্যাকে উপস্থিত থাকতে পারে, কিন্তু লজিক্যালি কিউ থেকে সরিয়ে ফেলা হতে পারে।

এর সমাধান করতে, DeliQueue “tombstoning” নামক একটি কৌশল ব্যবহার করে। প্রতিটি Message তার ব্যাকওয়ার্ড ও ফরোয়ার্ড পয়েন্টারের মাধ্যমে স্ট্যাকে নিজের অবস্থান, হিপের অ্যারেতে তার ইন্ডেক্স এবং সেটি সরানো হয়েছে কিনা তা নির্দেশকারী একটি বুলিয়ান ফ্ল্যাগের মাধ্যমে ট্র্যাক করে। যখন একটি Message রান করার জন্য প্রস্তুত হয়, তখন Looper থ্রেডটি তার removed ফ্ল্যাগটি CAS করে, এবং তারপর সেটিকে হিপ ও স্ট্যাক থেকে সরিয়ে দেয়।

যখন অন্য কোনো থ্রেডের একটি Message অপসারণ করার প্রয়োজন হয়, তখন এটি ডেটা স্ট্রাকচার থেকে সেটিকে তাৎক্ষণিকভাবে বের করে নেয় না। পরিবর্তে, এটি নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করে:

  1. যৌক্তিক অপসারণ: থ্রেডটি একটি CAS ব্যবহার করে অ্যাটমিকভাবে Message অপসারণ ফ্ল্যাগ 'ফলস' থেকে 'ট্রু'-তে সেট করে। Message তার অপেক্ষমান অপসারণের প্রমাণ হিসেবে ডেটা স্ট্রাকচারে থেকে যায়, যাকে তথাকথিত “টুম্বস্টোন” বলা হয়। একবার কোনো Message অপসারণের জন্য ফ্ল্যাগ করা হলে, DeliQueue যখনই সেটিকে খুঁজে পায়, তখন এমনভাবে বিবেচনা করে যেন সেটি আর কিউতে নেই।
  2. বিলম্বিত পরিষ্করণ: ডেটা স্ট্রাকচার থেকে প্রকৃত অপসারণের দায়িত্ব Looper থ্রেডের, এবং এটি পরবর্তী সময়ের জন্য স্থগিত রাখা হয়। স্ট্যাক বা হিপ পরিবর্তন করার পরিবর্তে, রিমুভার থ্রেডটি Message অন্য একটি লক-ফ্রি ফ্রিলিস্ট স্ট্যাকে যুক্ত করে।
  3. কাঠামোগত অপসারণ: শুধুমাত্র Looper হিপের সাথে কাজ করতে বা স্ট্যাক থেকে উপাদান অপসারণ করতে পারে। এটি জেগে উঠলে, ফ্রিলিস্টটি খালি করে এবং এর মধ্যে থাকা Message প্রসেস করে। এরপর প্রতিটি Message স্ট্যাক থেকে আনলিঙ্ক করা হয় এবং হিপ থেকে অপসারণ করা হয়।

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

ট্রাভার্সাল: জাভা মেমরি মডেলের নিরীহ ডেটা রেস

বেশিরভাগ কনকারেন্সি এপিআই, যেমন জাভা স্ট্যান্ডার্ড লাইব্রেরির Future , বা কোটলিনের Job এবং Deferred , কোনো কাজ সম্পন্ন হওয়ার আগেই তা বাতিল করার একটি ব্যবস্থা অন্তর্ভুক্ত করে। এই ক্লাসগুলোর কোনো একটির ইনস্ট্যান্স অন্তর্নিহিত কাজের এককের সাথে হুবহু মিলে যায়, এবং কোনো অবজেক্টের উপর cancel কল করলে তার সাথে যুক্ত নির্দিষ্ট অপারেশনগুলো বাতিল হয়ে যায়।

আজকের অ্যান্ড্রয়েড ডিভাইসগুলোতে মাল্টি-কোর সিপিইউ এবং কনকারেন্ট, জেনারেশনাল গার্বেজ কালেকশন রয়েছে। কিন্তু যখন অ্যান্ড্রয়েড প্রথম তৈরি হয়েছিল, তখন প্রতিটি ইউনিট অফ ওয়ার্কের জন্য একটি করে অবজেক্ট বরাদ্দ করা অত্যন্ত ব্যয়বহুল ছিল। ফলস্বরূপ, অ্যান্ড্রয়েডের Handler removeMessages এর বিভিন্ন ওভারলোডের মাধ্যমে ক্যান্সেলেশন সমর্থন করে — এটি একটি নির্দিষ্ট Message মুছে ফেলার পরিবর্তে, নির্দিষ্ট শর্তের সাথে মিলে যাওয়া সমস্ত Message মুছে ফেলে। বাস্তবে, এর জন্য removeMessages কল করার আগে ইনসার্ট করা সমস্ত Message মধ্যে দিয়ে পুনরাবৃত্তি করতে হয় এবং যেগুলো শর্তের সাথে মিলে যায়, সেগুলো মুছে ফেলতে হয়।

সামনে এগোনোর সময়, একটি থ্রেডের শুধুমাত্র একটি ক্রমবদ্ধ অ্যাটমিক অপারেশনের প্রয়োজন হয়, যা হলো স্ট্যাকের বর্তমান হেডটি পড়া। এরপর, পরবর্তী Message খুঁজে বের করার জন্য সাধারণ ফিল্ড রিড ব্যবহার করা হয়। যদি লুপার থ্রেডটি Message সরানোর সময় next ফিল্ডগুলো পরিবর্তন করে, তাহলে Looper রাইট এবং অন্য একটি থ্রেডের রিড অসিঙ্ক্রোনাইজড হয়ে যায় - এটি একটি ডেটা রেস । সাধারণত, ডেটা রেস একটি গুরুতর বাগ যা আপনার অ্যাপে বিশাল সমস্যা সৃষ্টি করতে পারে - যেমন মেমরি লিক, ইনফিনিট লুপ, ক্র্যাশ, ফ্রিজ এবং আরও অনেক কিছু। তবে, কিছু নির্দিষ্ট সীমিত শর্তের অধীনে, জাভা মেমরি মডেলের মধ্যে ডেটা রেস ক্ষতিকর নাও হতে পারে। ধরা যাক, আমরা একটি স্ট্যাক দিয়ে শুরু করছি:

headMessage.png

আমরা হেডের একটি অ্যাটমিক রিড সম্পাদন করি এবং A দেখতে পাই। A-এর নেক্সট পয়েন্টারটি B-কে নির্দেশ করে। আমরা যখন B-কে প্রসেস করি, ঠিক সেই সময়ে লুফারটি A-কে প্রথমে C এবং তারপর D-কে নির্দেশ করার জন্য আপডেট করে B এবং C-কে সরিয়ে দিতে পারে।

headMessage2.png

যদিও B এবং C যৌক্তিকভাবে অপসারিত হয়, B তার C এর দিকে থাকা নেক্সট পয়েন্টারটি ধরে রাখে এবং C , D এর দিকে থাকা নেক্সট পয়েন্টারটি ধরে রাখে। রিডিং থ্রেডটি বিচ্ছিন্ন ও অপসারিত নোডগুলোর মধ্য দিয়ে চলাচল অব্যাহত রাখে এবং অবশেষে D তে লাইভ স্ট্যাকে পুনরায় যোগদান করে।

ট্রাভার্সাল এবং রিমুভালের মধ্যেকার রেস কন্ডিশন সামলানোর জন্য DeliQueue-কে ডিজাইন করার মাধ্যমে আমরা নিরাপদ ও লক-মুক্ত ইটারেশন সম্ভব করে তুলি।

প্রস্থান করা হচ্ছে: নেটিভ রেফকাউন্ট

Looper একটি নেটিভ অ্যালোকেশন দ্বারা সমর্থিত, যা Looper বন্ধ হয়ে গেলে অবশ্যই ম্যানুয়ালি মুক্ত করতে হয়। Looper বন্ধ হওয়ার সময় যদি অন্য কোনো থ্রেড Message যোগ করে, তবে এটি মুক্ত হওয়ার পর নেটিভ অ্যালোকেশনটি ব্যবহার করতে পারে, যা মেমরি সুরক্ষার লঙ্ঘন। আমরা একটি ট্যাগড রেফকাউন্ট ব্যবহার করে এটি প্রতিরোধ করি, যেখানে অ্যাটমিকের একটি বিট Looper বন্ধ হচ্ছে কিনা তা নির্দেশ করতে ব্যবহৃত হয়।

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

যখন Looper থ্রেডটি বন্ধ হওয়ার জন্য প্রস্তুত হয়, তখন এটি অ্যাটমিক পদ্ধতিতে কুইটিং বিট সেট করার জন্য CAS ব্যবহার করে। যদি রেফকাউন্ট ০ হয়, তবে এটি তার নেটিভ অ্যালোকেশন মুক্ত করার জন্য অগ্রসর হতে পারে। অন্যথায়, এটি নিজেকে পার্ক করে রাখে, এই জেনে যে নেটিভ অ্যালোকেশনের শেষ ব্যবহারকারী যখন রেফকাউন্ট কমাবে, তখন এটিকে জাগিয়ে তোলা হবে। এই পদ্ধতির অর্থ হলো Looper থ্রেডটি অন্যান্য থ্রেডের অগ্রগতির জন্য অপেক্ষা করে, কিন্তু শুধুমাত্র যখন এটি বন্ধ হচ্ছে। এটি কেবল একবারই ঘটে এবং পারফরম্যান্সের জন্য সংবেদনশীল নয়, এবং এটি নেটিভ অ্যালোকেশন ব্যবহারকারী অন্যান্য কোডকে সম্পূর্ণরূপে লক-ফ্রি রাখে।

atomicLayout.png

এর বাস্তবায়নে আরও অনেক কৌশল ও জটিলতা রয়েছে। সোর্স কোড পর্যালোচনা করে আপনি DeliQueue সম্পর্কে আরও জানতে পারবেন।

অপ্টিমাইজেশন: শাখাবিহীন প্রোগ্রামিং

DeliQueue তৈরি ও পরীক্ষা করার সময়, দলটি অনেক বেঞ্চমার্ক চালিয়েছিল এবং নতুন কোডটির সতর্ক প্রোফাইলিং করেছিল। simpleperf টুল ব্যবহার করে চিহ্নিত একটি সমস্যা ছিল Message comparator কোডের কারণে সৃষ্ট পাইপলাইন ফ্লাশ।

একটি স্ট্যান্ডার্ড কম্পারেটর শর্তসাপেক্ষ জাম্প ব্যবহার করে, যেখানে কোন Message প্রথমে আসবে তা নির্ধারণ করার শর্তটি নিচে সরলীকৃতভাবে দেওয়া হলো:

static int compareMessages(@NonNull Message m1, @NonNull Message m2) {
    if (m1 == m2) {
        return 0;
    }

    // Primary queue order is by when.
    // Messages with an earlier when should come first in the queue.
    final long whenDiff = m1.when - m2.when;
    if (whenDiff > 0) return 1;
    if (whenDiff < 0) return -1;

    // Secondary queue order is by insert sequence.
    // If two messages were inserted with the same `when`, the one inserted
    // first should come first in the queue.
    final long insertSeqDiff = m1.insertSeq - m2.insertSeq;
    if (insertSeqDiff > 0) return 1;
    if (insertSeqDiff < 0) return -1;

    return 0;
}

এই কোডটি কন্ডিশনাল জাম্প ( b.le এবং cbnz ইনস্ট্রাকশন) হিসেবে কম্পাইল হয়। যখন সিপিইউ একটি কন্ডিশনাল ব্রাঞ্চের সম্মুখীন হয়, তখন শর্তটি গণনা না হওয়া পর্যন্ত এটি জানতে পারে না যে ব্রাঞ্চটি নেওয়া হবে কি না। ফলে, এরপর কোন ইনস্ট্রাকশনটি পড়তে হবে তা এটি জানে না এবং ব্রাঞ্চ প্রেডিকশন নামক একটি কৌশল ব্যবহার করে অনুমান করতে হয়। বাইনারি সার্চের মতো ক্ষেত্রে, প্রতিটি ধাপে ব্রাঞ্চের দিক অপ্রত্যাশিতভাবে ভিন্ন হবে, তাই সম্ভবত অর্ধেক অনুমানই ভুল হবে। সার্চিং এবং সর্টিং অ্যালগরিদমগুলিতে (যেমন মিন-হিপে ব্যবহৃত অ্যালগরিদম) ব্রাঞ্চ প্রেডিকশন প্রায়শই অকার্যকর হয়, কারণ ভুল অনুমানের খরচ সঠিক অনুমানের উন্নতির চেয়ে বেশি। যখন ব্রাঞ্চ প্রেডিক্টর ভুল অনুমান করে, তখন এটি পূর্বাভাসিত মানটি ধরে নেওয়ার পর করা কাজটি বাতিল করে দেয় এবং প্রকৃতপক্ষে নেওয়া পথ থেকে আবার শুরু করে - একে পাইপলাইন ফ্লাশ বলা হয়।

এই সমস্যাটি খুঁজে বের করার জন্য, আমরা branch-misses পারফরম্যান্স কাউন্টার ব্যবহার করে আমাদের বেঞ্চমার্কগুলোর প্রোফাইলিং করেছি, যা সেইসব স্ট্যাক ট্রেস রেকর্ড করে যেখানে ব্রাঞ্চ প্রেডিক্টর ভুল অনুমান করে। এরপর আমরা গুগল পিপিআরওএফ (Google pprof) দিয়ে ফলাফলগুলো ভিজ্যুয়ালাইজ করেছি, যা নিচে দেখানো হলো:

flame2.png

মনে রাখবেন যে মূল MessageQueue কোডটি অর্ডারড কিউ-এর জন্য একটি সিঙ্গলি-লিঙ্কড লিস্ট ব্যবহার করত। ইনসারশনের জন্য লিস্টটিকে সর্টেড অর্ডারে একটি লিনিয়ার সার্চ হিসেবে ট্র্যাভার্স করা হতো, যা ইনসারশন পয়েন্টের পরের প্রথম এলিমেন্টে থেমে যেত এবং তার সামনে নতুন Message লিঙ্ক করে দিত। হেড থেকে রিমুভ করার জন্য শুধু হেডটিকে আনলিঙ্ক করলেই চলত। অন্যদিকে, DeliQueue একটি মিন-হিপ ব্যবহার করে, যেখানে মিউটেশনের জন্য একটি ব্যালেন্সড ডেটা স্ট্রাকচারে লগারিদমিক কমপ্লেক্সিটির সাথে কিছু এলিমেন্টের পুনর্বিন্যাস (উপরে বা নিচে চালনা) করতে হয়, এবং এখানে যেকোনো তুলনার ক্ষেত্রে ট্র্যাভার্সালটি একটি লেফট চাইল্ড বা রাইট চাইল্ডে যাওয়ার সমান সম্ভাবনা থাকে। নতুন অ্যালগরিদমটি অ্যাসিপ্টোটিকভাবে দ্রুততর, কিন্তু এটি একটি নতুন প্রতিবন্ধকতা তৈরি করে, কারণ সার্চ কোডটি অর্ধেক সময় ব্রাঞ্চ মিসের কারণে আটকে যায়।

ব্রাঞ্চ মিসের কারণে আমাদের হিপ কোড ধীর হয়ে যাচ্ছিল বুঝতে পেরে, আমরা ব্রাঞ্চ-ফ্রি প্রোগ্রামিং ব্যবহার করে কোডটি অপ্টিমাইজ করেছি।

// Branchless Logic
static int compareMessages(@NonNull Message m1, @NonNull Message m2) {
    final long when1 = m1.when;
    final long when2 = m2.when;
    final long insertSeq1 = m1.insertSeq;
    final long insertSeq2 = m2.insertSeq;

    // signum returns the sign (-1, 0, 1) of the argument,
    // and is implemented as pure arithmetic:
    // ((num >> 63) | (-num >>> 63))
    final int whenSign = Long.signum(when1 - when2);
    final int insertSeqSign = Long.signum(insertSeq1 - insertSeq2);

    // whenSign takes precedence over insertSeqSign,
    // so the formula below is such that insertSeqSign only matters
    // as a tie-breaker if whenSign is 0.
    return whenSign * 2 + insertSeqSign;
}

অপ্টিমাইজেশনটি বোঝার জন্য, কম্পাইলার এক্সপ্লোরারে উদাহরণ দুটি ডিসঅ্যাসেম্বল করুন এবং LLVM-MCA ব্যবহার করুন, যা একটি সিপিইউ সিমুলেটর এবং সিপিইউ সাইকেলের একটি আনুমানিক টাইমলাইন তৈরি করতে পারে।

The original code:
Index     01234567890123
[0,0]     DeER .    .  .   sub  x0, x2, x3
[0,1]     D=eER.    .  .   cmp  x0, #0
[0,2]     D==eER    .  .   cset w0, ne
[0,3]     .D==eER   .  .   cneg w0, w0, lt
[0,4]     .D===eER  .  .   cmp  w0, #0
[0,5]     .D====eER .  .   b.le #12
[0,6]     . DeE---R .  .   mov  w1, #1
[0,7]     . DeE---R .  .   b    #48
[0,8]     . D==eE-R .  .   tbz  w0, #31, #12
[0,9]     .  DeE--R .  .   mov  w1, #-1
[0,10]    .  DeE--R .  .   b    #36
[0,11]    .  D=eE-R .  .   sub  x0, x4, x5
[0,12]    .   D=eER .  .   cmp  x0, #0
[0,13]    .   D==eER.  .   cset w0, ne
[0,14]    .   D===eER  .   cneg w0, w0, lt
[0,15]    .    D===eER .   cmp  w0, #0
[0,16]    .    D====eER.   csetm        w1, lt
[0,17]    .    D===eE-R.   cmp  w0, #0
[0,18]    .    .D===eER.   csinc        w1, w1, wzr, le
[0,19]    .    .D====eER   mov  x0, x1
[0,20]    .    .DeE----R   ret

b.le নামক একটি শর্তসাপেক্ষ শাখা লক্ষ্য করুন, যেটি ` when ফিল্ডগুলো তুলনা করে ফলাফল আগে থেকেই জানা থাকলে insertSeq ফিল্ডগুলো তুলনা করা এড়িয়ে চলে।

The branchless code:
Index     012345678
[0,0]     DeER .  .   sub       x0, x2, x3
[0,1]     DeER .  .   sub       x1, x4, x5
[0,2]     D=eER.  .   cmp       x0, #0
[0,3]     .D=eER  .   cset      w0, ne
[0,4]     .D==eER .   cneg      w0, w0, lt
[0,5]     .DeE--R .   cmp       x1, #0
[0,6]     . DeE-R .   cset      w1, ne
[0,7]     . D=eER .   cneg      w1, w1, lt
[0,8]     . D==eeER   add       w0, w1, w0, lsl #1
[0,9]     .  DeE--R   ret

এখানে, ব্রাঞ্চবিহীন ইমপ্লিমেন্টেশনটি ব্রাঞ্চযুক্ত কোডের সংক্ষিপ্ততম পথের চেয়েও কম সাইকেল ও ইন্সট্রাকশন ব্যবহার করে — এটি সব ক্ষেত্রেই উত্তম। দ্রুততর ইমপ্লিমেন্টেশন এবং ভুলভাবে অনুমান করা ব্রাঞ্চগুলো বাদ দেওয়ার ফলে আমাদের কিছু বেঞ্চমার্কে ৫ গুণ উন্নতি হয়েছে!


তবে, এই কৌশলটি সবসময় প্রযোজ্য নয়। ব্রাঞ্চবিহীন পদ্ধতিগুলোতে সাধারণত এমন কাজ করতে হয় যা বাতিল হয়ে যাবে, এবং যদি ব্রাঞ্চটি বেশিরভাগ সময় অনুমানযোগ্য হয়, তবে সেই নষ্ট হওয়া কাজটি আপনার কোডের গতি কমিয়ে দিতে পারে। এছাড়াও, একটি ব্রাঞ্চ অপসারণ করলে প্রায়শই একটি ডেটা নির্ভরতা তৈরি হয়। আধুনিক সিপিইউগুলো প্রতি সাইকেলে একাধিক অপারেশন সম্পাদন করে, কিন্তু পূর্ববর্তী নির্দেশনা থেকে ইনপুট প্রস্তুত না হওয়া পর্যন্ত তারা কোনো নির্দেশনা কার্যকর করতে পারে না। এর বিপরীতে, একটি সিপিইউ ব্রাঞ্চের ডেটা সম্পর্কে অনুমান করতে পারে এবং যদি ব্রাঞ্চটি সঠিকভাবে অনুমান করা যায় তবে এগিয়ে গিয়ে কাজ করতে পারে।

পরীক্ষা এবং যাচাইকরণ

লক-ফ্রি অ্যালগরিদমের সঠিকতা যাচাই করা অত্যন্ত কঠিন!

ডেভেলপমেন্ট চলাকালীন ক্রমাগত যাচাইকরণের জন্য স্ট্যান্ডার্ড ইউনিট টেস্টের পাশাপাশি, আমরা কিউ ইনভেরিয়েন্ট যাচাই করতে এবং ডেটা রেস বিদ্যমান থাকলে তা প্ররোচিত করার চেষ্টায় কঠোর স্ট্রেস টেস্টও লিখেছিলাম। আমাদের টেস্ট ল্যাবগুলোতে আমরা এমুলেটেড ডিভাইস এবং আসল হার্ডওয়্যারে লক্ষ লক্ষ টেস্ট ইনস্ট্যান্স চালাতে পারতাম।

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

আমাদের ডিবাগিং ক্ষমতা উন্নত করার জন্য, আমরা নতুন বিশ্লেষণ সরঞ্জাম তৈরি করেছি। নিচে অ্যান্ড্রয়েড প্ল্যাটফর্ম কোডের একটি সমস্যার উদাহরণ দেওয়া হলো, যেখানে একটি থ্রেড অন্য একটি থ্রেডকে Message দিয়ে অতিরিক্ত ভারাক্রান্ত করছে, যার ফলে একটি বড় ব্যাকলগ তৈরি হচ্ছে। আমাদের যোগ করা MessageQueue ইন্সট্রুমেন্টেশন ফিচারের কল্যাণে এটি পারফেটোতে দৃশ্যমান।

কর্মক্ষেত্র.png

system_server প্রসেসে MessageQueue ট্রেসিং সক্রিয় করতে, আপনার Perfetto কনফিগারেশনে নিম্নলিখিতটি অন্তর্ভুক্ত করুন:

data_sources {
  config {
    name: "track_event"
    target_buffer: 0  # Change this per your buffers configuration
    track_event_config {
      enabled_categories: "mq"
    }
  }
}

প্রভাব

DeliQueue, MessageQueue থেকে লক দূর করার মাধ্যমে সিস্টেম ও অ্যাপের পারফরম্যান্স উন্নত করে।

  • সিন্থেটিক বেঞ্চমার্ক: উন্নত কনকারেন্সি (ট্রাইবার স্ট্যাক) এবং দ্রুততর ইনসারশনের (মিন-হিপ) কল্যাণে, ব্যস্ত কিউ-তে মাল্টি-থ্রেডেড ইনসারশন প্রচলিত MessageQueue চেয়ে ৫,০০০ গুণ পর্যন্ত দ্রুততর
  • অভ্যন্তরীণ বিটা পরীক্ষকদের কাছ থেকে প্রাপ্ত পারফেটটো ট্রেসগুলিতে আমরা দেখতে পাচ্ছি যে, লক কনটেনশনে অ্যাপের প্রধান থ্রেডের ব্যয়িত সময় ১৫% হ্রাস পেয়েছে।
  • একই পরীক্ষামূলক ডিভাইসগুলিতে, লক নিয়ে প্রতিযোগিতা কমে যাওয়ায় ব্যবহারকারীর অভিজ্ঞতার উল্লেখযোগ্য উন্নতি ঘটে, যেমন:
    • অ্যাপে -৪% ফ্রেম মিস হয়েছে।
    • সিস্টেম UI এবং লঞ্চার ইন্টারঅ্যাকশনে -৭.৭% ফ্রেম মিস হয়েছে।
    • অ্যাপ চালু হওয়া থেকে প্রথম ফ্রেম আঁকা পর্যন্ত সময়ে -৯.১% হ্রাস, ৯৫% পার্সেন্টাইলে।

পরবর্তী পদক্ষেপ

অ্যান্ড্রয়েড ১৭-এ অ্যাপগুলোতে ডেলি-কিউ (DeliQueue) চালু করা হচ্ছে। অ্যাপ ডেভেলপারদের উচিত অ্যান্ড্রয়েড ডেভেলপারস ব্লগে নতুন লক-মুক্ত মেসেজ- MessageQueue এর জন্য তাদের অ্যাপ প্রস্তুত করার পদ্ধতি পর্যালোচনা করা, যাতে তারা তাদের অ্যাপগুলো কীভাবে পরীক্ষা করতে হয় তা শিখতে পারেন।

তথ্যসূত্র

[1] ট্রেইবার, আর.কে., ১৯৮৬। সিস্টেমস প্রোগ্রামিং: সমান্তরালতার সাথে মোকাবিলা। ইন্টারন্যাশনাল বিজনেস মেশিনস ইনকর্পোরেটেড, থমাস জে. ওয়াটসন রিসার্চ সেন্টার।

[2] গোয়েট্‌জ, বি., পিয়ার্‌স, টি., ব্লখ, জে., বোবিয়ার, জে., হোমস, ডি., এবং লিয়া, ডি. (2006)। জাভা কনকারেন্সি ইন প্র্যাকটিস। অ্যাডিসন-ওয়েসলি প্রফেশনাল।

    লিখেছেন:

    পড়তে থাকুন