জাভা: ম্যানুয়ালি-অনিবন্ধিত লুপটি মূল লুপের চেয়ে এখনও দ্রুত। কেন?


13

দৈর্ঘ্যের 2 টি অ্যারেতে কোডের নিম্নোক্ত দুটি স্নিপেটগুলি বিবেচনা করুন:

boolean isOK(int i) {
    for (int j = 0; j < filters.length; ++j) {
        if (!filters[j].isOK(i)) {
            return false;
        }
    }
    return true;
}

এবং

boolean isOK(int i) {
     return filters[0].isOK(i) && filters[1].isOK(i);
}

আমি ধরে নেব যে যথেষ্ট উষ্ণতার পরে এই দুটি টুকরোটির পারফরম্যান্স একই হওয়া উচিত।
আমি যেমন এখানে এবং এখানে বর্ণিত জেএমএইচ মাইক্রো-বেঞ্চমার্কিং ফ্রেমওয়ার্ক ব্যবহার করে এটি পরীক্ষা করেছি এবং লক্ষ্য করেছি যে দ্বিতীয় স্নিপেটটি 10% এরও বেশি দ্রুত is

প্রশ্ন: কেন জাভা আমার প্রথম স্নিপেটটি বেসিক লুপ আন্রোলিং কৌশলটি ব্যবহার করে অনুকূলিত করে নি?
বিশেষত, আমি নিম্নলিখিতগুলি বুঝতে চাই:

  1. আমি সহজে একটি কোড 2 ফিল্টার মামলা জন্য অনুকূল এবং এখনও ফিল্টার অন্য একটি নম্বর ক্ষেত্রে কাজ করতে পারেন যে (একটি সহজ রচয়িতা কল্পনা) তৈরী করতে পারে:
    return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)। জেআইটিসিও কি একই কাজ করতে পারে এবং তা না হলে কেন?
  2. জেআইটিসি কি সনাক্ত করতে পারে যে ' ফিল্টারস দৈর্ঘ্য == 2 ' সবচেয়ে ঘন ঘন কেস এবং কিছু ওয়ার্ম-আপ করার পরে এই মামলার অনুকূল যে কোডটি তৈরি করতে পারে? এটি ম্যানুয়ালি-নথিভুক্ত সংস্করণ হিসাবে প্রায় অনুকূল হতে হবে।
  3. জেআইটিসি সনাক্ত করতে পারে যে কোনও নির্দিষ্ট উদাহরণ খুব ঘন ঘন ব্যবহৃত হয় এবং তারপরে এই নির্দিষ্ট উদাহরণের জন্য একটি কোড তৈরি করতে পারে (যার জন্য এটি জানেন যে ফিল্টারগুলির সংখ্যা সর্বদা 2)?
    আপডেট: একটি উত্তর পেয়েছে যে জেআইটিসি কেবল একটি শ্রেণির স্তরে কাজ করে। ঠিক আছে বুঝেছি.

আদর্শভাবে, আমি জেআইটিসি কীভাবে কাজ করে তার গভীর বোঝার সাথে কারও কাছ থেকে উত্তর পেতে চাই।

বেঞ্চমার্ক চালানোর বিশদ:

  • জাভা 8 ওপেনজেডিকে এবং ওরাকল হটস্পটের সর্বশেষ সংস্করণে চেষ্টা করা, ফলাফলগুলি একই রকম
  • ব্যবহৃত জাভা পতাকা: -Xmx4g -Xms4g -server -Xbatch -XX: CICompilerCount = 2 (অভিনব পতাকা ছাড়াও একই রকম ফলাফল পেয়েছে)
  • যাইহোক, আমি যদি একইভাবে একটি লুপ (জেএমএইচ দিয়ে নয়) কয়েক বিলিয়ন বার চালিত করি, তবে দ্বিতীয় স্নিপেটটি সর্বদা পরিষ্কারভাবে দ্রুত হয়

সাধারণ বেঞ্চমার্ক আউটপুট:

বেঞ্চমার্ক (ফিল্টারইনডেক্স) মোড সিএনটি স্কোর ত্রুটি ইউনিট
লুপউনরোলিংবেঞ্চমার্ক.আরুনবঞ্চমার্ক 0 গড় 400 44.202 ± 0.224 এনএস / অপ
লুপউনرول্লিংবেঞ্চমার্ক.আরুনবেঞ্চমার্ক 1 গড় 400 38.347 ± 0.063 এনএস / ওপেন

