4 বিলিয়ন-পুনরাবৃত্তির জাভা লুপটি কেন কেবল 2 এমএস নেয়?


113

আমি ল্যাপটপে ২.7 গিগাহার্টজ ইন্টেল কোর আই with সহ নিম্নলিখিত জাভা কোডটি চালাচ্ছি। 2 measure 32 পুনরাবৃত্তির সাহায্যে একটি লুপ শেষ করতে কতক্ষণ সময় লাগে তা পরিমাপ করার উদ্দেশ্যে আমি ইচ্ছা করেছি, যা আমি প্রায় 1.48 সেকেন্ড (4 / 2.7 = 1.48) বলে আশা করি।

তবে বাস্তবে এটি 1.48 s এর পরিবর্তে 2 মিলিসেকেন্ডে সময় নেয়। আমি ভাবছি যদি এটি নীচে কোনও জেভিএম অপ্টিমাইজেশনের ফলাফল?

public static void main(String[] args)
{
    long start = System.nanoTime();

    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
    }
    long finish = System.nanoTime();
    long d = (finish - start) / 1000000;

    System.out.println("Used " + d);
}

69
হ্যাঁ ঠিক. লুপের শরীরে কোনও পার্শ্ব-প্রতিক্রিয়া নেই বলে সংকলকটি বেশ আনন্দের সাথে এটিকে সরিয়ে দেয়। javap -vদেখতে বাইট-কোড পরীক্ষা করুন ।
এলিয়ট ফ্রিচ

36
আপনি এটি আর বাইট-কোডে দেখতে পাবেন না। javacখুব সামান্য প্রকৃত অপ্টিমাইজেশন করে এবং এর বেশিরভাগটি জেআইটি সংকলককে ছেড়ে দেয়।
জর্ন ভের্নি

4
'আমি ভাবছি এটি নীচে কোনও জেভিএম অপ্টিমাইজেশনের ফলাফল কিনা?' - আপনি কি মনে করেন? জেভিএম অপ্টিমাইজেশন না হলে এটি আর কী হতে পারে?
অপাঙ্গীন

7
এই প্রশ্নের উত্তরটি মূলত স্ট্যাকওভারফ্লো . com/a/25323548/3182664 এ রয়েছে । এছাড়া সমাবেশ ফলে (মেশিন কোড) যে জে আই টি JIT এই ক্ষেত্রে জন্য জেনারেট করে, দেখাচ্ছে যে রয়েছে লুপ সম্পূর্ণরূপে জে আই টি JIT দ্বারা দূরে অপ্টিমাইজ করা হয় । ( স্ট্যাকওভারফ্লো . com/q/25326377/3182664 এ প্রশ্নটি দেখায় যে লুপটি 4 বিলিয়ন অপারেশন না করে তবে আরও কিছুটা সময় নিতে পারে তবে 4 বিলিয়ন বিয়োগফল ;-))। আমি প্রায় এই প্রশ্নটিকে অন্যটির সদৃশ হিসাবে বিবেচনা করব - কোন আপত্তি?
মার্কো 13

7
আপনি ধরে নিবেন প্রসেসর প্রতি হার্জেডে একটি পুনরাবৃত্তি করবে। এটি একটি সুদূরপ্রসারী অনুমান। প্রসেসররা আজ @ রাহুলের উল্লেখ অনুসারে সমস্ত ধরণের অপ্টিমাইজেশান সম্পাদন করে এবং কোর আই 7 কীভাবে কাজ করে সে সম্পর্কে আপনি আরও বেশি কিছু না জানলে আপনি এটি ধরে নিতে পারবেন না।
সোশি আশের

উত্তর:


106

এখানে দুটি সম্ভাবনার একটি চলছে:

  1. সংকলক বুঝতে পেরেছিল যে লুপটি অতিরিক্ত কাজ করছে এবং কিছুই করছে না তাই এটি একে অপ্টিমাইজ করে।

  2. জেআইটি (কেবলমাত্র সময়ের মধ্যে সংকলক) বুঝতে পেরেছিল যে লুপটি অতিরিক্ত কাজ এবং কিছুই করছে না, তাই এটি এটিকে অপ্টিমাইজ করে।

