linux যবহ লিনাক্স কার্নেলের সম্ভাব্য/অসম্ভাব্য ম্যাক্রো কীভাবে কাজ করে এবং তাদের সুবিধা কী?




লিনাক্স ব্যবহার (8)

তারা ঝাঁপ নির্দেশের "সম্ভাব্য" পাশের পক্ষে শাখা পূর্বাভাসের কারণগুলি নির্দেশ করে এমন নির্দেশগুলি নির্বাহ করতে কম্পাইলারের ইঙ্গিত দেয়। এটি একটি বড় জয় হতে পারে, যদি ভবিষ্যদ্বাণীটি সঠিক হয় তবে এর অর্থ হল লাফ নির্দেশনা মূলত মুক্ত এবং শূন্য চক্রগুলি গ্রহণ করবে। অন্যদিকে যদি ভবিষ্যদ্বাণীটি ভুল হয় তবে এর অর্থ হল প্রসেসর পাইপলাইনটি ফ্লাশ করা দরকার এবং এটি কয়েকটি চক্র খরচ করতে পারে। যতক্ষণ ভবিষ্যদ্বাণীটি বেশিরভাগ সময় সঠিক হবে, এটি কার্য সম্পাদনের জন্য ভাল হবে।

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

আমি লিনাক্স কার্নেলের কিছু অংশ দিয়ে খনন করেছি, এবং এরকম কল পেয়েছি:

if (unlikely(fd < 0))
{
    /* Do something */
}

অথবা

if (likely(!err))
{
    /* Do something */
}

আমি তাদের সংজ্ঞা খুঁজে পেয়েছি:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

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


long __builtin_expect(long EXP, long C);

এই কনস্ট্রাক্ট কম্পাইলারকে বলে যে এক্সপি এক্সপ্রেশনটিতে সম্ভবত সিটি থাকবে। ফেরত মান EXP হয়। __builtin_expect একটি শর্তাধীন অভিব্যক্তি ব্যবহার করা বোঝানো হয়। প্রায় সব ক্ষেত্রে এটি বুলিয়ান এক্সপ্রেশন প্রসঙ্গে ব্যবহার করা হবে, এটি ক্ষেত্রে দুটি সহায়ক ম্যাক্রো সংজ্ঞায়িত করা আরও বেশি সুবিধাজনক:

#define unlikely(expr) __builtin_expect(!!(expr), 0)
#define likely(expr) __builtin_expect(!!(expr), 1)

এই ম্যাক্রো তারপর হিসাবে ব্যবহার করা যেতে পারে

if (likely(a > 1))

রেফারেন্স: https://www.akkadia.org/drepper/cpumemory.pdf


আসুন জি সি সি 4.8 এর সাথে কি কি দেখতে হবে সেটি মুছে ফেলুন

__builtin_expect ছাড়া

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        printf("%d\n", i);
    puts("a");
    return 0;
}

GCC 4.8.2 x86_64 লিনাক্সের সাথে কম্পাইল এবং ডিকম্পাইল করুন:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

আউটপুট:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 14                   jne    24 <main+0x24>
  10:       ba 01 00 00 00          mov    $0x1,%edx
  15:       be 00 00 00 00          mov    $0x0,%esi
                    16: R_X86_64_32 .rodata.str1.1
  1a:       bf 01 00 00 00          mov    $0x1,%edi
  1f:       e8 00 00 00 00          callq  24 <main+0x24>
                    20: R_X86_64_PC32       __printf_chk-0x4
  24:       bf 00 00 00 00          mov    $0x0,%edi
                    25: R_X86_64_32 .rodata.str1.1+0x4
  29:       e8 00 00 00 00          callq  2e <main+0x2e>
                    2a: R_X86_64_PC32       puts-0x4
  2e:       31 c0                   xor    %eax,%eax
  30:       48 83 c4 08             add    $0x8,%rsp
  34:       c3                      retq

স্মৃতিতে নির্দেশের নির্দেশ অপরিবর্তিত ছিল: প্রথম printf এবং তারপর puts এবং retq রিটার্ন।

__builtin_expect সঙ্গে

এখন if (i) সাথে প্রতিস্থাপন করুন:

if (__builtin_expect(i, 0))

এবং আমরা পেতে পারি:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 11                   je     21 <main+0x21>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1+0x4
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq
  21:       ba 01 00 00 00          mov    $0x1,%edx
  26:       be 00 00 00 00          mov    $0x0,%esi
                    27: R_X86_64_32 .rodata.str1.1
  2b:       bf 01 00 00 00          mov    $0x1,%edi
  30:       e8 00 00 00 00          callq  35 <main+0x35>
                    31: R_X86_64_PC32       __printf_chk-0x4
  35:       eb d9                   jmp    10 <main+0x10>

printf ( __printf_chk সংকলিত) __printf_chk স্থানান্তরিত হওয়ার পরে এবং অন্যান্য উত্তরগুলি দ্বারা উল্লিখিত শাখার ভবিষ্যদ্বাণী উন্নত করার জন্য প্রত্যাবর্তন করা হয়েছিল।

সুতরাং এটি মূলত একই রকম:

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

এই অপ্টিমাইজেশান -O0 সঙ্গে সম্পন্ন করা হয় -O0

কিন্তু একটি উদাহরণ লেখার জন্য সৌভাগ্য যে __builtin_expect সাথে দ্রুত চালানো ছাড়া, CPU গুলি সত্যিই সেই দিনগুলি স্মার্ট । আমার চতুর প্রচেষ্টা এখানে


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

