অন্য বিবৃতিতে জিসিসির __ বিল্টিন_ এক্সপেক্টের কী সুবিধা?


144

আমি এমন একটি জায়গা পেলাম #defineযেখানে তারা ব্যবহার করে __builtin_expect

নথি বলছে:

অন্তর্নির্মিত কার্য: long __builtin_expect (long exp, long c)

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

রিটার্ন মান হল এর মান exp, যা একটি অবিচ্ছেদ্য অভিব্যক্তি হওয়া উচিত। অন্তর্নির্মিত শব্দার্থক এটি যে এটি প্রত্যাশিত হয় exp == c। উদাহরণ স্বরূপ:

      if (__builtin_expect (x, 0))
        foo ();

ইঙ্গিত দেয় যে আমরা কল করার আশা করি না foo, যেহেতু আমরা xশূন্য হওয়ার আশা করি ।

সুতরাং কেন সরাসরি ব্যবহার করবেন না:

if (x)
    foo ();

জটিল সিনট্যাক্সের বদলে __builtin_expect?



3
আমি মনে করি আপনার সরাসরি কোডটি হওয়া উচিত ছিল if ( x == 0) {} else foo();.. বা কেবল if ( x != 0 ) foo();যা জিসিসি ডকুমেন্টেশন থেকে কোডের সমান।
নওয়াজ

উত্তর:


187

সমাবেশের কোডটি কল্পনা করুন যা থেকে উত্পন্ন হবে:

if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}

আমার ধারণা এটির মতো কিছু হওয়া উচিত:

  cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:

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

লাফ কার্যকর করার আগে, এর নীচের নির্দেশগুলি ( barকেস) পাইপলাইনে ঠেলাঠেলি করা হয়। যেহেতু fooকেসটি অসম্ভাব্য, তাই লাফানোও খুব সম্ভবত অসম্ভব, তাই পাইপলাইন ছিটানো অসম্ভব।


1
এটি কি সত্যিই এর মতো কাজ করে? কেন foo সংজ্ঞা প্রথম আসতে পারে না? ফাংশন সংজ্ঞাগুলির ক্রমটি অপ্রাসঙ্গিক, যতক্ষণ না আপনার কাছে প্রোটোটাইপ আছে, তাই না?
কিংমশার 1

63
এটি ফাংশন সংজ্ঞা সম্পর্কে নয়। এটি মেশিন কোডটি এমনভাবে পুনর্বিন্যাসের বিষয়ে যা সিপিইউর জন্য নির্দেশনাগুলি কার্যকর করা হচ্ছে না এমন একটি ছোট সম্ভাবনার কারণ।
ব্লাগোভেষ্ট বায়ুকলিভ

4
ওহ আমি বুঝতে পারি। সুতরাং আপনার মানে যেহেতু উচ্চ সম্ভাবনা রয়েছে x = 0তাই বারটি প্রথমে দেওয়া হয়েছে is এবং ফু, পরে সংজ্ঞায়িত হয়েছে কারণ এটি সম্ভাবনা (বরং সম্ভাবনা ব্যবহার করুন) কম, তাই না?
কিংমশার 1

1
Ahhh..thanks। এটিই সেরা ব্যাখ্যা। অ্যাসেম্বলি কোডটি সত্যই কৌতুক করেছে :)
কিংমশার 1

5
এটি সিপিইউ শাখার ভবিষ্যদ্বাণীকে পাইপলাইনের উন্নতি করতে ইঙ্গিতগুলি এম্বেড করতে পারে
হস্তুরকুন

50

আসুন জিসিসি 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)
        puts("a");
    return 0;
}

জিসিসি 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 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  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

মেমরিতে নির্দেশের আদেশটি অপরিবর্তিত ছিল: প্রথমে 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 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>

putsফাংশন, খুব শেষ সরিয়ে নেওয়া হয়েছে retqপ্রত্যাবর্তন!

নতুন কোডটি মূলত:

int i = !time(NULL);
if (i)
    goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;

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

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

সি ++ 20 [[likely]]এবং[[unlikely]]

সি ++ ২০ এই সি ++ বিল্ট-ইনগুলি মানক করেছে: সি ++ 20 এর সম্ভাব্য / অসম্ভব বৈশিষ্ট্যটি যদি অন্য-বিবৃতিতে ব্যবহার করতে হয় তবে তারা সম্ভবত (পাং!) একই কাজ করবে।