আধুনিক সংকলক খুব বুদ্ধিমান; কোডটি অকেজো হলে তারা দেখতে পায় can গডবোল্টে একটি ফাঁকা লুপ রাখার চেষ্টা করুন এবং আউটপুটটি দেখুন, তারপরে -O2অপ্টিমাইজেশানগুলি চালু করুন , আপনি দেখতে পাবেন যে আউটপুট এর লাইনের সাথে কিছু রয়েছে

main():
    xor eax, eax
    ret

আমি কিছু পরিষ্কার করতে চাই, জাভাতে বেশিরভাগ অপটিমাইজেশন জেআইটিই করে। অন্য কয়েকটি ভাষায় (যেমন সি / সি ++) বেশিরভাগ অপটিমাইজেশন প্রথম সংকলক দ্বারা সম্পন্ন হয়।


সংকলককে কি এই ধরনের অপটিমাইজেশন করার অনুমতি রয়েছে? জাভা সম্পর্কে আমি নিশ্চিতভাবে জানি না, তবে জেটটি প্ল্যাটফর্মের জন্য সেরা অপ্টিমাইজেশানটি করতে জেআইটিকে অনুমতি দেওয়ার জন্য। নেট সংকলকরা সাধারণত এড়ানো উচিত।
IllidanS4

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

9
@ IllidanS4 কীভাবে রানটাইম পরিবেশ আরও ভাল অপ্টিমাইজেশন করতে সক্ষম হবে? খুব কমপক্ষে অবশ্যই কোডটি বিশ্লেষণ করতে হবে যা সংকলনের সময় কোড অপসারণের চেয়ে দ্রুততর হতে পারে না।
গেরহর্দ্ধ

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

6
@ IllidanS4 কোনও রানটাইম অপ্টিমাইজেশন শূন্য সময়ের চেয়ে কম সময় নিতে পারে না। সংকেতটি কোড অপসারণ করা থেকে বিরত রাখলে কোনও লাভ হবে না।
গেরার্ধ

55

দেখে মনে হচ্ছে এটি জেআইটি সংকলক দ্বারা অপ্টিমাইজড ছিল। আমি যখন এটি বন্ধ করি ( -Djava.compiler=NONE), কোডটি অনেক ধীর গতিতে চলে:

$ javac MyClass.java
$ java MyClass
Used 4
$ java -Djava.compiler=NONE MyClass
Used 40409

আমি ওপির কোডটি ভিতরে রেখেছি class MyClass


2
রহস্যময়। আমি যখন কোডটি দুটিভাবেই চালিত করি, তখন এটি পতাকা ছাড়াই দ্রুত হয় , তবে কেবল 10 এর একটি ফ্যাক্টর দ্বারা এবং লুপের পুনরাবৃত্তির সংখ্যায় জিরো যুক্ত বা সরিয়ে ফেলাও দশটির গুণক দ্বারা এবং ছাড়া ছাড়া চলমান সময়কে প্রভাবিত করে পতাকা। সুতরাং (আমার জন্য) লুপটি পুরোপুরি অপ্টিমাইজ হবে না বলে মনে হয়েছে, কেবলমাত্র 10 বার দ্রুত হয়েছে, কোনওভাবে। (ওরাকল জাভা 8-151)
tobias_k

@tobias_k এটা জে আই টি JIT লুপের মাধ্যমে যাচ্ছে কি পর্যায়ে আমি অনুমান উপর নির্ভর করে stackoverflow.com/a/47972226/1059372
ইউজিন

21

আমি কেবল স্পষ্ট করে বলব - যে এটি একটি জেভিএম অপ্টিমাইজেশন যা ঘটেছিল, লুপটি সরানো হবে একেবারে। এখানে একটি ছোট পরীক্ষা রয়েছে যা কেবলমাত্র যখনই সক্ষম / সক্ষম করে এবং একেবারেই অক্ষম করে তখন একটি বিশাল পার্থক্য কী তা দেখায় ।JITC1 Compiler

