বাংলা

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

সমান্তরাল টাস্ক এক্সিকিউশনে দক্ষতা অর্জন: ফর্ক-জয়েন ফ্রেমওয়ার্কের এক গভীর বিশ্লেষণ

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

সমান্তরালতার প্রয়োজনীয়তা বোঝা

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

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

ডিভাইড-অ্যান্ড-কনকার প্যারাডাইম

ফর্ক-জয়েন ফ্রেমওয়ার্কটি সুপ্রতিষ্ঠিত ডিভাইড-অ্যান্ড-কনকার অ্যালগরিদমিক প্যারাডাইমের উপর নির্মিত। এই পদ্ধতির মধ্যে রয়েছে:

  1. ডিভাইড (Divide): একটি জটিল সমস্যাকে ছোট, স্বাধীন উপ-সমস্যায় বিভক্ত করা।
  2. কনকার (Conquer): এই উপ-সমস্যাগুলো রিকার্সিভভাবে সমাধান করা। যদি কোনো উপ-সমস্যা যথেষ্ট ছোট হয়, তবে এটি সরাসরি সমাধান করা হয়। অন্যথায়, এটি আরও বিভক্ত করা হয়।
  3. কম্বাইন (Combine): উপ-সমস্যাগুলোর সমাধান একত্রিত করে মূল সমস্যার সমাধান তৈরি করা।

এই রিকার্সিভ প্রকৃতির কারণে ফর্ক-জয়েন ফ্রেমওয়ার্কটি নিম্নলিখিত ধরনের কাজের জন্য বিশেষভাবে উপযুক্ত:

জাভাতে ফর্ক-জয়েন ফ্রেমওয়ার্কের পরিচিতি

জাভার ফর্ক-জয়েন ফ্রেমওয়ার্ক, যা জাভা ৭-এ প্রবর্তিত হয়েছিল, ডিভাইড-অ্যান্ড-কনকার কৌশলের উপর ভিত্তি করে প্যারালাল অ্যালগরিদম বাস্তবায়নের জন্য একটি কাঠামোবদ্ধ উপায় সরবরাহ করে। এটি দুটি প্রধান অ্যাবস্ট্রাক্ট ক্লাস নিয়ে গঠিত:

এই ক্লাসগুলো একটি বিশেষ ধরনের ExecutorService-এর সাথে ব্যবহারের জন্য ডিজাইন করা হয়েছে যার নাম ForkJoinPoolForkJoinPool ফর্ক-জয়েন টাস্কগুলোর জন্য অপ্টিমাইজ করা হয়েছে এবং এটি ওয়ার্ক-স্টিলিং নামক একটি কৌশল ব্যবহার করে, যা এর দক্ষতার মূল চাবিকাঠি।

ফ্রেমওয়ার্কের মূল উপাদান

চলুন ফর্ক-জয়েন ফ্রেমওয়ার্কের সাথে কাজ করার সময় আপনার মুখোমুখি হতে পারে এমন মূল উপাদানগুলি ভেঙে দেখি:

1. ForkJoinPool

ForkJoinPool হলো ফ্রেমওয়ার্কের হৃদয়। এটি ওয়ার্কার থ্রেডের একটি পুল পরিচালনা করে যা টাস্ক সম্পাদন করে। প্রচলিত থ্রেড পুলের মতো নয়, ForkJoinPool বিশেষভাবে ফর্ক-জয়েন মডেলের জন্য ডিজাইন করা হয়েছে। এর প্রধান বৈশিষ্ট্যগুলির মধ্যে রয়েছে:

আপনি এভাবে একটি ForkJoinPool তৈরি করতে পারেন:

// কমন পুল ব্যবহার করা (বেশিরভাগ ক্ষেত্রে প্রস্তাবিত)
ForkJoinPool pool = ForkJoinPool.commonPool();

// অথবা একটি কাস্টম পুল তৈরি করা
// ForkJoinPool customPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());

