আপনি কি অজানা আকারের ভারসাম্যহীন স্প্লিটেটরটিকে ভারসাম্য বজায় রাখতে পারবেন?


12

আমি Streamঅজানা সংখ্যার দূরবর্তীভাবে সঞ্চিত জেএসওএন ফাইলগুলির একটি ভিন্ন ভিন্ন সেটগুলির সমান্তরাল প্রক্রিয়াকরণের জন্য একটি ব্যবহার করতে চাই (ফাইলগুলির সংখ্যাটি সামনে জানা যায় না)। ফাইলগুলি আকারে বিভিন্ন আকারে পরিবর্তিত হতে পারে, প্রতি ফাইল 1 জেএসওএন রেকর্ড থেকে অন্য কয়েকটি ফাইলে 100,000 রেকর্ড পর্যন্ত। এই ক্ষেত্রে একটি JSON রেকর্ডের অর্থ ফাইলটিতে একটি লাইন হিসাবে প্রতিনিধিত্ব করা একটি স্ব-অন্তর্ভুক্ত JSON অবজেক্ট।

আমি সত্যিই এর জন্য স্ট্রিম ব্যবহার করতে চাই এবং তাই আমি এটি প্রয়োগ করেছি Spliterator:

public abstract class JsonStreamSpliterator<METADATA, RECORD> extends AbstractSpliterator<RECORD> {

    abstract protected JsonStreamSupport<METADATA> openInputStream(String path);

    abstract protected RECORD parse(METADATA metadata, Map<String, Object> json);

    private static final int ADDITIONAL_CHARACTERISTICS = Spliterator.IMMUTABLE | Spliterator.DISTINCT | Spliterator.NONNULL;
    private static final int MAX_BUFFER = 100;
    private final Iterator<String> paths;
    private JsonStreamSupport<METADATA> reader = null;

    public JsonStreamSpliterator(Iterator<String> paths) {
        this(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths);
    }

    private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths) {
        super(est, additionalCharacteristics);
        this.paths = paths;
    }

    private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths, String nextPath) {
        this(est, additionalCharacteristics, paths);
        open(nextPath);
    }

    @Override
    public boolean tryAdvance(Consumer<? super RECORD> action) {
        if(reader == null) {
            String path = takeNextPath();
            if(path != null) {
                open(path);
            }
            else {
                return false;
            }
        }
        Map<String, Object> json = reader.readJsonLine();
        if(json != null) {
            RECORD item = parse(reader.getMetadata(), json);
            action.accept(item);
            return true;
        }
        else {
            reader.close();
            reader = null;
            return tryAdvance(action);
        }
    }

    private void open(String path) {
        reader = openInputStream(path);
    }

    private String takeNextPath() {
        synchronized(paths) {
            if(paths.hasNext()) {
                return paths.next();
            }
        }
        return null;
    }

    @Override
    public Spliterator<RECORD> trySplit() {
        String nextPath = takeNextPath();
        if(nextPath != null) {
            return new JsonStreamSpliterator<METADATA,RECORD>(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths, nextPath) {
                @Override
                protected JsonStreamSupport<METADATA> openInputStream(String path) {
                    return JsonStreamSpliterator.this.openInputStream(path);
                }
                @Override
                protected RECORD parse(METADATA metaData, Map<String,Object> json) {
                    return JsonStreamSpliterator.this.parse(metaData, json);
                }
            };              
        }
        else {
            List<RECORD> records = new ArrayList<RECORD>();
            while(tryAdvance(records::add) && records.size() < MAX_BUFFER) {
                // loop
            }
            if(records.size() != 0) {
                return records.spliterator();
            }
            else {
                return null;
            }
        }
    }
}

আমার যে সমস্যাটি হচ্ছে তা হ'ল প্রথমদিকে যখন স্ট্রিমটি সুন্দরভাবে সমান্তরাল হয়, শেষ পর্যন্ত বৃহত্তম ফাইলটি একক থ্রেডে প্রসেসিংয়ের বাকি থাকে। আমি বিশ্বাস করি যে প্রক্সিমাল কারণটি ভালভাবে নথিভুক্ত হয়েছে: স্প্লিটেটরটি "ভারসাম্যহীন"।

আরও দৃ concrete়তার সাথে দেখা যায় যে trySplitপদ্ধতিটি একটি নির্দিষ্ট পয়েন্টের পরে Stream.forEachলাইফসাইকেলে বলা হয় না , সুতরাং শেষে ছোট ছোট ব্যাচগুলি বিতরণের অতিরিক্ত যুক্তি trySplitখুব কমই সম্পাদিত হয়।

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

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

এই সমস্যাটি ঘিরে কি কোনও সহজ / সরল / প্রচলিত উপায় আছে?


