সমান্তরাল অসীম জাভা স্ট্রিমগুলি মেমরি থেকে শেষ


16

আমি নীচের জাভা প্রোগ্রামটি একটি কেন দেয় তা বোঝার চেষ্টা করছি OutOfMemoryError, যখন সংশ্লিষ্ট প্রোগ্রামটি না .parallel()করে দেয় without

System.out.println(Stream
    .iterate(1, i -> i+1)
    .parallel()
    .flatMap(n -> Stream.iterate(n, i -> i+n))
    .mapToInt(Integer::intValue)
    .limit(100_000_000)
    .sum()
);

আমার দুটি প্রশ্ন আছে:

  1. এই প্রোগ্রামের উদ্দেশ্যে আউটপুট কি?

    .parallel()এটি ছাড়া এটি দেখে মনে হচ্ছে যে এটি কেবল আউটপুট দেয় sum(1+2+3+...)যার অর্থ এটি ফ্ল্যাটম্যাপের প্রথম প্রবাহে কেবল "আটকে যায়", যা বোঝা যায়।

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

  2. স্মৃতিশক্তি শেষ হয়ে যাওয়ার কারণ কী? এই স্ট্রিমগুলি কীভাবে হুডের অধীনে প্রয়োগ করা হয় তা আমি বিশেষভাবে বুঝতে চেষ্টা করছি।

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

সম্পাদনা: যদি এটি প্রাসঙ্গিক হয় তবে আমি জাভা 11 ব্যবহার করছি।

এডিট 2: আপাতদৃষ্টিতে একই জিনিস এমনকি সাধারণ প্রোগ্রামের ক্ষেত্রেও ঘটে IntStream.iterate(1,i->i+1).limit(1000_000_000).parallel().sum(), তাই এটির limitচেয়ে বরং অলসতার সাথে কাজ করতে পারে flatMap


সমান্তরাল () অভ্যন্তরীণভাবে ফোর্কজাইনপুল ব্যবহার করে। আমি অনুমান করি যে ফোর্কজৌইন ফ্রেমওয়ার্ক জাভা 7 থেকে জাভাতে আছে
অরবিন্দ

উত্তর:


9

আপনি বলেছিলেন " তবে কোন অর্ডের জিনিসগুলি মূল্যায়ন করা হয় এবং কোথায় বাফারিং ঘটে " আমি পুরোপুরি জানি না ", যা সমান্তরাল স্ট্রিমগুলি সম্পর্কে কী তা অবিকল হয়। মূল্যায়নের ক্রমটি অনির্ধারিত।

আপনার উদাহরণ সমালোচনামূলক দিক .limit(100_000_000)। এটি সূচিত করে যে বাস্তবায়ন কেবল স্বেচ্ছাচারিত মানগুলি যোগ করতে পারে না, তবে প্রথম 100,000,000 সংখ্যা যোগ করতে হবে । দ্রষ্টব্য যে রেফারেন্স বাস্তবায়নে .unordered().limit(100_000_000)ফলাফল পরিবর্তন হয় না, যা নির্দেশ করে যে আনর্ডার্ড করা মামলার জন্য বিশেষ প্রয়োগ নেই, তবে এটি বাস্তবায়নের বিশদ।

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

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

আপনার ক্ষেত্রে, একটি প্রশংসনীয় দৃশ্যটি হ'ল অন্যান্য কর্মী থ্রেডগুলি বামতম কাজের কাজ গণনার চেয়ে বাফারগুলিকে বরাদ্দ দেওয়ার ক্ষেত্রে দ্রুত। এই দৃশ্যে, টাইমিংয়ের সূক্ষ্ম পরিবর্তনগুলি প্রবাহকে মাঝে মাঝে একটি মান দিয়ে ফিরে আসতে পারে।

বামতম অংশটির প্রক্রিয়াজাতকরণ ব্যতীত যখন আমরা সমস্ত কর্মী থ্রেডটি ধীর করে ফেলি, তখন আমরা স্ট্রিমটি শেষ করতে পারি (কমপক্ষে বেশিরভাগ রানে):

System.out.println(IntStream
    .iterate(1, i -> i+1)
    .parallel()
    .peek(i -> { if(i != 1) LockSupport.parkNanos(1_000_000_000); })
    .flatMap(n -> IntStream.iterate(n, i -> i+n))
    .limit(100_000_000)
    .sum()
);

The প্রসেসিং অর্ডারের পরিবর্তে এনকাউন্টার অর্ডার সম্পর্কে কথা বলার সময় আমি স্টুয়ার্ট মার্কসের পরামর্শ অনুসরণ করছি বাম থেকে ডান ক্রমটি ব্যবহার করার জন্য।


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

1
আপনি ব্যবহার করছেন Files.lines(…)? এটি জাভা 9-তে উল্লেখযোগ্যভাবে উন্নত করা হয়েছে
হোলার

1
এই কি এটা নতুন JREs, এটা এখনও ফিরে আসবে জাভা 8. মধ্যে নেই BufferedReader.lines()কিছু পরিস্থিতিতে (না ডিফল্ট ফাইলসিস্টেম, একটি বিশেষ অক্ষরসেট বা আকার বা চেয়ে বড় Integer.MAX_FILES)। এর মধ্যে যদি একটি প্রয়োগ হয় তবে একটি কাস্টম সমাধান সাহায্য করতে পারে। এটি একটি নতুন প্রশ্নোত্তর মূল্যবান হতে পারে ...
হোলার