commonPool() একটি স্ট্যাটিক, শেয়ারড পুল যা আপনি নিজের পুল তৈরি এবং পরিচালনা না করেই ব্যবহার করতে পারেন। এটি প্রায়শই একটি উপযুক্ত সংখ্যক থ্রেড (সাধারণত উপলব্ধ প্রসেসরের সংখ্যার উপর ভিত্তি করে) দিয়ে পূর্ব-কনফিগার করা থাকে।

2. RecursiveTask<V>

RecursiveTask<V> একটি অ্যাবস্ট্রাক্ট ক্লাস যা V টাইপের ফলাফল গণনা করে এমন একটি টাস্ককে প্রতিনিধিত্ব করে। এটি ব্যবহার করার জন্য, আপনাকে করতে হবে:

compute() মেথডের ভিতরে, আপনি সাধারণত যা করবেন:

উদাহরণ: একটি অ্যারেতে সংখ্যার যোগফল গণনা করা

আসুন একটি ক্লাসিক উদাহরণ দিয়ে ব্যাখ্যা করি: একটি বড় অ্যারের উপাদানগুলির যোগফল বের করা।

import java.util.concurrent.RecursiveTask;

public class SumArrayTask extends RecursiveTask<Long> {

    private static final int THRESHOLD = 1000; // বিভাজনের জন্য থ্রেশহোল্ড
    private final int[] array;
    private final int start;
    private final int end;

    public SumArrayTask(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;

        // বেস কেস: যদি সাব-অ্যারে যথেষ্ট ছোট হয়, তবে সরাসরি যোগ করুন
        if (length <= THRESHOLD) {
            return sequentialSum(array, start, end);
        }

        // রিকার্সিভ কেস: টাস্কটিকে দুটি সাব-টাস্কে বিভক্ত করুন
        int mid = start + length / 2;

        SumArrayTask leftTask = new SumArrayTask(array, start, mid);
        SumArrayTask rightTask = new SumArrayTask(array, mid, end);

        // বাম টাস্কটিকে ফর্ক করুন (এক্সিকিউশনের জন্য শিডিউল করুন)
        leftTask.fork();

        // ডান টাস্কটি সরাসরি গণনা করুন (অথবা এটিও ফর্ক করতে পারেন)
        // এখানে, আমরা একটি থ্রেড ব্যস্ত রাখতে ডান টাস্কটি সরাসরি গণনা করি
        Long rightResult = rightTask.compute();

        // বাম টাস্কটিকে জয়েন করুন (এর ফলাফলের জন্য অপেক্ষা করুন)
        Long leftResult = leftTask.join();

        // ফলাফলগুলো একত্রিত করুন
        return leftResult + rightResult;
    }