2
আপনার একটি আকারের প্রাক্কলন প্রয়োজন। এটি সম্পূর্ণ জাল হতে পারে, যতক্ষণ না এটি মোটামুটি আপনার ভারসাম্যহীন বিভাজনের অনুপাত প্রতিফলিত করে। অন্যথায়, স্ট্রিমটি জানে না যে বিভাজনগুলি ভারসাম্যহীন এবং নির্দিষ্ট কিছু অংশ তৈরি হয়ে গেলে এটি বন্ধ হয়ে যাবে।
হলগার

@ আপনি কি বিশদটি বর্ণনা করতে পারেন "একবার নির্দিষ্ট সংখ্যক অংশ তৈরি হয়ে গেলে" থামবে "বা এর জন্য আমাকে জেডিকে উত্সে নির্দেশ করুন? থেমে থেমে থেমে থাকা সংখ্যা কত?
অ্যালেক্স আর

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

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

আমি দাবি করছি না যে বাস্তবায়নের কৌশলটি খুব স্মার্ট ছিল, তবে কমপক্ষে, এটি আনুমানিক আকারের কিছু পরিস্থিতিগুলির জন্য কাজ করে (অন্যথায়, এটি সম্পর্কে আরও অনেক বাগ রিপোর্ট ছিল)। সুতরাং দেখে মনে হচ্ছে, পরীক্ষাগুলির সময় আপনার পক্ষে কিছু ত্রুটি ছিল। উদাহরণস্বরূপ, আপনার প্রশ্নের কোডে, আপনি প্রসারিত করছেন AbstractSpliteratorতবে ওভাররাইড করছেন trySplit()যা কোনও কিছু বাদে কোনও খারাপ কম্বো Long.MAX_VALUE, কারণ আপনি আকারের অনুমানটি মানিয়ে নিচ্ছেন না trySplit()। তারপরে trySplit(), আকারের অনুমানটি বিভক্ত হয়ে যাওয়া উপাদানগুলির সংখ্যা দ্বারা হ্রাস করা উচিত।
হলগার

উত্তর:


0

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

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

পাঁচ বছর আগে আমি একই রকম সমস্যার সমাধান করছিলাম, আপনি আমার সমাধানটি একবার দেখতে পারেন ।


হ্যাঁ আপনি "একটি আকারের অনুমানের প্রতিবেদন করতে বাধ্য নন" এবং Long.MAX_VALUEসঠিকভাবে একটি অজানা আকারের বর্ণনা দিচ্ছেন, তবে সত্যিকারের স্ট্রিম বাস্তবায়ন তখন খারাপভাবে সম্পাদন করে না help এমনকি ThreadLocalRandom.current().nextInt(100, 100_000)আনুমানিক আকার হিসাবে ফলাফল ব্যবহার করে আরও ভাল ফলাফল পাওয়া যায়।
হলগার

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

আপনার সমাধান বন্ধ splits একটি ArraySpliteratorযা হয়েছে আনুমানিক আকার (এমনকি একটি সঠিক সাইজ)। সুতরাং স্ট্রিম বাস্তবায়ন অ্যারে আকার বনাম দেখতে পাবে Long.MAX_VALUE, এই ভারসাম্যহীনতা বিবেচনা করুন এবং "বৃহত্তর" স্প্লিটেটরটিকে (যার Long.MAX_VALUEঅর্থ "অজানা" উপেক্ষা করে ) বিভক্ত করুন, যতক্ষণ না এটি আরও বিভাজন করতে পারে না। তারপরে, যদি পর্যাপ্ত পরিমাণ না থাকে তবে এটি অ্যারে ভিত্তিক স্প্লিটেটরগুলি তাদের পরিচিত আকারগুলি ব্যবহার করে বিভক্ত করবে। হ্যাঁ, এটি খুব ভালভাবে কাজ করে, তবে আমার বক্তব্যটির বিরোধিতা করছে না যে এটির আকার নির্ধারণ করা আপনার পক্ষে কতটা দরিদ্র তা নির্বিশেষে।
হলগার

ঠিক আছে, সুতরাং এটি একটি ভুল বোঝাবুঝির মতো বলে মনে হচ্ছে --- কারণ আপনার ইনপুটটিতে আকারের প্রাক্কলনের প্রয়োজন নেই। কেবল পৃথক বিভাজনে এবং আপনি সর্বদা তা পেতে পারেন can
মার্কো টপলনিক

ঠিক আছে, আমার প্রথম মন্তব্যটি ছিল " আপনার একটি আকারের প্রাক্কলন প্রয়োজন It এটি সম্পূর্ণ জাল হতে পারে, যতক্ষণ না এটি আপনার ভারসাম্যহীন বিভক্তির অনুপাতকে প্রায় প্রতিফলিত করে " "এখানে মূল বক্তব্যটি হ'ল ওপির কোডটি একটি একক উপাদানযুক্ত অন্য স্প্লিটেটর তৈরি করে তবে এখনও একটি অজানা আকার রিপোর্ট। এটিই স্ট্রিম বাস্তবায়নকে অসহায় করে তোলে। নতুন স্প্লিটেরেটরের জন্য যে কোনও অনুমানের সংখ্যা উল্লেখযোগ্যভাবে কম Long.MAX_VALUEহবে।
হলগার