1
Libdispatch এর dispatch_once ফাংশনটি দেখুন, যা ব্যবহারিক অপ্টিমাইজেশনের জন্য __builtin_expect ব্যবহার করে। ধীর পথটি সর্বকালের জন্য চালায় এবং শাখা পূর্বাভাসকারীকে দ্রুত পথ অবলম্বন করা উচিত বলে ইঙ্গিত দিতে __ বিল্টিন_ এক্সপেক্টটি শোষণ করে। কোনও লক ব্যবহার না করে দ্রুত পথ চলে! mikeash.com/pyblog/…
অ্যাডাম কাপলান

জিসিসি 9.2: gcc.godbolt.org/z/GzP6cx (আসলে ইতিমধ্যে 8.1 এ) কোনও পার্থক্য দেখা যাচ্ছে না
রুসলান

40

ধারণাটি __builtin_expectহ'ল সংকলককে বলুন যে আপনি সাধারণত দেখতে পাবেন যে অভিব্যক্তিটি সিতে মূল্যায়ন করে, যাতে সংকলক সেই ক্ষেত্রে অনুকূলিত করতে পারে।

আমি অনুমান করতে পারি যে কেউ ভেবেছিল তারা চালাক এবং তারা কাজটি করে দিয়েছিল up

দুর্ভাগ্যক্রমে, পরিস্থিতিটি যদি খুব ভালভাবে না বোঝা যায় (তবে সম্ভবত তারা এ জাতীয় কোনও কাজ করেন নি), এটি সম্ভবত পরিস্থিতি আরও খারাপ করে দিয়েছে। ডকুমেন্টেশন এমনকি বলে:

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

সাধারণভাবে, আপনার ব্যবহার করা উচিত নয় __builtin_expectযতক্ষণ না:

  • আপনার খুব বাস্তব পারফরম্যান্স সমস্যা আছে
  • আপনি ইতিমধ্যে সিস্টেমের অ্যালগরিদমগুলি যথাযথভাবে অপ্টিমাইজ করেছেন
  • আপনার দৃser় প্রতিবেদনের ব্যাক আপ করতে পারফরম্যান্স ডেটা পেয়েছেন যে কোনও নির্দিষ্ট ক্ষেত্রে সম্ভবত সবচেয়ে বেশি সম্ভাবনা থাকে

7
@ মিশেল: এটি আসলে শাখার পূর্বাভাসের বিবরণ নয়।
অলিভার চার্লসওয়ার্থ

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

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

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

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

13

ঠিক আছে, যেমন এটি বিবরণে বলা হয়েছে, প্রথম সংস্করণটি নির্মাণে একটি ভবিষ্যদ্বাণীপূর্ণ উপাদান যুক্ত করে, সংকলককে জানিয়েছে যে x == 0শাখাটি তত বেশি সম্ভবত - এটি হ'ল এটি শাখা যা আপনার প্রোগ্রাম দ্বারা প্রায়শই নেওয়া হবে।

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

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

তবে, আমি কেবলমাত্র এই অপ্টিমাইজেশনটি লক্ষণীয় প্রভাবের জন্য প্রত্যাশা করব যদি প্রশ্নে শর্তযুক্ত শর্তযুক্ত একটি আঁটসাঁটো অভ্যন্তরীণ লুপের অংশ হয় যা প্রচুর পরিমাণে বলা হয় , যেহেতু ফলাফলের কোডের পার্থক্য তুলনামূলকভাবে কম is এবং আপনি যদি এটিকে ভুল উপায়ে অপ্টিমাইজ করেন তবে আপনি আপনার কার্য সম্পাদন ভাল করে ফেলতে পারেন।


তবে শেষে এটি সমস্তই সংকলক দ্বারা শর্তটি পরীক্ষা করার বিষয়ে, আপনি কি বলতে চান যে কম্পাইলারটি সর্বদা এই শাখাটি ধরে নেয় এবং এগিয়ে যায়, এবং পরে যদি কোনও মিল না থাকে তবে? কি ঘটেছে? আমি মনে করি সংকলক ডিজাইনে এই শাখার পূর্বাভাস স্টাফ এবং আরও কীভাবে কাজ করে সে সম্পর্কে আরও কিছু রয়েছে।
কিংমশার 1

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

ধন্যবাদ, আপনার এবং মাইকেল আমার মতামত একই রকম হয়েছে তবে বিভিন্ন কথায় লিখেছেন :-) আমি বুঝতে পারি টেস্ট-শাখা সম্পর্কে সঠিক সংকলক ইন্টার্নালগুলি এখানে ব্যাখ্যা করা সম্ভব নয় :)
কিংমাসের 1