(প্রথম লাইনটি প্রথম স্নিপেটের সাথে মিলিত হয়, দ্বিতীয় লাইন - দ্বিতীয়টিতে।

সম্পূর্ণ বেঞ্চমার্ক কোড:

public class LoopUnrollingBenchmark {

    @State(Scope.Benchmark)
    public static class BenchmarkData {
        public Filter[] filters;
        @Param({"0", "1"})
        public int filterIndex;
        public int num;

        @Setup(Level.Invocation) //similar ratio with Level.TRIAL
        public void setUp() {
            filters = new Filter[]{new FilterChain1(), new FilterChain2()};
            num = new Random().nextInt();
        }
    }

    @Benchmark
    @Fork(warmups = 5, value = 20)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public int runBenchmark(BenchmarkData data) {
        Filter filter = data.filters[data.filterIndex];
        int sum = 0;
        int num = data.num;
        if (filter.isOK(num)) {
            ++sum;
        }
        if (filter.isOK(num + 1)) {
            ++sum;
        }
        if (filter.isOK(num - 1)) {
            ++sum;
        }
        if (filter.isOK(num * 2)) {
            ++sum;
        }
        if (filter.isOK(num * 3)) {
            ++sum;
        }
        if (filter.isOK(num * 5)) {
            ++sum;
        }
        return sum;
    }


    interface Filter {
        boolean isOK(int i);
    }

    static class Filter1 implements Filter {
        @Override
        public boolean isOK(int i) {
            return i % 3 == 1;
        }
    }

    static class Filter2 implements Filter {
        @Override
        public boolean isOK(int i) {
            return i % 7 == 3;
        }
    }

    static class FilterChain1 implements Filter {
        final Filter[] filters = createLeafFilters();

        @Override
        public boolean isOK(int i) {
            for (int j = 0; j < filters.length; ++j) {
                if (!filters[j].isOK(i)) {
                    return false;
                }
            }
            return true;
        }
    }

    static class FilterChain2 implements Filter {
        final Filter[] filters = createLeafFilters();

        @Override
        public boolean isOK(int i) {
            return filters[0].isOK(i) && filters[1].isOK(i);
        }
    }

    private static Filter[] createLeafFilters() {
        Filter[] filters = new Filter[2];
        filters[0] = new Filter1();
        filters[1] = new Filter2();
        return filters;
    }

    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }
}

1
সংকলক গ্যারান্টি দিতে পারে না যে অ্যারের দৈর্ঘ্য 2 I'm আমি নিশ্চিত নই যে এটি এটি তালিকাভুক্ত হবে যদিও তা পারলেও।
marstran

1
@Setup(Level.Invocation): নিশ্চিত নয় যে এটি সাহায্য করে (জাভাদোকটি দেখুন)।
জিপিআই

3
যেহেতু কোথাও কোনও গ্যারান্টি নেই যে অ্যারেটি সর্বদা দৈর্ঘ্য 2, তাই দুটি পদ্ধতি একই জিনিস করছে না। জেআইটি তখন কীভাবে নিজেকে প্রথমটিতে দ্বিতীয়টিতে পরিবর্তিত হতে দেয়?
অ্যান্ড্রেয়াস

@ আন্দ্রেয়াস আমি আপনাকে প্রশ্নের উত্তর দেওয়ার পরামর্শ দিচ্ছি, তবে জেআইটি কেন এইরকম আরও কিছু মামলার তুলনায় এই মামলায় নাম তালিকাভুক্ত করতে পারছে না তা ব্যাখ্যা করুন
আলেকজান্ডার

1
@Alexander জে আই টি JIT করতে দেখি যে অ্যারে দৈর্ঘ্য, সৃষ্টির পর পরিবর্তন করতে পারবেন না কারণ ক্ষেত্র finalকিন্তু জে আই টি JIT দেখে না যে, সমস্ত উদাহরণ ক্লাসের দৈর্ঘ্য 2. একটি অ্যারের পাবে দেখার জন্য, তা ডুব করতে হবে createLeafFilters()পদ্ধতিটি এবং কোডটি গভীরভাবে বিশ্লেষণ করে শিখুন যে অ্যারে সর্বদা 2 দীর্ঘ হবে। আপনি কেন বিশ্বাস করেন যে জেআইটি অপ্টিমাইজার আপনার কোডের গভীরে ডুব দেবে?
অ্যান্ড্রেয়াস

উত্তর:


10

