জাভা সমান্তরাল স্ট্রিম - সমান্তরাল () পদ্ধতিটি চালু করার ক্রম [বন্ধ]


11
AtomicInteger recordNumber = new AtomicInteger();
Files.lines(inputFile.toPath(), StandardCharsets.UTF_8)
     .map(record -> new Record(recordNumber.incrementAndGet(), record)) 
     .parallel()           
     .filter(record -> doSomeOperation())
     .findFirst()

আমি যখন এটি লিখেছিলাম তখন আমি ধরে নিয়েছিলাম যে থ্রেডগুলি কেবলমাত্র মানচিত্রের কল তৈরি হবে কারণ মানচিত্রের পরে সমান্তরাল স্থাপন করা হয়েছে placed তবে ফাইলের কয়েকটি লাইন প্রতিটি সম্পাদনের জন্য বিভিন্ন রেকর্ড নম্বর পেয়েছিল।

হুডের নীচে স্ট্রিমগুলি কীভাবে কাজ করে তা বুঝতে আমি সরকারী জাভা স্ট্রিম ডকুমেন্টেশন এবং কয়েকটি ওয়েব সাইট পড়েছি ।

কিছু প্রশ্ন:

  • জাভা সমান্তরাল স্ট্রিমটি স্প্লিটটাইটারের উপর ভিত্তি করে কাজ করে , যা প্রতিটি সংগ্রহ যেমন অ্যারেলিস্ট, লিংকডলিস্ট ইত্যাদির দ্বারা প্রয়োগ করা হয় When যখন আমরা এই সংগ্রহগুলির মধ্যে একটি সমান্তরাল স্ট্রিম নির্মাণ করি, তখন সংশ্লিষ্ট বিভক্ত পুনরুক্তিটি সংগ্রহটি বিভক্ত ও পুনরাবৃত্ত করতে ব্যবহৃত হবে। এটি ব্যাখ্যা করে যে ম্যাপের ফলাফলের পরিবর্তে মূল ইনপুট উত্স (ফাইল লাইন) স্তরে কেন সমান্তরালতা ঘটেছিল (যেমন রেকর্ড পোজো)। আমার বোধগম্যতা কি সঠিক?

  • আমার ক্ষেত্রে, ইনপুটটি একটি ফাইল আইও স্ট্রিম। কোন বিভাজন পুনরুক্তি ব্যবহার করা হবে?

  • আমরা parallel()পাইপলাইনে কোথায় রাখি তা বিবেচ্য নয়। আসল ইনপুট উত্সটি সর্বদা বিভক্ত হবে এবং বাকি মধ্যবর্তী ক্রিয়াকলাপগুলি প্রয়োগ করা হবে।

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

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


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

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

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

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

1
@ ইউজিন যখন Pathস্থানীয় ফাইল সিস্টেমে রয়েছে এবং আপনি একটি সাম্প্রতিক জেডিকে ব্যবহার করছেন, স্প্লিটেটরেটারে 1024 এর গুণমানের তুলনায় আরও ভাল সমান্তরাল প্রক্রিয়াকরণ ক্ষমতা থাকবে But তবে ভারসাম্যপূর্ণ বিভাজন কিছু findFirstপরিস্থিতিতে দৃ counter়ভাবে উত্পাদনশীলও হতে পারে …
হোলার

উত্তর:


8

এটি ব্যাখ্যা করে যে ম্যাপের ফলাফলের পরিবর্তে মূল ইনপুট উত্স (ফাইল লাইন) স্তরে কেন সমান্তরালতা ঘটেছিল (যেমন রেকর্ড পোজো)।

পুরো স্ট্রিমটি হয় সমান্তরাল বা অনুক্রমিক। ক্রমান্বয়ে বা সমান্তরালে চলতে আমরা অপারেশনের একটি উপসেট নির্বাচন করি না।

টার্মিনাল ক্রিয়াকলাপটি শুরু করা হলে, প্রবাহের পাইপলাইনটি প্রবাহিত ধারাটির প্রবর্তনের উপর নির্ভর করে ধারাবাহিকভাবে বা সমান্তরালভাবে কার্যকর করা হয় uted [...] টার্মিনাল অপারেশন শুরু করা হলে, প্রবাহের পাইপলাইনটি প্রবাহিত মোডের উপর নির্ভর করে ধারাবাহিকভাবে বা সমান্তরালভাবে সঞ্চালিত হয়। একই উত্স

আপনি উল্লেখ হিসাবে, সমান্তরাল স্ট্রীমগুলি বিভক্ত পুনরাবৃত্তি ব্যবহার করে। স্পষ্টতই, অপারেশনগুলি চলমান শুরু হওয়ার আগে এটি ডেটা ভাগ করা।


আমার ক্ষেত্রে, ইনপুটটি একটি ফাইল আইও স্ট্রিম। কোন বিভাজন পুনরুক্তি ব্যবহার করা হবে?

উত্সের দিকে তাকিয়ে দেখছি এটি ব্যবহার করে java.nio.file.FileChannelLinesSpliterator