তারা ইন্টারনেট অনুসন্ধান করেও শিখতে খুব সহজ :-)
কেরেরেক এসবি

আমি আরও ভালভাবে আমার কলেজ বইতে ফিরে যেতে পারি compiler design - Aho, Ullmann, Sethi:-)
কিংমশার 1

1

আপনি যে প্রশ্নটি জিজ্ঞাসা করেছেন বলে মনে হয়েছে এমন প্রশ্নের উত্তরের কোনও উত্তর আমি দেখতে পাচ্ছি না, অনুচ্ছেদে:

সংকলকটিতে শাখার পূর্বাভাসকে ইঙ্গিত করার আরও পোর্টেবল উপায় আছে কি?

আপনার প্রশ্নের শিরোনাম আমাকে এইভাবে এটি করার কথা ভাবায়:

if ( !x ) {} else foo();

সংকলক যদি ধরে নেয় যে 'সত্য' সম্ভবত বেশি, তবে এটি কল না করার জন্য অনুকূলিত হতে পারে foo()

এখানে সমস্যাটি কেবলমাত্র এটিই নয় যে আপনি সাধারণভাবেই জানেন না যে সংকলকটি কী ধারনা করবে - সুতরাং যে কোনও কোড যা এই ধরণের প্রযুক্তি ব্যবহার করে সেগুলির যত্ন সহকারে পরিমাপ করা প্রয়োজন (এবং প্রসঙ্গ পরিবর্তিত হলে সম্ভবত সময়ের সাথে পর্যবেক্ষণ করা)।


এটি প্রকৃতপক্ষে, ওপি মূলত টাইপ করতে চেয়েছিল (শিরোনাম দ্বারা নির্দেশিত) ঠিক একইভাবে হতে পারে - তবে কোনও কারণে elseপোস্টটির শৃঙ্খলা থেকে ব্যবহার বাদ দেওয়া হয়েছিল।
ব্রেন্ট ব্র্যাডবার্ন

1

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

কমান্ডগুলি হয় gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o

আমি যখন -O3 use ব্যবহার করি তখন __builtin_expect (i, 0) এর অস্তিত্ব থাকুক বা না থাকুক না কেন এটি একই দেখায়।

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp     
0000000000000001    movq    %rsp, %rbp    // open function stack
0000000000000004    xorl    %edi, %edi       // set time args 0 (NULL)
0000000000000006    callq   _time      // call time(NULL)
000000000000000b    testq   %rax, %rax   // check time(NULL)  result
000000000000000e    je  0x14           //  jump 0x14 if testq result = 0, namely jump to puts
0000000000000010    xorl    %eax, %eax   //  return 0   ,  return appear first 
0000000000000012    popq    %rbp    //  return 0
0000000000000013    retq                     //  return 0
0000000000000014    leaq    0x9(%rip), %rdi  ## literal pool for: "a"  // puts  part, afterwards
000000000000001b    callq   _puts
0000000000000020    xorl    %eax, %eax
0000000000000022    popq    %rbp
0000000000000023    retq

যখন -O2 with দিয়ে সংকলন করা হয় তখন এটি __builtin_expect (i, 0) এর সাথে এবং ছাড়া আলাদা দেখায়

প্রথম ছাড়া

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    jne 0x1c       //   jump to 0x1c if not zero, then return
0000000000000010    leaq    0x9(%rip), %rdi ## literal pool for: "a"   //   put part appear first ,  following   jne 0x1c
0000000000000017    callq   _puts
000000000000001c    xorl    %eax, %eax     // return part appear  afterwards
000000000000001e    popq    %rbp
000000000000001f    retq

এখন __ বিল্টিন_স্পেকেক্টের সাথে (i, 0)

testOpt.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    xorl    %edi, %edi
0000000000000006    callq   _time
000000000000000b    testq   %rax, %rax
000000000000000e    je  0x14   // jump to 0x14 if zero  then put. otherwise return 
0000000000000010    xorl    %eax, %eax   // return appear first 
0000000000000012    popq    %rbp
0000000000000013    retq
0000000000000014    leaq    0x7(%rip), %rdi ## literal pool for: "a"
000000000000001b    callq   _puts
0000000000000020    jmp 0x10

সংক্ষিপ্তসার হিসাবে, __builtin_expect শেষ ক্ষেত্রে কাজ করে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.