উদাহরণস্বরূপ, একটি পাওয়ারপিসি সিপিওর উপর একটি অচিন্তিত শাখাটি 16 চক্র নিতে পারে, সঠিকভাবে ইঙ্গিত করা 8 এবং ভুলভাবে ইঙ্গিত করা হয়েছে 24. অন্তরঙ্গ লুপগুলির মধ্যে ভাল নির্দেশনা একটি অসাধারণ পার্থক্য সৃষ্টি করতে পারে।

পোর্টেবিলিটি সত্যিই একটি সমস্যা নয় - সম্ভবত একটি প্রতি-প্ল্যাটফর্ম হেডারের মধ্যে সংজ্ঞা হয়; স্ট্যাটিক শাখা ইঙ্গিতগুলিকে সমর্থন করে না এমন প্ল্যাটফর্মের জন্য আপনি কেবল "সম্ভাব্য" এবং "অসম্ভব" সংজ্ঞায়িত করতে পারেন।


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

লিনাক্সে এই বিশেষ বৈশিষ্ট্যটি কিছুটা দুর্ঘটনাক্রমে ড্রাইভারগুলিতে ব্যবহৃত হয়। osgx হট অ্যাট্রিবিউটের osgx নির্দেশ করে, একটি ব্লকের সাথে যেকোনো hot বা cold ফাংশন বলা হয় যা স্বয়ংক্রিয়ভাবে শর্তটি হ'ল তা ইঙ্গিত দেয়। উদাহরণস্বরূপ, dump_stack() cold হিসাবে চিহ্নিত করা হয়, তাই এটি dump_stack() ,

 if(unlikely(err)) {
     printk("Driver error found. %d\n", err);
     dump_stack();
 }

gcc ভবিষ্যত সংস্করণগুলি এই ইঙ্গিতগুলির উপর ভিত্তি করে একটি ফাংশনটি নির্বাচন করতে পারে। এমন পরামর্শ রয়েছে যে এটি boolean নয়, তবে সম্ভবত একটি স্কোর, ইত্যাদি। সাধারণত, cold মতো কিছু বিকল্প পদ্ধতি ব্যবহার করা উচিত। এটি কোন স্থানে কিন্তু গরম পথ ব্যবহার করার কোন কারণ নেই। একটি কম্পাইলার কি এক স্থাপত্য করতে হবে অন্য সম্পূর্ণ ভিন্ন হতে পারে।


এই ম্যাক্রোগুলি যা কোন শাখা যেতে পারে সে সম্পর্কে কম্পাইলারকে ইঙ্গিত দেয়। ম্যাক্রোগুলি যদি উপলব্ধ থাকে তবে জিसीसी নির্দিষ্ট এক্সটেনশনগুলিতে প্রসারিত হয়।

GCC শাখা পূর্বাভাস জন্য অপ্টিমাইজ করার জন্য এই ব্যবহার করে। উদাহরণস্বরূপ, যদি আপনি নিম্নলিখিত মত কিছু আছে

if (unlikely(x)) {
  dosomething();
}

return x;

তারপর এটি আরও কিছু করার জন্য এই কোডটিকে পুনঃস্থাপন করতে পারে:

if (!x) {
  return x;
}

dosomething();
return x;

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

বেশিরভাগ আধুনিক প্রসেসরগুলির এখন শাখার পূর্বাভাসের কিছু প্রকার রয়েছে, তবে এটি শুধুমাত্র তখনই সহায়তা করে যখন আপনি আগে শাখাটি ব্যবহার করেছেন এবং শাখাটি এখনও শাখা পূর্বাভাস ক্যাশে রয়েছে।

কম্পাইলার এবং প্রসেসর এই পরিস্থিতিতে ব্যবহার করতে পারেন যে অন্যান্য কৌশল আছে। আপনি http://en.wikipedia.org/wiki/Branch_preredictor- এ উইকিপিডিয়ায় কীভাবে শাখা ভবিষ্যদ্বাণীগুলি কাজ করে সে সম্পর্কে আরও বিস্তারিত জানতে পারেন।


অনেক লিনাক্স রিলিজে, আপনি complier.h / usr / linux / এ খুঁজে পেতে পারেন, আপনি কেবল এটি ব্যবহারের জন্য অন্তর্ভুক্ত করতে পারেন। এবং অন্য মতামত, অসম্ভাব্য () সম্ভবত অধিকতর দরকারী (), কারণ

if ( likely( ... ) ) {
     doSomething();
}

এটি অনেক কম্পাইলার পাশাপাশি অপ্টিমাইজ করা যেতে পারে।

এবং উপায় অনুসারে, যদি আপনি কোডের বিস্তারিত আচরণ পালন করতে চান তবে আপনি কেবল অনুসরণ করতে পারেন:

gcc -c test.c objdump -d test.o> obj.s

তারপর, obj.s খুলুন, আপনি উত্তর খুঁজে পেতে পারেন।


(সাধারণ মন্তব্য - অন্যান্য উত্তর বিস্তারিত কভার করে)

আপনি তাদের ব্যবহার করে পোর্টেবিলিটি হারাতে হবে যে কোন কারণ নেই।

আপনার কাছে সর্বদা একটি সহজ নীল-প্রভাব "ইনলাইন" বা ম্যাক্রো তৈরি করার বিকল্প রয়েছে যা আপনাকে অন্যান্য কম্পাইলারগুলির সাথে অন্যান্য প্ল্যাটফর্মগুলিতে কম্পাইল করার অনুমতি দেবে।

আপনি যদি অন্যান্য প্ল্যাটফর্মগুলিতে থাকেন তবে আপনি অপটিমাইজেশনের সুবিধা পাবেন না।





likely-unlikely