কেন clang অ-অক্সি সঙ্গে-00(এই সহজ ভাসমান বিন্দু যোগফল) জন্য উত্পাদন করে?



assembly llvm (1)

আমি এই কোডটি এলএলএমএম ক্ল্যাং অ্যাপল এলএলভিএম সংস্করণ 8.0.0 (clang-800.0.42.1) এ ডেস্যাসেমলিং করছি:

int main() {
    float a=0.151234;
    float b=0.2;
    float c=a+b;
    printf("%f", c);
}

আমি কোনও নির্দিষ্টকরণের সাথে সংকলন করেছি, কিন্তু আমি -O0 (একইটি) এবং -O2- এর সাথেও চেষ্টা করেছি (প্রকৃতপক্ষে মান এবং এটি পূর্বনির্ধারিত সঞ্চয়গুলি গণনা করে)

ফলে disassembly নিম্নলিখিত (আমি প্রাসঙ্গিক অংশ না মুছে ফেলা)

->  0x100000f30 <+0>:  pushq  %rbp
    0x100000f31 <+1>:  movq   %rsp, %rbp
    0x100000f34 <+4>:  subq   $0x10, %rsp
    0x100000f38 <+8>:  leaq   0x6d(%rip), %rdi       
    0x100000f3f <+15>: movss  0x5d(%rip), %xmm0           
    0x100000f47 <+23>: movss  0x59(%rip), %xmm1        
    0x100000f4f <+31>: movss  %xmm1, -0x4(%rbp)  
    0x100000f54 <+36>: movss  %xmm0, -0x8(%rbp)
    0x100000f59 <+41>: movss  -0x4(%rbp), %xmm0         
    0x100000f5e <+46>: addss  -0x8(%rbp), %xmm0
    0x100000f63 <+51>: movss  %xmm0, -0xc(%rbp)
    ...

দৃশ্যত এটি নিম্নলিখিত কাজ করছে:

  1. নিবন্ধনকারী xmm0 এবং xmm1 এ দুইটি floats লোড হচ্ছে
  2. স্ট্যাক তাদের রাখা
  3. স্ট্যাক থেকে xmm0 এ এক মান লোড করুন (এক xmm0 আগে ছিল না)
  4. উপরন্তু সঞ্চালন।
  5. স্ট্যাক ফিরে ফলাফল সংরক্ষণ করুন।

আমি এটি অকার্যকর কারণ:

  1. সবকিছু রেজিস্ট্রি সম্পন্ন করা যাবে। আমি পরে একটি এবং খ ব্যবহার করছি না, তাই এটি স্ট্যাক জড়িত কোন অপারেশন এড়িয়ে যেতে পারে।
  2. স্ট্যাক ব্যবহার করতে চাইলেও, যদি এটি একটি ভিন্ন ক্রমের সাথে অপারেশন করে তবে এটি স্ট্যাক থেকে xmm0 পুনরায় লোড করতে পারে।

দেওয়া হয়েছে যে কম্পাইলার সবসময় সঠিক, কেন এটা এই কৌশল পছন্দ করেন?


-O0 ( -O0 ) ডিফল্ট । এটি কম্পাইলারটিকে আপনি দ্রুত কম্পাইলার (ছোট কম্পাইল সময়) কম্পাইল করতে বলে, কার্যকর কোড তৈরির জন্য অতিরিক্ত সময় সংকলন করতে না।

( -O0 আক্ষরিক অর্থে কোনও অপ্টিমাইজেশান নয়, উদাহরণস্বরূপ -O0 if(1 == 2){ } ব্লকগুলির ভিতরে কোডটি মুছে ফেলবে। বিশেষ করে অন্যান্য অন্যান্য কম্পাইলারগুলির চেয়ে Gcc বেশি এখনও -O0-O0 জন্য -O0 ব্যবহার করে, কারণ এটি অবশেষে ASM নির্গত করার আগে যুক্তিটির একাধিক অভ্যন্তরীণ উপস্থাপনাগুলির মাধ্যমে আপনার সি উৎসকে রূপান্তর করে।)

প্লাস, "কম্পাইলার সর্বদা সঠিক" -O3 এ এমনকি একটি -O3 । কম্পাইলারগুলি একটি বৃহত স্কেলে খুব ভাল, তবে ক্ষুদ্র মিস-অপ্টিমাইজেশানগুলি এখনও একক লুপের মধ্যেই সাধারণ। প্রায়শই খুব কম প্রভাবের সাথে, কিন্তু একটি লুপে নষ্ট নির্দেশগুলি (বা uops) আউট-অফ-অর্ডার এক্সিকিউশন পুনর্বহাল উইন্ডোতে স্থান খায় এবং অন্য থ্রেড দিয়ে কোর ভাগ করে কম হাইপার-থ্রেডিং বন্ধুত্বপূর্ণ হতে পারে। হাতের লেখা লিখিত সমাবেশের তুলনায় কল্যাজ অনুমান দ্রুত পরীক্ষা করার জন্য সি ++ কোড দেখুন - কেন? একটি সহজ নির্দিষ্ট ক্ষেত্রে কম্পাইলার আঘাত সম্পর্কে আরও জন্য।

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

(এটি volatile হিসাবে খুব খারাপ নয়: এক বিবৃতির মধ্যে একই পরিবর্তনশীলের একাধিক রেফারেন্স সর্বদা একাধিক লোড হতে পারে না; at -O0 কম্পাইলারগুলি এখনও একক অভিব্যক্তিটির মধ্যে কিছুটা অপ্টিমাইজ করবে।)

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