    private Long sequentialSum(int[] array, int start, int end) {
        Long sum = 0L;
        for (int i = start; i < end; i++) {
            sum += array[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        int[] data = new int[1000000]; // উদাহরণস্বরূপ বড় অ্যারে
        for (int i = 0; i < data.length; i++) {
            data[i] = i % 100;
        }

        ForkJoinPool pool = ForkJoinPool.commonPool();
        SumArrayTask task = new SumArrayTask(data, 0, data.length);

        System.out.println("যোগফল গণনা করা হচ্ছে...");
        long startTime = System.nanoTime();
        Long result = pool.invoke(task);
        long endTime = System.nanoTime();

        System.out.println("যোগফল: " + result);
        System.out.println("ব্যয়িত সময়: " + (endTime - startTime) / 1_000_000 + " ms");

        // তুলনার জন্য, একটি ক্রমানুসারে যোগফল
        // long sequentialResult = 0;
        // for (int val : data) {
        //     sequentialResult += val;
        // }
        // System.out.println("Sequential Sum: " + sequentialResult);
    }
}

এই উদাহরণে:

3. RecursiveAction

RecursiveAction ক্লাসটি RecursiveTask-এর মতোই, তবে এটি এমন টাস্কের জন্য ব্যবহৃত হয় যা কোনো রিটার্ন ভ্যালু তৈরি করে না। মূল যুক্তি একই থাকে: টাস্কটি বড় হলে বিভক্ত করুন, সাবটাস্ক ফর্ক করুন, এবং তারপর তাদের সমাপ্তি প্রয়োজন হলে জয়েন করুন।

একটি RecursiveAction ইমপ্লিমেন্ট করতে, আপনাকে:

compute()-এর ভিতরে, আপনি সাবটাস্ক শিডিউল করার জন্য fork() এবং তাদের সমাপ্তির জন্য অপেক্ষা করতে join() ব্যবহার করবেন। যেহেতু কোনো রিটার্ন ভ্যালু নেই, তাই আপনাকে প্রায়শই ফলাফল "একত্রিত" করতে হয় না, তবে আপনাকে নিশ্চিত করতে হতে পারে যে অ্যাকশনটি নিজে শেষ হওয়ার আগে সমস্ত নির্ভরশীল সাবটাস্ক শেষ হয়েছে।

উদাহরণ: প্যারালাল অ্যারে এলিমেন্ট ট্রান্সফর্মেশন

আসুন একটি অ্যারের প্রতিটি উপাদানকে সমান্তরালভাবে রূপান্তর করার কথা ভাবি, উদাহরণস্বরূপ, প্রতিটি সংখ্যার বর্গ করা।

import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;

public class SquareArrayAction extends RecursiveAction {

    private static final int THRESHOLD = 1000;
    private final int[] array;
    private final int start;
    private final int end;

    public SquareArrayAction(int[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        int length = end - start;

        // বেস কেস: যদি সাব-অ্যারে যথেষ্ট ছোট হয়, তবে ক্রমানুসারে রূপান্তর করুন
        if (length <= THRESHOLD) {
            sequentialSquare(array, start, end);
            return; // কোনো ফলাফল ফেরত দেওয়ার নেই
        }

        // রিকার্সিভ কেস: টাস্কটি বিভক্ত করুন
        int mid = start + length / 2;

        SquareArrayAction leftAction = new SquareArrayAction(array, start, mid);
        SquareArrayAction rightAction = new SquareArrayAction(array, mid, end);

        // উভয় সাব-অ্যাকশন ফর্ক করুন
        // একাধিক ফর্কড টাস্কের জন্য invokeAll ব্যবহার করা প্রায়শই বেশি কার্যকর
        invokeAll(leftAction, rightAction);

        // invokeAll-এর পরে কোনো সুস্পষ্ট জয়েনের প্রয়োজন নেই যদি আমরা মধ্যবর্তী ফলাফলের উপর নির্ভর না করি
        // যদি আপনি আলাদাভাবে ফর্ক করে তারপর জয়েন করতেন:
        // leftAction.fork();
        // rightAction.fork();
        // leftAction.join();
        // rightAction.join();
    }

    private void sequentialSquare(int[] array, int start, int end) {
        for (int i = start; i < end; i++) {
            array[i] = array[i] * array[i];
        }
    }

    public static void main(String[] args) {
        int[] data = new int[1000000];
        for (int i = 0; i < data.length; i++) {
            data[i] = (i % 50) + 1; // ১ থেকে ৫০ পর্যন্ত মান
        }

        ForkJoinPool pool = ForkJoinPool.commonPool();
        SquareArrayAction action = new SquareArrayAction(data, 0, data.length);

        System.out.println("অ্যারের উপাদানগুলোকে বর্গ করা হচ্ছে...");
        long startTime = System.nanoTime();
        pool.invoke(action); // অ্যাকশনের জন্য invoke() সমাপ্তির জন্য অপেক্ষা করে
        long endTime = System.nanoTime();

        System.out.println("অ্যারে রূপান্তর সম্পন্ন হয়েছে।");
        System.out.println("ব্যয়িত সময়: " + (endTime - startTime) / 1_000_000 + " ms");

        // যাচাই করার জন্য ঐচ্ছিকভাবে প্রথম কয়েকটি উপাদান মুদ্রণ করুন
        // System.out.println("বর্গ করার পর প্রথম ১০টি উপাদান:");
        // for (int i = 0; i < 10; i++) {
        //     System.out.print(data[i] + " ");
        // }
        // System.out.println();
    }
}

এখানকার মূল বিষয়গুলো:

অ্যাডভান্সড ফর্ক-জয়েন কনসেপ্ট এবং সেরা অনুশীলন

যদিও ফর্ক-জয়েন ফ্রেমওয়ার্কটি শক্তিশালী, এটিতে দক্ষতা অর্জনের জন্য আরও কিছু সূক্ষ্ম বিষয় বোঝা জড়িত:

1. সঠিক থ্রেশহোল্ড নির্বাচন করা

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

2. অতিরিক্ত ফর্কিং এবং জয়েনিং এড়িয়ে চলা

ঘন ঘন এবং অপ্রয়োজনীয় ফর্কিং এবং জয়েনিং পারফরম্যান্সের অবনতি ঘটাতে পারে। প্রতিটি fork() কল পুলে একটি টাস্ক যোগ করে এবং প্রতিটি join() একটি থ্রেডকে ব্লক করতে পারে। কৌশলগতভাবে সিদ্ধান্ত নিন কখন ফর্ক করতে হবে এবং কখন সরাসরি গণনা করতে হবে। যেমনটি SumArrayTask উদাহরণে দেখা গেছে, একটি শাখা সরাসরি গণনা করার সময় অন্যটি ফর্ক করা থ্রেডগুলিকে ব্যস্ত রাখতে সাহায্য করতে পারে।

3. invokeAll ব্যবহার করা

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

4. ব্যতিক্রম পরিচালনা করা

একটি compute() মেথডের মধ্যে নিক্ষিপ্ত ব্যতিক্রমগুলি একটি RuntimeException-এ (প্রায়শই একটি CompletionException) মোড়ানো হয় যখন আপনি টাস্কটিকে join() বা invoke() করেন। আপনাকে এই ব্যতিক্রমগুলি আনর্যাপ করে যথাযথভাবে পরিচালনা করতে হবে।

try {
    Long result = pool.invoke(task);
} catch (CompletionException e) {
    // টাস্ক দ্বারা নিক্ষিপ্ত ব্যতিক্রমটি পরিচালনা করুন
    Throwable cause = e.getCause();
    if (cause instanceof IllegalArgumentException) {
        // নির্দিষ্ট ব্যতিক্রমগুলি পরিচালনা করুন
    } else {
        // অন্যান্য ব্যতিক্রমগুলি পরিচালনা করুন
    }
}

5. কমন পুল বোঝা

বেশিরভাগ অ্যাপ্লিকেশনের জন্য, ForkJoinPool.commonPool() ব্যবহার করা প্রস্তাবিত পদ্ধতি। এটি একাধিক পুল পরিচালনার ওভারহেড এড়ায় এবং আপনার অ্যাপ্লিকেশনের বিভিন্ন অংশ থেকে টাস্কগুলিকে একই থ্রেড পুল ভাগ করে নিতে দেয়। তবে, মনে রাখবেন যে আপনার অ্যাপ্লিকেশনের অন্যান্য অংশও কমন পুল ব্যবহার করতে পারে, যা সাবধানে পরিচালনা না করলে সম্ভাব্যভাবে সংঘাতের কারণ হতে পারে।

6. কখন ফর্ক-জয়েন ব্যবহার করবেন না

ফর্ক-জয়েন ফ্রেমওয়ার্কটি কম্পিউট-বাউন্ড টাস্কগুলির জন্য অপ্টিমাইজ করা হয়েছে যা কার্যকরভাবে ছোট, রিকার্সিভ অংশে বিভক্ত করা যেতে পারে। এটি সাধারণত নিম্নলিখিতগুলির জন্য উপযুক্ত নয়:

গ্লোবাল বিবেচনা এবং ব্যবহারের ক্ষেত্র

ফর্ক-জয়েন ফ্রেমওয়ার্কের মাল্টি-কোর প্রসেসরগুলি কার্যকরভাবে ব্যবহার করার ক্ষমতা এটিকে গ্লোবাল অ্যাপ্লিকেশনগুলির জন্য অমূল্য করে তোলে যা প্রায়শই নিম্নলিখিত বিষয়গুলি নিয়ে কাজ করে:

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

উপসংহার

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

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