জাভা 8: স্ট্রিমস বনাম সংগ্রহের পারফরম্যান্স


140

আমি জাভা-তে নতুন 8.

পরীক্ষা একটি তালিকা ফিল্টারিং মধ্যে রয়েছে Integer, এবং প্রতিটি জোড় সংখ্যা জন্য, বর্গমূল এবং এর ফলে এটা সংরক্ষণকারী নিরূপণ Listএর Double

কোডটি এখানে:

    public static void main(String[] args) {
        //Calculating square root of even numbers from 1 to N       
        int min = 1;
        int max = 1000000;

        List<Integer> sourceList = new ArrayList<>();
        for (int i = min; i < max; i++) {
            sourceList.add(i);
        }

        List<Double> result = new LinkedList<>();


        //Collections approach
        long t0 = System.nanoTime();
        long elapsed = 0;
        for (Integer i : sourceList) {
            if(i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Stream approach
        Stream<Integer> stream = sourceList.stream();       
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Parallel stream approach
        stream = sourceList.stream().parallel();        
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
    }.

এবং এখানে একটি দ্বৈত কোর মেশিনের ফলাফল:

    Collections: Elapsed time:        94338247 ns   (0,094338 seconds)
    Streams: Elapsed time:           201112924 ns   (0,201113 seconds)
    Parallel streams: Elapsed time:  357243629 ns   (0,357244 seconds)

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

প্রশ্নাবলী:

  • এই পরীক্ষা কি মেলা? আমি কি কোন ভুল করেছি?
  • স্ট্রিমগুলি কি সংগ্রহের চেয়ে ধীর? কেউ কি এই সম্পর্কে একটি ভাল আনুষ্ঠানিক মানদণ্ড তৈরি করেছে?
  • আমার কোন পদ্ধতির জন্য চেষ্টা করা উচিত?

আপডেট ফলাফল।

আমি জেভিএম ওয়ার্মআপ (১ কে পুনরাবৃত্তি) এর পরে @pveentjer এর পরামর্শ অনুসারে 1k বার পরীক্ষাটি চালিয়েছি:

    Collections: Average time:      206884437,000000 ns     (0,206884 seconds)
    Streams: Average time:           98366725,000000 ns     (0,098367 seconds)
    Parallel streams: Average time: 167703705,000000 ns     (0,167704 seconds)

এই ক্ষেত্রে স্ট্রিমগুলি আরও পারফরম্যান্ট। আমি অবাক হয়েছি এমন কোনও অ্যাপে কী পর্যবেক্ষণ করা হবে যেখানে রানটাইমের সময় ফিল্টারিং ফাংশনটি কেবল একবার বা দুবার বলা হয়।


1
আপনি একটি IntStreamপরিবর্তে এটি চেষ্টা করে ?
মার্ক রোটভিল

2
আপনি দয়া করে সঠিকভাবে পরিমাপ করতে পারেন? যদি আপনি যা করছেন সবই যদি এক রান হয় তবে আপনার মাপদণ্ড অবশ্যই বন্ধ থাকবে।
স্কিভি

2
@ মিস্টারস্মিথ কীভাবে আপনি আপনার জেভিএমকে গরম করেছেন, 1 কে পরীক্ষার মাধ্যমে কীভাবে আমাদের স্বচ্ছতা পেতে পারে?
স্কিভি

1
: এবং সঠিক microbenchmarks লিখছি আগ্রহী তাদের জন্য, প্রশ্ন Heres stackoverflow.com/questions/504103/...
মশাই স্মিথ

2
@Asslias ব্যবহারটি toListসমান্তরালভাবে চালানো উচিত যদিও তা কোনও থ্রেড-নিরাপদ তালিকায় সংগ্রহ করা হয়, যেহেতু একত্রিত হওয়ার আগে বিভিন্ন থ্রেড থ্রেড-সীমাবদ্ধ অন্তর্বর্তী তালিকায় সংগ্রহ করবে।
স্টুয়ার্ট

উত্তর:


192
  1. LinkedListপুনরুক্তিকারী ব্যবহার করে তালিকার মাঝামাঝি থেকে ভারী অপসারণ ছাড়াও কোনও কিছুর জন্য ব্যবহার বন্ধ করুন ।

  2. হাতে হাতে বেঞ্চমার্কিং কোড লেখা বন্ধ করুন, জেএমএইচ ব্যবহার করুন

সঠিক মানদণ্ড:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
    public static final int N = 10000;

    static List<Integer> sourceList = new ArrayList<>();
    static {
        for (int i = 0; i < N; i++) {
            sourceList.add(i);
        }
    }

    @Benchmark
    public List<Double> vanilla() {
        List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
        for (Integer i : sourceList) {
            if (i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        return result;
    }

    @Benchmark
    public List<Double> stream() {
        return sourceList.stream()
                .filter(i -> i % 2 == 0)
                .map(Math::sqrt)
                .collect(Collectors.toCollection(
                    () -> new ArrayList<>(sourceList.size() / 2 + 1)));
    }
}

ফলাফল:

Benchmark                   Mode   Samples         Mean   Mean error    Units
StreamVsVanilla.stream      avgt        10       17.588        0.230    ns/op
StreamVsVanilla.vanilla     avgt        10       10.796        0.063    ns/op

আমি যেমনটি প্রত্যাশা করেছি ঠিক তেমন স্ট্রিম বাস্তবায়ন মোটামুটি ধীর। জেআইটি সমস্ত ল্যাম্বদা স্টাফগুলিকে ইনলাইন করতে সক্ষম তবে ভ্যানিলা সংস্করণের মতো নিখুঁতভাবে সংক্ষিপ্ত কোড তৈরি করে না।

সাধারণত, জাভা 8 স্ট্রিমগুলি যাদু নয়। তারা ইতিমধ্যে ভালভাবে বাস্তবায়িত জিনিসগুলিকে স্পিডআপ করতে পারেনি (সম্ভবত, সরল পুনরাবৃত্তি বা জাভা 5 এর প্রতিটি বিবৃতি Iterable.forEach()এবং Collection.removeIf()কলগুলির জন্য প্রতিস্থাপন করা হয়েছে )। স্ট্রিমগুলি কোডিং সুবিধা এবং সুরক্ষা সম্পর্কে আরও বেশি। সুবিধা - গতি বাণিজ্য এখানে কাজ করছে।


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

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

26
@ ব্রায়ানগোয়েটস, আপনি কি দয়া করে ব্যবহারের ক্ষেত্রে নির্দিষ্ট করতে পারবেন, যখন স্ট্রিমগুলি দ্রুততর হয়?
আলেকজান্ডার

1
FMH শেষ সংস্করণে: ব্যবহার @Benchmarkপরিবর্তে@GenerateMicroBenchmark
pdem

3
@ ব্রায়ানগোয়েজ, যখন স্ট্রিমগুলি দ্রুত হয় আপনি কি ব্যবহারের ক্ষেত্রে উল্লেখ করতে পারবেন?
কিলটেক

17

1) আপনি আপনার বেঞ্চমার্কটি ব্যবহার করে 1 সেকেন্ডেরও কম সময় দেখেন। এর অর্থ আপনার ফলাফলগুলিতে পার্শ্ব প্রতিক্রিয়ার শক্তিশালী প্রভাব থাকতে পারে। সুতরাং, আমি আপনার কার্য 10 বার বাড়িয়েছি

    int max = 10_000_000;

এবং আপনার মানদণ্ড চালিয়েছে। আমার ফলাফল:

Collections: Elapsed time:   8592999350 ns  (8.592999 seconds)
Streams: Elapsed time:       2068208058 ns  (2.068208 seconds)
Parallel streams: Elapsed time:  7186967071 ns  (7.186967 seconds)

সম্পাদনা ছাড়া ( int max = 1_000_000) ফলাফল ছিল

Collections: Elapsed time:   113373057 ns   (0.113373 seconds)
Streams: Elapsed time:       135570440 ns   (0.135570 seconds)
Parallel streams: Elapsed time:  104091980 ns   (0.104092 seconds)

এটি আপনার ফলাফলের মতো: সংগ্রহের চেয়ে স্ট্রিম ধীর slow উপসংহার: প্রবাহের সূচনা / মান প্রেরণের জন্য অনেক সময় ব্যয় হয়েছিল।

২) টাস্ক স্ট্রিম বাড়ানোর পরে দ্রুত পরিণত হয়েছে (এটি ঠিক আছে), তবে সমান্তরাল স্ট্রিমটি খুব ধীর হয়ে গেছে। কোনো সমস্যা? দ্রষ্টব্য: আপনি collect(Collectors.toList())আপনার কমান্ড আছে। একক সংগ্রহের জন্য সংগ্রহ সমকালীন সম্পাদনের ক্ষেত্রে মূলত পারফরম্যান্স বাধা এবং ওভারহেডের পরিচয় দেয়। প্রতিস্থাপন করে ওভারহেডের আপেক্ষিক ব্যয়ের অনুমান করা সম্ভব

collecting to collection -> counting the element count

স্ট্রিমগুলির জন্য এটি করা যেতে পারে collect(Collectors.counting())। আমি ফলাফল পেয়েছি:

Collections: Elapsed time:   41856183 ns    (0.041856 seconds)
Streams: Elapsed time:       546590322 ns   (0.546590 seconds)
Parallel streams: Elapsed time:  1540051478 ns  (1.540051 seconds)

এটি একটি বড় কাজের জন্য! ( int max = 10000000) উপসংহার: সংগ্রহের জন্য আইটেম সংগ্রহ করতে বেশিরভাগ সময় লেগেছিল। ধীরতম অংশটি তালিকায় যুক্ত হচ্ছে। বিটিডাব্লু, সিম্পল এর ArrayListজন্য ব্যবহৃত হয় Collectors.toList()


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

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

সার্ভার মোডে জেআইটি, 10 কে মৃত্যুদণ্ড কার্যকর করার পরে কিক্সড করে। এবং তারপরে কোডটি সংকলন করতে এবং এটি অদলবদল করতে কিছুটা সময় নেয়।
pveentjer

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

@ লি আমি collectএখানে বাস্তবায়ন সম্পর্কে একইভাবে মনে করি । তবে শেষ পর্যন্ত বেশ কয়েকটি তালিকাকে একটিতে একীভূত করা উচিত এবং দেখে মনে হচ্ছে যে প্রদত্ত উদাহরণটিতে একত্রে সবচেয়ে ভারী ক্রিয়াকলাপটি মার্জ করা।
সের্গে ফেদোরভ

4
    public static void main(String[] args) {
    //Calculating square root of even numbers from 1 to N       
    int min = 1;
    int max = 10000000;

    List<Integer> sourceList = new ArrayList<>();
    for (int i = min; i < max; i++) {
        sourceList.add(i);
    }

    List<Double> result = new LinkedList<>();


    //Collections approach
    long t0 = System.nanoTime();
    long elapsed = 0;
    for (Integer i : sourceList) {
        if(i % 2 == 0){
            result.add( doSomeCalculate(i));
        }
    }
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Stream approach
    Stream<Integer> stream = sourceList.stream();       
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Parallel stream approach
    stream = sourceList.stream().parallel();        
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i ->  doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
}

static double doSomeCalculate(int input) {
    for(int i=0; i<100000; i++){
        Math.sqrt(i+input);
    }
    return Math.sqrt(input);
}

আমি কোডটি কিছুটা পরিবর্তন করেছি, আমার ম্যাক বইয়ের প্রোতে ছুটেছি যেখানে 8 টি কোর রয়েছে, আমি যুক্তিসঙ্গত ফলাফল পেয়েছি:

সংগ্রহগুলি: অতিবাহিত সময়: 1522036826 এনএস (1.522037 সেকেন্ড)

স্ট্রিমগুলি: অতিবাহিত সময়: 4315833719 এনএস (4.315834 সেকেন্ড)

সমান্তরাল স্ট্রিমগুলি: অতিবাহিত সময়: 261152901 এনএস (0.261153 সেকেন্ড)


আমি মনে করি আপনার পরীক্ষাটি ন্যায্য, আপনার কেবলমাত্র একটি মেশিনের আরও সিপিইউ কোর থাকা দরকার।
মেলন

3

আপনি যা করার চেষ্টা করছেন তার জন্য আমি যাইহোক নিয়মিত জাভা এপি ব্যবহার করব না। এখানে এক টন বক্সিং / আনবক্সিং চলছে, সুতরাং সেখানে একটি বিশাল পারফরম্যান্স ওভারহেড।

ব্যক্তিগতভাবে আমি মনে করি যে ডিজাইন করা অনেকগুলি এপিআই হ'ল বক্রতা কারণ তারা প্রচুর অবজেক্ট লিটার তৈরি করে।

ডাবল / ইন্টির একটি আদিম অ্যারে ব্যবহার করার চেষ্টা করুন এবং এটি একক থ্রেডেড করার চেষ্টা করুন এবং দেখুন পারফরম্যান্সটি কী।

পিএস: আপনি হয়ত জেএমএইচটি দেখতে চান বেঞ্চমার্কটি করার যত্ন নিতে of এটি জেভিএমকে উষ্ণ করার মতো কিছু সাধারণ সমস্যাগুলির যত্ন নেয়।


লিংকডলিস্টগুলি অ্যারেলিস্টগুলির চেয়ে আরও খারাপ কারণ আপনাকে সমস্ত নোড অবজেক্ট তৈরি করতে হবে। মোড অপারেটরটি কুকুর ধীর। আমি 10/15 চক্রের মতো কিছু বিশ্বাস করি + এটি নির্দেশের পাইপলাইনটি ড্রেন করে। আপনি যদি 2 দ্বারা খুব দ্রুত বিভাগ করতে চান তবে কেবল 1 বিটটি ডানে সরিয়ে দিন। এগুলি বেসিক কৌশল, তবে আমি নিশ্চিত যে জিনিসগুলিকে গতি বাড়ানোর জন্য মোড অ্যাডভান্সড ট্রিকস রয়েছে তবে এগুলি সম্ভবত আরও নির্দিষ্ট সমস্যাযুক্ত।
pveentjer

আমি বক্সিং সম্পর্কে সচেতন। এটি কেবল একটি অনানুষ্ঠানিক মানদণ্ড। সংগ্রহ এবং স্ট্রিম উভয় পরীক্ষায় বক্সিং / আনবক্সিংয়ের সমান পরিমাণ থাকার ধারণাটি idea
মিস্টার স্মিথ

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

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

"আপনি যা করার চেষ্টা করছেন তার জন্য" শর্তে বেঞ্চমার্ক পরীক্ষার কথা চিন্তা করা এড়ানো ভাল বলে আমি মনে করি। অর্থাত্, সাধারণত এই ধরণের ব্যায়ামগুলি প্রদর্শনযোগ্য হিসাবে যথেষ্ট সরল করা হয়, তবে যথেষ্ট জটিল যেগুলি দেখতে তাদের সহজ / করা উচিত।
রিভ্যান্টেজ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.