1
Integer.MAX_VALUE, অবশ্যই…
হোলার

1
বাইরের স্রোত, ফাইলগুলির একটি ধারা কী? এটির একটি অনুমানযোগ্য আকার আছে?
হোলার

5

আমার সর্বোত্তম অনুমানটি হ'ল parallel()অভ্যন্তরীণ আচরণের পরিবর্তনগুলি যুক্ত করা flatMap()যা এর আগে অলসতার সাথে মূল্যায়ন করার আগে থেকেই সমস্যা ছিল

OutOfMemoryErrorত্রুটি আপনি পেয়ে রিপোর্ট পেশ করে হয় [JDK-8202307] একটি java.lang.OutOfMemoryError পথ। যখন Stream.iterator কলিং জাভা গাদা স্থান () পরবর্তী () একটি স্ট্রিম যা flatMap মধ্যে অসীম / খুব বড় স্ট্রিম ব্যবহার উপর । আপনি যদি টিকিটের দিকে তাকান তবে এটি আপনি যে স্ট্যাকটি পাচ্ছেন এটি কমবেশি কম। নীচের কারণে উইন্ড ফিক্স হিসাবে টিকিটটি বন্ধ ছিল:

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


এটা খুবই আকর্ষণীয়! এটি বোঝা যায় যে পুশ / টান স্থানান্তরের জন্য বাফারিং দরকার যা স্মৃতি ব্যবহার করতে পারে। তবে আমার ক্ষেত্রে এটি দেখে মনে হচ্ছে যে কেবল পুশ ব্যবহার করা ঠিকঠাক কাজ করা উচিত এবং বাকী উপাদানগুলি উপস্থিত হওয়ার সাথে সাথে কেবল ত্যাগ করা উচিত? অথবা আপনি বলছেন যে ফ্ল্যাপম্যাপের ফলে একটি পুনরুক্তি তৈরি হবে?
টমাস আহলে

3

ওমোম স্ট্রিমটি অসীম হওয়ার কারণে নয় , তবে বাস্তবে তা নয়

উদাহরণস্বরূপ, আপনি যদি মন্তব্য করেন তবে .limit(...)এটি কখনই মেমরির বাইরে চলে না - তবে অবশ্যই এটি কখনও শেষ হয় না।

এটি বিভক্ত হয়ে গেলে, স্ট্রিমটি প্রতিটি থ্রেডের মধ্যে জড়িত হয়ে থাকলে উপাদানগুলির সংখ্যা কেবলমাত্র ট্র্যাক রাখতে পারে (প্রকৃত সংযোজকের মতো দেখতে Spliterators$ArraySpliterator#array)।

দেখে মনে হচ্ছে আপনি এটি ছাড়াই পুনরুত্পাদন করতে পারবেন flatMap, কেবল নিম্নলিখিতটি দিয়ে চালান -Xmx128m:

    System.out.println(Stream
            .iterate(1, i -> i + 1)
            .parallel()
      //    .flatMap(n -> Stream.iterate(n, i -> i+n))
            .mapToInt(Integer::intValue)
            .limit(100_000_000)
            .sum()
    );

তবে, মন্তব্য করার পরে limit(), যতক্ষণ না আপনি আপনার ল্যাপটপটি ছাড়ার সিদ্ধান্ত নেন ততক্ষণ এটি ঠিকঠাক হওয়া উচিত।

বাস্তব প্রয়োগের বিশদগুলি ছাড়াও, আমার মনে হয় যা ঘটছে তা এখানে:

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


"একবার বিভক্ত" বলতে কী বোঝ? সীমা কি এটি কোনওভাবে বিভক্ত হয়?
টমাস আহলে

@ থমাসআহলে সমান্তরালতা অর্জনের parallel()জন্য ForkJoinPoolঅভ্যন্তরীণভাবে ব্যবহার করবে । Spliteratorপ্রত্যেকটিতে নির্ধারণ কাজ করতে ব্যবহার করা হবে ForkJoinটাস্ক, আমি মনে করি আমরা "বিভাজিত" হিসেবে কাজ করবে তা এখানে একক কল করতে পারেন।
করোল ডউবেকি

তবে কেন এটি সীমাবদ্ধতার সাথে ঘটে?
টমাস আহলে

@ থমাসআহলে আমি আমার দুটি সেন্ট দিয়ে উত্তরটি সম্পাদনা করেছি।
কোস্তি সিউদাতু

1
@ থমাস অহলে একটি ব্রেকপয়েন্ট স্থাপন করেছে Integer.sum(), যা রিডুসার দ্বারা ব্যবহৃত হয় IntStream.sum। আপনি দেখতে পাবেন যে নো-সীমাবদ্ধ সংস্করণ সেই কলটি সর্বদা ফাংশন করে, যখন সীমাবদ্ধ সংস্করণ কখনই এটি ওওমের আগে কল করে না।
কোস্তি সিউদাতু
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.