টিএল; ডিআর এখানে পারফরম্যান্স পার্থক্যের মূল কারণ লুপ আন্রোলিং সম্পর্কিত নয়। এটি বরং ধরণের জল্পনা এবং ইনলাইন ক্যাশে

আনারোলিং কৌশলগুলি

বস্তুত, হটস্পট পরিভাষা, এই ধরনের লুপ হিসাবে গণ্য হয় গণনা , এবং নির্দিষ্ট ক্ষেত্রে জেভিএম করতে তাদের উদ্ঘাটিত করা। যদিও আপনার ক্ষেত্রে না।

হটস্পটের দুটি লুপ আন্রোলিং কৌশল রয়েছে: 1) সর্বাধিক তালিকাভুক্ত করা, অর্থাত্ লুপটি পুরোপুরি সরিয়ে ফেলুন; বা 2) একসাথে বেশ কয়েকটি পরপর পুনরাবৃত্তি আঠালো।

সর্বাধিক তালিকাভুক্তি করা যেতে পারে, কেবল যদি পুনরাবৃত্তির সঠিক সংখ্যাটি জানা থাকে

  if (!cl->has_exact_trip_count()) {
    // Trip count is not exact.
    return false;
  }

আপনার ক্ষেত্রে যাইহোক, প্রথম পুনরাবৃত্তির পরে ফাংশনটি ফিরে আসতে পারে।

আংশিক তালিকাভুক্তি সম্ভবত প্রয়োগ করা যেতে পারে, তবে নিম্নলিখিত শর্তটি এনআরোলিং ভেঙে:

  // Don't unroll if the next round of unrolling would push us
  // over the expected trip count of the loop.  One is subtracted
  // from the expected trip count because the pre-loop normally
  // executes 1 iteration.
  if (UnrollLimitForProfileCheck > 0 &&
      cl->profile_trip_cnt() != COUNT_UNKNOWN &&
      future_unroll_ct        > UnrollLimitForProfileCheck &&
      (float)future_unroll_ct > cl->profile_trip_cnt() - 1.0) {
    return false;
  }

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

অনুমান টাইপ করুন

আপনার নিবন্ধিত সংস্করণে দুটি পৃথক invokeinterfaceবাইকোড রয়েছে। এই সাইটগুলিতে দুটি স্বতন্ত্র ধরণের প্রোফাইল রয়েছে। প্রথম রিসিভার সর্বদা Filter1এবং দ্বিতীয় গ্রহণকারী সর্বদা থাকে Filter2। সুতরাং, আপনার কাছে মূলত দুটি মনমোরফিক কল সাইট রয়েছে এবং হটস্পট উভয় কলকে পুরোপুরি ইনলাইন করতে পারে - তথাকথিত "ইনলাইন ক্যাশে" যার ক্ষেত্রে এই ক্ষেত্রে 100% হিট অনুপাত রয়েছে।

লুপটি সহ, কেবলমাত্র একটি invokeinterfaceবাইটোকোড রয়েছে এবং কেবলমাত্র এক ধরণের প্রোফাইল সংগ্রহ করা হয়। হটস্পট জেভিএম দেখেছে যা রিসিভারের filters[j].isOK()সাথে 86% বার এবং Filter1রিসিভারের সাথে 14% বার বলা হয় Filter2। এটি একটি দ্বি-দ্বিস্থায়ী কল হবে be ভাগ্যক্রমে, হটস্পট অনুমানমূলকভাবে বাইমোরফিক কলগুলিকেও ইনলাইন করতে পারে। এটি শর্তযুক্ত শাখার সাথে উভয় লক্ষ্যকেই ইনলাইন করে। তবে, এক্ষেত্রে হিট অনুপাত সর্বাধিক ৮%% হবে এবং আর্কিটেকচার স্তরে সম্পর্কিত ভুল অনুমানিত শাখাগুলির দ্বারা কর্মক্ষমতা ক্ষতিগ্রস্থ হবে।

আপনার কাছে 3 বা আরও বেশি ফিল্টার থাকলে জিনিসগুলি আরও খারাপ হবে। এই ক্ষেত্রে isOK()একটি megamorphic কল হতে হবে যা হটস্পট এ সব ইনলাইন করতে পারেন না। সুতরাং, সংকলিত কোডটিতে একটি সত্য ইন্টারফেস কল থাকবে যা একটি বৃহত্তর কর্মক্ষমতা প্রভাব ফেলে has