পাইপলাইনে আমরা সমান্তরাল () কোথায় রাখি তা বিবেচ্য নয়। আসল ইনপুট উত্সটি সর্বদা বিভক্ত হবে এবং বাকি মধ্যবর্তী ক্রিয়াকলাপগুলি প্রয়োগ করা হবে।

ঠিক। এমনকি আপনি কল করতে পারেন parallel()এবং sequential()একাধিকবার। সর্বশেষ আহ্বান করা একজন জিতবে। আমরা যখন কল করি তখন আমরা parallel()সেট করেছিলাম যে ফিরে আসা প্রবাহের জন্য; এবং উপরে উল্লিখিত হিসাবে, সমস্ত ক্রিয়াকলাপ ক্রমান্বয়ে বা সমান্তরালভাবে চলে।


এই ক্ষেত্রে, জাভা ব্যবহারকারীদের মূল উত্স ব্যতীত পাইপলাইনে কোথাও সমান্তরাল অপারেশন করার অনুমতি দেয় না ...

এটি মতামত একটি বিষয় হয়ে ওঠে। আমি মনে করি জাবুজা জেডিকে ডিজাইনারদের পছন্দকে সমর্থন করার জন্য একটি ভাল কারণ দিয়েছে।


অর্জনের এক উপায় হ'ল আমার নিজস্ব কাস্টমাইজড স্প্লিট পুনরায় পাঠক। অন্য কোন উপায আছে কি?

এটি আপনার কার্যক্রমের উপর নির্ভর করে on

  • যদি findFirst()আপনার আসল টার্মিনাল অপারেশন হয়, তবে আপনাকে প্যারালাল এক্সিকিউশন সম্পর্কেও চিন্তা করার দরকার নেই, কারণ যে doSomething()কোনওভাবেই অনেক কল আসবে না ( findFirst()শর্ট সার্কিট হচ্ছে)। .parallel()প্রকৃতপক্ষে একের অধিক উপাদানকে প্রক্রিয়াজাত করা হতে পারে, যখন findFirst()ক্রম প্রবাহে তা আটকাতে পারে।
  • যদি আপনার টার্মিনাল অপারেশনটি খুব বেশি ডেটা তৈরি করে না, তবে সম্ভবত আপনি Recordঅনুক্রমিক স্ট্রিম ব্যবহার করে আপনার অবজেক্ট তৈরি করতে পারেন , তারপরে ফলাফলটি সমান্তরালে প্রক্রিয়া করুন:

    List<Record> smallData = Files.lines(inputFile.toPath(), 
                                         StandardCharsets.UTF_8)
      .map(record -> new Record(recordNumber.incrementAndGet(), record)) 
      .collect(Collectors.toList())
      .parallelStream()     
      .filter(record -> doSomeOperation())
      .collect(Collectors.toList());
    
  • যদি আপনার পাইপলাইনে মেমরিতে প্রচুর ডেটা লোড হয়ে যায় (যার কারণ আপনি Files.lines()সম্ভবত ব্যবহার করছেন ), তবে সম্ভবত আপনার একটি কাস্টম বিভাজন পুনরুক্তি প্রয়োজন। যদিও আমি সেখানে যাওয়ার আগে আমি অন্যান্য বিকল্পগুলি খতিয়ে দেখি (শুরু করার জন্য একটি আইডি কলাম সহ এ জাতীয় সংরক্ষণের লাইন - এটি কেবল আমার মতামত)।
    আমি আরও ছোট ব্যাচে রেকর্ড প্রক্রিয়া করার চেষ্টা করব:

    AtomicInteger recordNumber = new AtomicInteger();
    final int batchSize = 10;
    
    try(BufferedReader reader = Files.newBufferedReader(inputFile.toPath(), 
            StandardCharsets.UTF_8);) {
        Supplier<List<Record>> batchSupplier = () -> {
            List<Record> batch = new ArrayList<>();
            for (int i = 0; i < batchSize; i++) {
                String nextLine;
                try {
                    nextLine = reader.readLine();
                } catch (IOException e) {
                    //hanlde exception
                    throw new RuntimeException(e);
                }
    
                if(null == nextLine) 
                    return batch;
                batch.add(new Record(recordNumber.getAndIncrement(), nextLine));
            }
            System.out.println("next batch");
    
            return batch;
        };
    
        Stream.generate(batchSupplier)
            .takeWhile(list -> list.size() >= batchSize)
            .map(list -> list.parallelStream()
                             .filter(record -> doSomeOperation())
                             .collect(Collectors.toList()))
            .flatMap(List::stream)
            .forEach(System.out::println);
    }
    

    এটি doSomeOperation()সমস্ত ডেটা মেমরিতে লোড না করে সমান্তরালে চালায় । তবে নোট করুন যে batchSizeএকটি চিন্তা দেওয়া প্রয়োজন হবে।