0

অনেক পরীক্ষা-নিরীক্ষার পরেও, আমি এখনও আকারের অনুমানের সাথে খেলে কোনও যুক্ত সমান্তরালতা পেতে সক্ষম হইনি। মূলত, ব্যতীত অন্য যে কোনও মানই Long.MAX_VALUEস্প্লিট্রেটারকে খুব তাড়াতাড়ি (এবং কোনও বিভাজন ছাড়াই) সমাপ্তি ঘটায়, অন্যদিকে কোনও Long.MAX_VALUEঅনুমান trySplitফিরে না আসা পর্যন্ত নিরলসভাবে ডেকে আনে null

আমি যে সমাধানটি পেয়েছি তা হ'ল স্প্লিটেটরগুলির মধ্যে অভ্যন্তরীণভাবে সংস্থানগুলি ভাগ করা এবং তাদের নিজেদের মধ্যে ভারসাম্য বজায় রাখা উচিত।

কাজের কোড:

public class AwsS3LineSpliterator<LINE> extends AbstractSpliterator<AwsS3LineInput<LINE>> {

    public final static class AwsS3LineInput<LINE> {
        final public S3ObjectSummary s3ObjectSummary;
        final public LINE lineItem;
        public AwsS3LineInput(S3ObjectSummary s3ObjectSummary, LINE lineItem) {
            this.s3ObjectSummary = s3ObjectSummary;
            this.lineItem = lineItem;
        }
    }

    private final class InputStreamHandler {
        final S3ObjectSummary file;
        final InputStream inputStream;
        InputStreamHandler(S3ObjectSummary file, InputStream is) {
            this.file = file;
            this.inputStream = is;
        }
    }

    private final Iterator<S3ObjectSummary> incomingFiles;

    private final Function<S3ObjectSummary, InputStream> fileOpener;

    private final Function<InputStream, LINE> lineReader;

    private final Deque<S3ObjectSummary> unopenedFiles;

    private final Deque<InputStreamHandler> openedFiles;

    private final Deque<AwsS3LineInput<LINE>> sharedBuffer;

    private final int maxBuffer;

    private AwsS3LineSpliterator(Iterator<S3ObjectSummary> incomingFiles, Function<S3ObjectSummary, InputStream> fileOpener,
            Function<InputStream, LINE> lineReader,
            Deque<S3ObjectSummary> unopenedFiles, Deque<InputStreamHandler> openedFiles, Deque<AwsS3LineInput<LINE>> sharedBuffer,
            int maxBuffer) {
        super(Long.MAX_VALUE, 0);
        this.incomingFiles = incomingFiles;
        this.fileOpener = fileOpener;
        this.lineReader = lineReader;
        this.unopenedFiles = unopenedFiles;
        this.openedFiles = openedFiles;
        this.sharedBuffer = sharedBuffer;
        this.maxBuffer = maxBuffer;
    }

    public AwsS3LineSpliterator(Iterator<S3ObjectSummary> incomingFiles, Function<S3ObjectSummary, InputStream> fileOpener, Function<InputStream, LINE> lineReader, int maxBuffer) {
        this(incomingFiles, fileOpener, lineReader, new ConcurrentLinkedDeque<>(), new ConcurrentLinkedDeque<>(), new ArrayDeque<>(maxBuffer), maxBuffer);
    }

    @Override
    public boolean tryAdvance(Consumer<? super AwsS3LineInput<LINE>> action) {
        AwsS3LineInput<LINE> lineInput;
        synchronized(sharedBuffer) {
            lineInput=sharedBuffer.poll();
        }
        if(lineInput != null) {
            action.accept(lineInput);
            return true;
        }
        InputStreamHandler handle = openedFiles.poll();
        if(handle == null) {
            S3ObjectSummary unopenedFile = unopenedFiles.poll();
            if(unopenedFile == null) {
                return false;
            }
            handle = new InputStreamHandler(unopenedFile, fileOpener.apply(unopenedFile));
        }
        for(int i=0; i < maxBuffer; ++i) {
            LINE line = lineReader.apply(handle.inputStream);
            if(line != null) {
                synchronized(sharedBuffer) {
                    sharedBuffer.add(new AwsS3LineInput<LINE>(handle.file, line));
                }
            }
            else {
                return tryAdvance(action);
            }
        }
        openedFiles.addFirst(handle);
        return tryAdvance(action);
    }

    @Override
    public Spliterator<AwsS3LineInput<LINE>> trySplit() {
        synchronized(incomingFiles) {
            if (incomingFiles.hasNext()) {
                unopenedFiles.add(incomingFiles.next());
                return new AwsS3LineSpliterator<LINE>(incomingFiles, fileOpener, lineReader, unopenedFiles, openedFiles, sharedBuffer, maxBuffer);
            } else {
                return null;
            }
        }
    }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.