ব্ল্যাক ম্যাজিক অফ (জাভা) পদ্ধতি প্রেরণা নিবন্ধে অনুমানমূলক ইনলাইনিং সম্পর্কে আরও ।

উপসংহার

ভার্চুয়াল / ইন্টারফেস কলগুলিকে ইনলাইন করতে, হটস্পট জেভিএম প্রতিযোগিতা বাইকোডে প্রকারের প্রোফাইল সংগ্রহ করে। যদি কোনও লুপে ভার্চুয়াল কল থাকে তবে কলটির জন্য কেবলমাত্র এক প্রকারের প্রোফাইল থাকবে, লুপটি অনিয়ন্ত্রিত কিনা তা বিবেচনা করে না।

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


মহান উত্তরের জন্য ধন্যবাদ। কেবল সম্পূর্ণতার জন্য: আপনি কি কোনও জেআইটিসি কৌশল সম্পর্কে অবগত আছেন যা একটি নির্দিষ্ট উদাহরণের জন্য কোড তৈরি করতে পারে?
আলেকজান্ডার 13

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

13

উপস্থাপিত লুপটি সম্ভবত "নন গণনা" বিভাগের অন্তর্গত হয় যা লুপগুলি যার জন্য পুনরাবৃত্তি গণনাটি সংকলন সময় বা রান সময় নির্ধারণ করা যায় না। অ্যারের আকার সম্পর্কে @ অ্যান্ড্রেয়াস যুক্তির কারণেই নয় কেবল এলোমেলো শর্তসাপেক্ষ কারণেও break(যখন আমি এই পোস্টটি লিখতাম তখন আপনার মানদণ্ডে ব্যবহৃত হত)।

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

এটি অনুসরণ করে, আপনার অনুমানটি ধরে রাখে না যে আপনি লুপটির "ম্যানুয়াল আন্রোলিং" করেছেন। শর্তসাপেক্ষ বিরতিতে একটি &&শৃঙ্খলিত বুলিয়ান অভিব্যক্তিতে রূপান্তর করার জন্য আপনি এটি একটি মৌলিক লুপ আন্রোলিং কৌশল বিবেচনা করছেন technique আমি এটি বরং একটি বিশেষ কেস বিবেচনা করব এবং হট স্পট অপ্টিমাইজারটি ফ্লাইতে জটিল রিফ্যাক্টরিংয়ের কাজটি করতে পেরে অবাক হব। এটি এখানে কীভাবে এটি করতে পারে তা নিয়ে আলোচনা করছেন সম্ভবত এই উল্লেখটি আকর্ষণীয়।

এটি সমসাময়িক আনারোলিংয়ের মেকানিকগুলিকে আরও ঘনিষ্ঠভাবে প্রতিফলিত করবে এবং আনারোলড মেশিন কোডটির মতো দেখতে সম্ভবত এখনও কোথাও নেই:

if (! filters[0].isOK(i))
{
   return false;
} 
if(! filters[1].isOK(i))
{
   return false;
}
return true;

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

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

যেহেতু আপনার লক্ষ্য ন্যূনতম রানটাইম, আপনি a && b && c ...সম্ভবত উপস্থাপিত যে কোনও কিছুর চেয়ে লুপ-আন-তালিকাভুক্ত হওয়ার আশায় নির্ভর করতে না চান , তবে ফর্মটি সম্ভবত সবচেয়ে কার্যকর one তবে আপনার এটি সাধারণ পদ্ধতিতে থাকতে পারে না। Java.util.Function এর কার্যকরী রচনার সাথে আবার বিশাল ওভারহেড রয়েছে (প্রতিটি ফাংশন একটি শ্রেণি, প্রতিটি কল একটি ভার্চুয়াল পদ্ধতি যা প্রেরণের প্রয়োজন)। সম্ভবত এই জাতীয় পরিস্থিতিতে ভাষা স্তরটি বিকল করা এবং রানটাইমের সময় কাস্টম বাইট কোড তৈরি করা বুদ্ধিমান হতে পারে । অন্যদিকে একটি &&যুক্তিকে বাইট কোড স্তরটিতেও শাখা করা প্রয়োজন এবং যদি / ফেরার সমতুল্য হতে পারে (যা ওভারহেড ছাড়া জেনারেট করা যায় না)।


শুধু একটি ছোট adendum: জেভিএম বিশ্বের একটি গণনা লুপ কোনো লুপ যে একটি ওভার "রান" হয় int i = ....; i < ...; ++iকোন অন্যান্য লুপ নয়।
ইউজিন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.