দাবি অস্বীকার: পরীক্ষার মতো লিখবেন না - এটি প্রমাণ করার জন্যই আসল লুপ "অপসারণ" ঘটে যায় C2 Compiler:

@Benchmark
@Fork(1)
public void full() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
        ++result;
    }
}

@Benchmark
@Fork(1)
public void minusOne() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

@Benchmark
@Fork(value = 1, jvmArgsAppend = { "-XX:TieredStopAtLevel=1" })
public void withoutC2() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

@Benchmark
@Fork(value = 1, jvmArgsAppend = { "-Xint" })
public void withoutAll() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

ফলাফলগুলি দেখায় যে এর কোন অংশটি JITসক্ষম হয়েছে তার উপর নির্ভর করে পদ্ধতিটি দ্রুততর হয় (এত দ্রুত যা দেখে মনে হয় যে এটি "কিছুই করছে না" - লুপ অপসারণ, যা সম্ভবত এটি ঘটছে C2 Compiler- যা সর্বোচ্চ স্তরের):

 Benchmark                Mode  Cnt      Score   Error  Units
 Loop.full        avgt    2      10⁻⁷          ms/op
 Loop.minusOne    avgt    2      10⁻⁶          ms/op
 Loop.withoutAll  avgt    2  51782.751          ms/op
 Loop.withoutC2   avgt    2   1699.137          ms/op 

13

ইতিমধ্যে ইঙ্গিত হিসাবে, জেআইটি (কেবলমাত্র সময়) সংকলক অপ্রয়োজনীয় পুনরাবৃত্তি অপসারণ করতে একটি খালি লুপ অনুকূল করতে পারেন। কিন্তু কিভাবে?

আসলে, দুটি জেআইটি সংকলক রয়েছে: সি 1 এবং সি 2 । প্রথমে কোডটি C1 দিয়ে সংকলিত হয়েছে। সি 1 পরিসংখ্যান সংগ্রহ করে এবং জেভিএমকে এটি আবিষ্কার করতে সহায়তা করে যে 100% ক্ষেত্রে আমাদের খালি লুপটি কোনও পরিবর্তন করে না এবং অকেজো হয়। এই পরিস্থিতিতে সি 2 মঞ্চে প্রবেশ করে। কোডটি যখন প্রায়শই কল করা হয়, তখন এটি সংগৃহীত পরিসংখ্যান ব্যবহার করে সি 2 দিয়ে অনুকূলিত এবং সংকলন করা যায়।

উদাহরণ হিসাবে, আমি পরবর্তী কোড স্নিপেট পরীক্ষা করব (আমার জেডি কে 9-অভ্যন্তরীণ বিল্ডিং স্লোডব্যাগে সেট করা আছে ):

public class Demo {
    private static void run() {
        for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
        }
        System.out.println("Done!");
    }
}

নিম্নলিখিত কমান্ড লাইনের বিকল্পগুলির সাথে:

-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Demo.run

এবং আমার রান পদ্ধতির বিভিন্ন সংস্করণ রয়েছে , সি 1 এবং সি 2 যথাযথভাবে সংকলিত। আমার জন্য, চূড়ান্ত রূপটি (সি 2) এর মতো দেখাচ্ছে:

...

; B1: # B3 B2 <- BLOCK HEAD IS JUNK  Freq: 1
0x00000000125461b0: mov   dword ptr [rsp+0ffffffffffff7000h], eax
0x00000000125461b7: push  rbp
0x00000000125461b8: sub   rsp, 40h
0x00000000125461bc: mov   ebp, dword ptr [rdx]
0x00000000125461be: mov   rcx, rdx
0x00000000125461c1: mov   r10, 57fbc220h
0x00000000125461cb: call  indirect r10    ; *iload_1

0x00000000125461ce: cmp   ebp, 7fffffffh  ; 7fffffff => 2147483647
0x00000000125461d4: jnl   125461dbh       ; jump if not less