1
স্পষ্টতার জন্য ধন্যবাদ। আপনি হাইলাইট করেছেন তৃতীয় সমাধান সম্পর্কে জানা ভাল। আমি টিকহাইল এবং সরবরাহকারী ব্যবহার না করায় আমি একবার নজর দেব।
এক্সপ্লোরার

2
Spliteratorআরও কার্যকর সমান্তরাল প্রক্রিয়াজাতকরণের অনুমতি দেওয়ার সাথে সাথে একটি কাস্টম বাস্তবায়ন জটিলতর হবে না ...
হোলার

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

1
বহিরাগত প্রবাহকে সমান্তরালে বাঁকানো বর্তমান বাস্তবায়নের অভ্যন্তরের সাথে খারাপ হস্তক্ষেপের কারণ হতে পারে, এমন বিন্দুটি যা Stream.generateএকটি সীমানাবিহীন প্রবাহ উত্পাদন করে, যা ওপি'র মতো ব্যবহারের ক্ষেত্রে যেমন কাজ করে না findFirst()। বিপরীতে, একটি বিভাজনযুক্ত একক সমান্তরাল স্ট্রিম যা trySplitকাজগুলিতে সরাসরি-সামনের দিকে ফিরে আসে এবং শ্রমিকের থ্রেডগুলি পূর্বের সমাপ্তির জন্য অপেক্ষা না করে পরবর্তী অংশটি প্রক্রিয়া করার অনুমতি দেয়।
হলগার

2
ধরে নেওয়ার কোনও কারণ নেই যে কোনও findFirst()অপারেশন কেবলমাত্র অল্প সংখ্যক উপাদানকেই প্রক্রিয়া করবে। সমস্ত উপাদানগুলির 90% প্রক্রিয়াকরণের পরে এখনও প্রথম ম্যাচটি ঘটতে পারে। তদ্ব্যতীত, দশ মিলিয়ন লাইন থাকাকালীন, এমনকি 10% এর পরেও একটি মিল খুঁজে পেতে 10 মিলিয়ন লাইনের প্রক্রিয়াজাতকরণ প্রয়োজন।
হলগার

7

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

Spliteratorব্যবহারের ক্ষেত্রে আসল Files.lines(…)বাস্তবায়ন-নির্ভর। জাভা 8 (ওরাকল বা ওপেনজেডিকে) এ আপনি সর্বদা একই সাথে পান BufferedReader.lines()। আরও সাম্প্রতিক জেডিকে-তে, যদি Pathডিফল্ট ফাইল সিস্টেমের অন্তর্ভুক্ত থাকে এবং চারসেট এই বৈশিষ্ট্যের জন্য সমর্থিতগুলির মধ্যে একটি হয় তবে আপনি একটি উত্সর্গীকৃত Spliteratorবাস্তবায়ন সহ একটি স্ট্রিম পাবেন java.nio.file.FileChannelLinesSpliterator। পূর্বশর্তগুলি যদি পূরণ না হয় তবে আপনি একই হিসাবে পান BufferedReader.lines()যা এখনও কার্যকরভাবে Iteratorপ্রয়োগ করা হয় BufferedReaderএবং এর মাধ্যমে আবৃত হয় Spliterators.spliteratorUnknownSize

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

public static Stream<Record> records(Path p) throws IOException {
    LineNoSpliterator sp = new LineNoSpliterator(p);
    return StreamSupport.stream(sp, false).onClose(sp);
}

private static class LineNoSpliterator implements Spliterator<Record>, Runnable {
    int chunkSize = 100;
    SeekableByteChannel channel;
    LineNumberReader reader;

    LineNoSpliterator(Path path) throws IOException {
        channel = Files.newByteChannel(path, StandardOpenOption.READ);
        reader=new LineNumberReader(Channels.newReader(channel,StandardCharsets.UTF_8));
    }

    @Override
    public void run() {
        try(Closeable c1 = reader; Closeable c2 = channel) {}
        catch(IOException ex) { throw new UncheckedIOException(ex); }
        finally { reader = null; channel = null; }
    }

    @Override
    public boolean tryAdvance(Consumer<? super Record> action) {
        try {
            String line = reader.readLine();
            if(line == null) return false;
            action.accept(new Record(reader.getLineNumber(), line));
            return true;
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    @Override
    public Spliterator<Record> trySplit() {
        Record[] chunks = new Record[chunkSize];
        int read;
        for(read = 0; read < chunks.length; read++) {
            int pos = read;
            if(!tryAdvance(r -> chunks[pos] = r)) break;
        }
        return Spliterators.spliterator(chunks, 0, read, characteristics());
    }

    @Override
    public long estimateSize() {
        try {
            return (channel.size() - channel.position()) / 60;
        } catch (IOException ex) {
            return 0;
        }
    }

    @Override
    public int characteristics() {
        return ORDERED | NONNULL | DISTINCT;
    }
}

0

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

IntStream.rangeClosed (1,20).peek(a->System.out.print(a+" "))
        .map(a->a + 200).sum();
System.out.println();
IntStream.rangeClosed(1,20).peek(a->System.out.print(a+" "))
        .map(a->a + 200).parallel().sum();
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.