লজিক্যাল এন্ড অপারেটর ( &&
) শর্ট সার্কিট মূল্যায়ন ব্যবহার করে যার অর্থ প্রথম পরীক্ষাটি যদি সত্যের সাথে মূল্যায়ন করে তবেই দ্বিতীয় পরীক্ষা করা হয়। এটি প্রায়শই আপনার প্রয়োজনীয় শব্দার্থক হয়। উদাহরণস্বরূপ, নিম্নলিখিত কোডটি বিবেচনা করুন:
if ((p != nullptr) && (p->first > 0))
আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনি পয়েন্টারটি অবলম্বন করার আগে অকার্যকর। যদি এটি ছিল না অল্প-সার্কিট মূল্যায়ন, আপনি অনির্ধারিত আচরণ আছে কারণ আপনি একটি নাল পয়েন্টার dereferencing করা চাই চাই।
এটিও সম্ভব যে শর্তগুলির মূল্যায়ন ব্যয়বহুল প্রক্রিয়া ক্ষেত্রে শর্ট সার্কিট মূল্যায়ন কার্যকারিতা লাভ করে। উদাহরণ স্বরূপ:
if ((DoLengthyCheck1(p) && (DoLengthyCheck2(p))
যদি DoLengthyCheck1
ব্যর্থ হয়, কল করার কোনও মানে নেই DoLengthyCheck2
।
যাইহোক, ফলস্বরূপ বাইনারিগুলিতে একটি শর্ট সার্কিট অপারেশন প্রায়শই দুটি শাখার ফলস্বরূপ, যেহেতু এই শব্দার্থকগুলি সংরক্ষণের জন্য এই সংকলকটির পক্ষে এটি সহজতম উপায়। (এই কারণেই, মুদ্রার অপর প্রান্তে, শর্ট সার্কিট মূল্যায়ন কখনও কখনও অপ্টিমাইজেশন সম্ভাবনা বাধা দিতে পারে)) আপনি if
জিসিসি 5.4 দ্বারা আপনার বিবৃতিটির জন্য উত্পন্ন অবজেক্ট কোডের প্রাসঙ্গিক অংশটি দেখে এটি দেখতে পারেন :
movzx r13d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
cmp r13w, 478 ; (curr[i] < 479)
ja .L5
cmp ax, 478 ; (l[i + shift] < 479)
ja .L5
add r8d, 1 ; nontopOverlap++
আপনি এখানে দুটি তুলনা ( cmp
নির্দেশাবলী) দেখতে পাচ্ছেন , প্রতিটি তার পরে পৃথক শর্তাধীন জাম্প / শাখা ( ja
অথবা উপরে থাকলে লাফিয়ে)।
এটি থাম্বের একটি সাধারণ নিয়ম যা শাখাগুলি ধীরে ধীরে হয় এবং তাই আঁট লুপগুলিতে এড়ানো যায়। এটি কার্যত সমস্ত x86 প্রসেসরের ক্ষেত্রে সত্য হয়েছে, নমুনা ৮০৮৮ সাল থেকে (যার ধীরে ধীরে আনার সময় এবং খুব ছোট প্রিফেচ সারি [একটি নির্দেশের ক্যাশে তুলনীয়], শাখার পূর্বাভাসের সম্পূর্ণ অভাবের সাথে মিলিত, যার অর্থ নেওয়া হয়েছে যে শাখাগুলি ক্যাশে ফেলে দিতে হবে ) আধুনিক বাস্তবায়নে (যার দীর্ঘ পাইপলাইনগুলি ভুলভাবে অনুষ্টিত শাখাগুলিকে একইভাবে ব্যয়বহুল করে তোলে)। আমি সেখানে পিছলে যে ছোট ক্যাভ্যাট নোট করুন। পেন্টিয়াম প্রো যেহেতু আধুনিক প্রসেসরগুলিতে উন্নত শাখার পূর্বাভাস ইঞ্জিন রয়েছে যা শাখাগুলির ব্যয় হ্রাস করার জন্য ডিজাইন করা হয়েছে। যদি শাখার দিকনির্দেশটি সঠিকভাবে অনুমান করা যায় তবে ব্যয়টি সর্বনিম্ন। বেশিরভাগ সময়, এটি ভালভাবে কাজ করে তবে আপনি যদি এমন প্যাথোলজিকাল ক্ষেত্রে পড়েন যেখানে শাখার ভবিষ্যদ্বাণীকারী আপনার পক্ষে নেই,আপনার কোড অত্যন্ত ধীর পেতে পারে । এটি সম্ভবত আপনি এখানে আছেন বলে আপনি যেহেতু বলেছিলেন যে আপনার অ্যারেটি সাজানো নেই।
আপনি বলছেন যে বেঞ্চমার্কগুলি নিশ্চিত করেছে যে এর &&
সাথে প্রতিস্থাপন করা *
কোডটি লক্ষণীয়ভাবে দ্রুততর করে। এর কারণটি স্পষ্ট হয় যখন আমরা অবজেক্ট কোডের প্রাসঙ্গিক অংশটি তুলনা করি:
movzx r13d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
xor r15d, r15d ; (curr[i] < 479)
cmp r13w, 478
setbe r15b
xor r14d, r14d ; (l[i + shift] < 479)
cmp ax, 478
setbe r14b
imul r14d, r15d ; meld results of the two comparisons
cmp r14d, 1 ; nontopOverlap++
sbb r8d, -1
এটি সামান্য পাল্টা-স্বজ্ঞাত যে এটি আরও দ্রুত হতে পারে, যেহেতু এখানে আরও নির্দেশাবলী রয়েছে, তবে কখনও কখনও এটি অপ্টিমাইজেশান কাজ করে। আপনি দেখতে পাচ্ছেন এখানে একই তুলনা ( cmp
) করা হচ্ছে, তবে এখন, প্রতিটি এর আগে একটি xor
এবং এর পরে একটি setbe
। এক্সওআর একটি রেজিস্টার সাফ করার জন্য কেবল একটি স্ট্যান্ডার্ড ট্রিক। setbe
একটি x86 নির্দেশ করে একটি পতাকার মান উপর ভিত্তি করে একটি বিট সেট করে, এবং প্রায়ই branchless কোড প্রয়োগ করতে ব্যবহৃত হয়। এখানে, setbe
বিপরীত হয় ja
। তুলনাটি নীচে বা সমান হলে এটি তার গন্তব্য নিবন্ধকে 1 এ সেট করে (যেহেতু নিবন্ধক প্রাক শূন্য ছিল, অন্যথায় এটি 0 হবে), তবে ja
তুলনাটি উপরে থাকলে ব্রাঞ্চ করা হয়। একবার এই দুটি মানের প্রাপ্ত হয়েছে r15b
এবংr14b
নিবন্ধভুক্ত, তারা একসাথে ব্যবহার করে গুণিত হয় imul
। গুণটি traditionতিহ্যগতভাবে তুলনামূলকভাবে ধীর গতিতে কাজ করা ছিল, তবে এটি আধুনিক প্রসেসরের উপর দ্রুত গতিযুক্ত এবং এটি বিশেষত দ্রুত হবে কারণ এটি কেবলমাত্র দুটি বাইট-আকারের মানকে গুণ করছে।
আপনি কেবল বিটওয়াইড এবং অপারেটর ( &
) এর সাথে গুণটি সহজেই প্রতিস্থাপন করতে পারেন যা শর্ট সার্কিট মূল্যায়ন করে না। এটি কোডটিকে আরও পরিষ্কার করে তোলে এবং এটি এমন একটি প্যাটার্ন যা সংকলকরা সাধারণত স্বীকৃতি দেয়। তবে আপনি যখন নিজের কোড দিয়ে এটি করেন এবং এটি জিসিসি 5.4 দিয়ে সংকলন করেন, এটি প্রথম শাখাটি নির্গত করতে থাকবে:
movzx r13d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
cmp r13w, 478 ; (curr[i] < 479)
ja .L4
cmp ax, 478 ; (l[i + shift] < 479)
setbe r14b
cmp r14d, 1 ; nontopOverlap++
sbb r8d, -1
এইভাবে কোডটি নির্গত করতে কোনও প্রযুক্তিগত কারণ নেই, তবে কোনও কারণে, এর অভ্যন্তরীণ হিউরিস্টিকগুলি এটি বলছে যে এটি দ্রুত। এটা তোলে হবে সম্ভবত দ্রুততর যদি শাখা predictor আপনার পাশে ছিল, কিন্তু এটি সম্ভবত মন্থর হতে হবে শাখা ভবিষ্যদ্বাণী আরো প্রায়ই তুলনায় এটি সফল ব্যর্থ।
সংকলক (এবং অন্যান্য সংকলক, যেমন ক্ল্যাং) এর নতুন প্রজন্মগুলি এই নিয়মটি জানে এবং কখনও কখনও এটি একই কোড তৈরি করতে ব্যবহার করবে যা আপনি হাত-অনুকূলীকরণের দ্বারা চাওয়া হত। আমি নিয়মিত ক্লেং &&
একই কোডটিতে অনুবাদ বাক্য অনুবাদ দেখি যা যদি আমি ব্যবহার করতাম তবে নির্গত হত &
। নিম্নলিখিতটি সাধারণ &&
অপারেটরটি ব্যবহার করে আপনার কোড সহ জিসিসি 6.2 থেকে প্রাসঙ্গিক আউটপুট রয়েছে :
movzx r13d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
cmp r13d, 478 ; (curr[i] < 479)
jg .L7
xor r14d, r14d ; (l[i + shift] < 479)
cmp eax, 478
setle r14b
add esi, r14d ; nontopOverlap++
উল্লেখ্য কিভাবে চালাক এই হয়! স্বাক্ষর অবস্থার ব্যবহার করছে ( jg
এবং setle
) হিসেবে স্বাক্ষরবিহীন শর্ত (উল্টোদিকে ja
এবং setbe
), কিন্তু এই গুরুত্বপূর্ণ নয়। আপনি দেখতে পাচ্ছেন যে এটি এখনও পুরানো সংস্করণের মতো প্রথম শর্তের জন্য তুলনা-এবং-শাখা করে setCC
এবং দ্বিতীয় অবস্থার জন্য শাখাবিহীন কোড উত্পন্ন করতে একই নির্দেশ ব্যবহার করে , তবে এটি কীভাবে বৃদ্ধি করে তাতে অনেক বেশি দক্ষতা অর্জন করেছে । sbb
অপারেশনের জন্য পতাকা নির্ধারণের জন্য দ্বিতীয়, অপ্রয়োজনীয় তুলনা না করে বরং এটি জ্ঞান ব্যবহার করে যা r14d
1 বা 0 হবে নিঃশর্তভাবে এই মানটিকে যুক্ত করতে nontopOverlap
। যদি r14d
0 হয় তবে সংযোজনটি কোনও অপ-অপশন; অন্যথায়, এটি 1 যুক্ত করে, ঠিক যেমন এটি করার কথা।
আপনি যখন বিটওয়াইস অপারেটরের চেয়ে শর্ট-সার্কিট অপারেটর ব্যবহার করেন তখন জিসিসি 6.2 আসলে আরও কার্যকর কোড তৈরি করে :&&
&
movzx r13d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
cmp r13d, 478 ; (curr[i] < 479)
jg .L6
cmp eax, 478 ; (l[i + shift] < 479)
setle r14b
cmp r14b, 1 ; nontopOverlap++
sbb esi, -1
শাখা এবং শর্তসাপেক্ষ সেটটি এখনও রয়েছে, তবে এখন এটি বাড়ানোর কম চতুর পথে ফিরে আসে nontopOverlap
। আপনার সংকলককে চতুর করে চালানোর চেষ্টা করার সময় কেন আপনার যত্নবান হওয়া উচিত এটি একটি গুরুত্বপূর্ণ পাঠ!
তবে আপনি যদি বেঞ্চমার্ক দিয়ে প্রমাণ করতে পারেন যে শাখা প্রশাখার কোডটি আসলে ধীরে ধীরে, তবে এটি আপনার সংকলকটি চেষ্টা করে এবং চালাক করে দেওয়ার জন্য অর্থ দিতে পারে। আপনাকে কেবল বিচ্ছিন্নতার যত্ন সহকারে পরিদর্শন করেই করতে হবে you এবং আপনি যখন সংকলকের পরবর্তী সংস্করণে আপগ্রেড করবেন তখন আপনার সিদ্ধান্তগুলি পুনরায় মূল্যায়ন করার জন্য প্রস্তুত। উদাহরণস্বরূপ, আপনার কাছে কোডটি আবার লিখিত হতে পারে:
nontopOverlap += ((curr[i] < 479) & (l[i + shift] < 479));
এখানে মোটেই কোনও if
বিবৃতি নেই, এবং সংখ্যক সংকলকগণ কখনই এর জন্য ব্রাঞ্চিং কোড নির্গমন করার বিষয়ে ভাবেন না। জিসিসিও এর ব্যতিক্রম নয়; সমস্ত সংস্করণ নীচের মতো কিছু তৈরি করে:
movzx r14d, WORD PTR [rbp+rcx*2]
movzx eax, WORD PTR [rbx+rcx*2]
cmp r14d, 478 ; (curr[i] < 479)
setle r15b
xor r13d, r13d ; (l[i + shift] < 479)
cmp eax, 478
setle r13b
and r13d, r15d ; meld results of the two comparisons
add esi, r13d ; nontopOverlap++
আপনি যদি পূর্বের উদাহরণগুলি অনুসরণ করে চলেছেন তবে এটি আপনার খুব পরিচিত দেখা উচিত। উভয় তুলনা একটি শাখাবিহীন উপায়ে সম্পন্ন করা হয়, মধ্যবর্তী ফলাফলগুলি and
একসাথে এড করা হয় এবং তারপরে এই ফলাফলটি (যা হয় 0 বা 1 হবে) add
এড হয় nontopOverlap
। আপনি যদি শাখাবিহীন কোড চান, এটি কার্যত তা নিশ্চিত করবে যে আপনি এটি পেয়েছেন।
জিসিসি 7 আরও স্মার্ট হয়ে উঠেছে। এটি এখন মূল কোড হিসাবে উপরের ট্রিকটির জন্য কার্যত অভিন্ন কোড (নির্দেশাবলীর কিছুটা পুনরায় সাজানো ব্যতীত) উত্পন্ন করে। সুতরাং, আপনার প্রশ্নের উত্তর, "সংকলক কেন এভাবে আচরণ করে?" , সম্ভবত কারণ তারা নিখুঁত না! তারা সর্বাধিক অনুকূল কোড জেনারেট করতে হিউরিস্টিক্স ব্যবহার করার চেষ্টা করে তবে তারা সর্বদা সেরা সিদ্ধান্ত নেয় না। তবে অন্তত তারা সময়ের সাথে সাথে আরও স্মার্ট হতে পারে!
এই পরিস্থিতিটি দেখার একটি উপায় হ'ল ব্রাঞ্চিং কোডটিতে সেরা সেরা-কেস পারফরম্যান্স রয়েছে। যদি শাখার পূর্বাভাস সফল হয়, অপ্রয়োজনীয় ক্রিয়াকলাপগুলি এড়িয়ে যাওয়ার ফলে কিছুটা দ্রুত চলমান সময় আসবে। তবে শাখাবিহীন কোডের মধ্যে সবচেয়ে খারাপ পরিস্থিতি রয়েছে । যদি শাখার পূর্বাভাস ব্যর্থ হয় তবে একটি শাখা এড়াতে প্রয়োজনীয় হিসাবে অতিরিক্ত কয়েকটি নির্দেশাবলী কার্যকর করা অবশ্যই একটি ভুল অনুমিত শাখার চেয়ে দ্রুত হবে । এমনকি কম্পাইলারদের মধ্যে সবচেয়ে স্মার্ট এবং চতুর এই পছন্দটি করাতে খুব কঠিন সময় আসবে।
প্রোগ্রামারদের এটির জন্য নজর রাখা দরকার এমন কিছু কিনা আপনার প্রশ্নের জন্য এবং উত্তরটি প্রায় অবশ্যই হবেনা, নির্দিষ্ট গরম লুপগুলি বাদ দিয়ে যা আপনি মাইক্রো-অপ্টিমাইজেশনের মাধ্যমে গতি বাড়ানোর চেষ্টা করছেন। তারপরে, আপনি বিচ্ছিন্ন হয়ে বসে পড়ুন এবং এটি টুইট করার উপায় খুঁজে পান। এবং যেমনটি আমি আগেই বলেছি, আপনি যখন সংকলকের নতুন সংস্করণে আপডেট করবেন তখন সেই সিদ্ধান্তগুলি পুনর্বিবেচনার জন্য প্রস্তুত থাকুন, কারণ এটি হয় আপনার কৌতুকপূর্ণ কোডটির সাথে মূid় কিছু করতে পারে, বা এটির অপ্টিমাইজেশনের হিউরিস্টিকগুলি যথেষ্ট পরিবর্তন করেছে যে আপনি ফিরে যেতে পারেন আপনার মূল কোড ব্যবহার করতে। পুঙ্খানুপুঙ্খ মন্তব্য!