; B2: # B3 <- B1  Freq: 0.999999
0x00000000125461d6: mov   ebp, 7fffffffh  ; *if_icmpge

; B3: # N44 <- B1 B2  Freq: 1       
0x00000000125461db: mov   edx, 0ffffff5dh
0x0000000012837d60: nop
0x0000000012837d61: nop
0x0000000012837d62: nop
0x0000000012837d63: call  0ae86fa0h

...

এটি কিছুটা অগোছালো তবে আপনি যদি ঘনিষ্ঠভাবে তাকান তবে আপনি খেয়াল করতে পারেন যে এখানে কোনও দীর্ঘ চলমান লুপ নেই। এখানে 3 টি ব্লক রয়েছে: বি 1, বি 2 এবং বি 3 এবং কার্যকরকরণের পদক্ষেপগুলি হতে পারে B1 -> B2 -> B3বা B1 -> B3। যেখানে Freq: 1- একটি ব্লক কার্যকর করার আনুমানিক ফ্রিকোয়েন্সি।


8

লুপটি সনাক্ত করতে যে সময় লাগে তা আপনি পরিমাপ করছেন যা কিছু করে না, ব্যাকগ্রাউন্ড থ্রেডে কোডটি সংকলন করে কোডটি মুছে ফেলছে।

for (int t = 0; t < 5; t++) {
    long start = System.nanoTime();
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
    }
    long time = System.nanoTime() - start;

    String s = String.format("%d: Took %.6f ms", t, time / 1e6);
    Thread.sleep(50);
    System.out.println(s);
    Thread.sleep(50);
}

আপনি এটি দিয়ে চালিয়ে -XX:+PrintCompilationগেলে দেখতে পান যে কোডটি ব্যাকগ্রাউন্ডে স্তর 3 বা সি 1 সংকলক এবং কয়েক লুপের পরে সি 4 এর স্তর 4 এ সংকলিত হয়েছে।

    129   34 %     3       A::main @ 15 (93 bytes)
    130   35       3       A::main (93 bytes)
    130   36 %     4       A::main @ 15 (93 bytes)
    131   34 %     3       A::main @ -2 (93 bytes)   made not entrant
    131   36 %     4       A::main @ -2 (93 bytes)   made not entrant
0: Took 2.510408 ms
    268   75 %     3       A::main @ 15 (93 bytes)
    271   76 %     4       A::main @ 15 (93 bytes)
    274   75 %     3       A::main @ -2 (93 bytes)   made not entrant
1: Took 5.629456 ms
2: Took 0.000000 ms
3: Took 0.000364 ms
4: Took 0.000365 ms

আপনি যদি লুপটি ব্যবহার করতে পরিবর্তন করেন তবে longএটি অনুকূলিত হয় না।

    for (long i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
    }

পরিবর্তে আপনি পেতে

0: Took 1579.267321 ms
1: Took 1674.148662 ms
2: Took 1885.692166 ms
3: Took 1709.870567 ms
4: Took 1754.005112 ms

এটি আশ্চর্যজনক ... কেন কোনও longকাউন্টার একই অপটিমাইজেশনটি ঘটতে বাধা দেবে?
রায়ান আমোস

@ রায়ানআমোস অপ্টিমাইজেশন কেবলমাত্র সাধারণ আদিম লুপ গণিতে প্রয়োগ করা হয় যদি টাইপ intনোট চর এবং সংক্ষিপ্তভাবে বাইট কোড স্তরে কার্যকর হয় effectively
পিটার লরি

-1

আপনি ন্যানোসেকেন্ডে শুরু এবং শেষের সময়টিকে বিবেচনা করুন এবং বিলম্বিতার জন্য গণনা করার জন্য আপনি 10 ^ 6 দ্বারা ভাগ করবেন

long d = (finish - start) / 1000000

এটি হওয়া উচিত 10^9কারণ 1দ্বিতীয় = 10^9ন্যানোসেকেন্ড।


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