দুর্ভাগ্যবশত, ডিবাগ-তথ্য ফরম্যাটগুলি নিবন্ধকদের মাধ্যমে একটি পরিবর্তনশীল অবস্থানটি ট্র্যাক করতে পারে না, তাই এই ধীর-এবং-মূঢ় কোড-জেন ছাড়া সম্পূর্ণরূপে সামঞ্জস্যপূর্ণ ডিবাগিং সম্ভব নয়।

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

এমনকি খারাপ, -O0 এমন কোড তৈরি করে যা এখনও GDB এর jump কমান্ডটি ব্যবহার করে যদি আপনি কোনও ভিন্ন সোর্স লাইনে নির্বাহ চালিয়ে যান । তাই প্রতিটি সি বিবৃতি নির্দেশাবলীর একটি সম্পূর্ণ স্বাধীন ব্লক মধ্যে কম্পাইল করা হবে। ( জিডিবি ডিবাগারে "লাফ" / "এড়িয়ে যাওয়া" সম্ভব? )

for() loops idiomatic (asm জন্য) রূপান্তর করা যাবে do{}while() loops , এবং অন্যান্য বিধিনিষেধ।

উপরের সকল কারণের জন্য, (মাইক্রো-) বেঞ্চমার্কিং অ-অপ্টিমাইজড কোডটি সময়ের বিশাল অপচয়; আপনি স্বাভাবিক অপ্টিমাইজেশান সংকলন করার সময় কোন ব্যাপার না যে উৎসটি লিখেছেন তার ফলাফলগুলি নির্বোধের উপর নির্ভর করে। -O0 বনাম। -O3 কর্মক্ষমতা রৈখিকভাবে সম্পর্কিত নয়; কিছু কোড অন্যদের তুলনায় অনেক বেশি গতিতে হবে

-O0 কোডের -O3 প্রায়ই -O3 থেকে -O3 - প্রায়ই একটি লুপ পাল্টা যা মেমরিতে রাখা হয়, একটি ~ 6-চক্র লুপ-বাহিত নির্ভরতা চেইন তৈরি করে। এটি কম্পাইলার-জেনারেট এসিএমের মত আকর্ষণীয় প্রভাব তৈরি করতে পারে যেমন অপ্টিমাইজেশান ছাড়াই সংকলিত হওয়া অবস্থায় একটি অকার্যকর অ্যাসাইনমেন্ট গতি বাড়ানো (যা একটি ASM দৃষ্টিকোণ থেকে আকর্ষণীয়, কিন্তু সি নয়)।

"আমার বেঞ্চমার্ক অন্যথায় অপ্টিমাইজ করা" -O0 কোডের কর্মক্ষমতা দেখার জন্য একটি বৈধ -O0 । উদাহরণস্বরূপ চূড়ান্ত নিয়োগের জন্য সি লুপ অপ্টিমাইজেশান সহায়তা দেখুন এবং খরগোশের গর্ত সম্পর্কে আরো বিশদ যা -O0 জন্য -O0 হয়।

আকর্ষণীয় কম্পাইলার আউটপুট পেয়ে

যদি আপনি দেখতে চান কম্পাইলার 2 ভেরিয়েবল যোগ করে, একটি ফাংশন লিখুন যা args নেয় এবং একটি মান প্রদান করে । মনে রাখবেন আপনি শুধুমাত্র এএসএমটি দেখতে চান না, এটি চালান না, তাই আপনাকে রানটাইম পরিবর্তনশীল হওয়া উচিত এমন কোনও জিনিসের জন্য একটি main বা সাংখ্যিক আক্ষরিক মানগুলির প্রয়োজন নেই।

GCC / clang সমাবেশ আউটপুট থেকে "শব্দ" সরাতে কিভাবে দেখুন ? এই সম্পর্কে আরো জন্য।

float foo(float a, float b) {
    float c=a+b;
    return c;
}

প্রত্যাশিত clang clang -O3 ( গডবোল্ট কম্পাইলার এক্সপ্লোরার ) সঙ্গে কম্পাইল

    addss   xmm0, xmm1
    ret

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

gcc -fverbose-asm নামগুলি দেখানো প্রতিটি লাইনের উপর মন্তব্য করবে। অপ্টিমাইজড কোড যা প্রায়শই একটি অভ্যন্তরীণ tmp নাম, তবে অ-অপ্টিমাইজড কোডে এটি স্বাভাবিক একটি সাধারণ উৎসের থেকে C উত্স থেকে। আমি ম্যানুয়ালি ক্লাউং আউটপুট মন্তব্য করেছি কারণ এটি যে কাজ করে না।

# clang7.0 -O0  also on Godbolt
foo:
    push    rbp
    mov     rbp, rsp                  # make a traditional stack frame
    movss   DWORD PTR [rbp-20], xmm0  # spill the register args
    movss   DWORD PTR [rbp-24], xmm1  # into the red zone (below RSP)

    movss   xmm0, DWORD PTR [rbp-20]  # a
    addss   xmm0, DWORD PTR [rbp-24]  # +b
    movss   DWORD PTR [rbp-4], xmm0   # store c

    movss   xmm0, DWORD PTR [rbp-4]   # return 0
    pop     rbp                       # epilogue
    ret

মজা ঘটনা: register float c = a+b; , রিটার্ন মান XMM0 এ বিবৃতির মধ্যে স্থানান্তরিত / পুনরায় লোড হওয়ার পরিবর্তে থাকতে পারে। পরিবর্তনশীল কোন ঠিকানা আছে। (আমি গডবোল্ট লিঙ্কটিতে ফাংশনের যে সংস্করণটি অন্তর্ভুক্ত করেছি।)

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

সম্পর্কিত:





compiler-optimization