জিসিসি 5.4.0 সহ একটি ব্যয়বহুল লাফ


171

আমার এমন একটি ফাংশন ছিল যা দেখতে দেখতে (কেবল গুরুত্বপূর্ণ অংশটি দেখায়):

double CompareShifted(const std::vector<uint16_t>& l, const std::vector<uint16_t> &curr, int shift, int shiftY)  {
...
  for(std::size_t i=std::max(0,-shift);i<max;i++) {
     if ((curr[i] < 479) && (l[i + shift] < 479)) {
       nontopOverlap++;
     }
     ...
  }
...
}

এই মত লিখিত, ফাংশনটি আমার মেশিনে 34 ডলার। শর্তটি বোল গুণে পরিণত করার পরে (কোডটিকে এরকম দেখায়):

double CompareShifted(const std::vector<uint16_t>& l, const std::vector<uint16_t> &curr, int shift, int shiftY)  {
...
  for(std::size_t i=std::max(0,-shift);i<max;i++) {
     if ((curr[i] < 479) * (l[i + shift] < 479)) {
       nontopOverlap++;
     }
     ...
  }
...
}

মৃত্যুদন্ড কার্যকর সময় কমে 19 ডলার।

ব্যবহৃত সংকলকটি জিসিসি 5.4.0 ছিল -O3 এর সাথে এবং গডবোল্ট.আর্গ.আর ব্যবহার করে উত্পন্ন এএসএম কোডটি যাচাই করার পরে আমি জানতে পেরেছিলাম যে প্রথম উদাহরণটি একটি লাফ উত্পন্ন করে, যখন দ্বিতীয়টি তা করে না। আমি জিসিসি .2.২.০ চেষ্টা করার সিদ্ধান্ত নিয়েছি যা প্রথম উদাহরণটি ব্যবহার করার সময় একটি লাফের নির্দেশও উত্পন্ন করে তবে জিসিসি gene মনে হয় এটি আর একটি উত্পন্ন করে না।

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

সম্পাদনা: গডবোল্টের লিঙ্ক https://godbolt.org/g/5lKPF3


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

26
আমার অনুমান যে শর্ট সার্কিট মূল্যায়ন এর &&কারণ।
জেনস

9
নোট করুন যে এটি কেন আমাদের রয়েছে &
রুবেনভ

7
@ জাকুব এটি বাছাই করলে সম্ভবত মৃত্যুদন্ডের গতি বাড়বে, এই প্রশ্নটি দেখুন
রুবেনভ

8
@ রুবেনভবি "অবশ্যই মূল্যায়ন করা উচিত নয়" আসলে কোনও অভিব্যক্তির জন্য এমন কোনও অর্থ বোঝায় না যার কোনও পার্শ্ব প্রতিক্রিয়া নেই। আমার সন্দেহ হয় যে ভেক্টর বাউন্ডস-চেকিং করে এবং জিসিসি প্রমাণ করতে পারে না যে এটি সীমা ছাড়িয়ে যাবে না। সম্পাদনা করুন: বাস্তবিক, আমি মনে করি না আপনি কি করছে কিছু করছেন আমি থামাতে সীমার বাইরে থেকে + Shift।
র্যান্ডম 832

উত্তর:


263

লজিক্যাল এন্ড অপারেটর ( &&) শর্ট সার্কিট মূল্যায়ন ব্যবহার করে যার অর্থ প্রথম পরীক্ষাটি যদি সত্যের সাথে মূল্যায়ন করে তবেই দ্বিতীয় পরীক্ষা করা হয়। এটি প্রায়শই আপনার প্রয়োজনীয় শব্দার্থক হয়। উদাহরণস্বরূপ, নিম্নলিখিত কোডটি বিবেচনা করুন:

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অপারেশনের জন্য পতাকা নির্ধারণের জন্য দ্বিতীয়, অপ্রয়োজনীয় তুলনা না করে বরং এটি জ্ঞান ব্যবহার করে যা r14d1 বা 0 হবে নিঃশর্তভাবে এই মানটিকে যুক্ত করতে nontopOverlap। যদি r14d0 হয় তবে সংযোজনটি কোনও অপ-অপশন; অন্যথায়, এটি 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় কিছু করতে পারে, বা এটির অপ্টিমাইজেশনের হিউরিস্টিকগুলি যথেষ্ট পরিবর্তন করেছে যে আপনি ফিরে যেতে পারেন আপনার মূল কোড ব্যবহার করতে। পুঙ্খানুপুঙ্খ মন্তব্য!


3
ঠিক আছে, এখানে সার্বজনীন "আরও ভাল" নেই। এটি সব আপনার পরিস্থিতির উপর নির্ভর করে, এ কারণেই আপনি যখন এই জাতীয় নিম্ন স্তরের পারফরম্যান্স অপ্টিমাইজেশন করছেন তখন আপনাকে অবশ্যই বেঞ্চমার্ক করতে হবে। আমি উত্তরে যেমনটি ব্যাখ্যা করেছি, আপনি যদি শাখার পূর্বাভাসের আকার হারাতে থাকেন তবে ভুল অনুমান করা শাখাগুলি আপনার কোডটি অনেকটা কমিয়ে দিচ্ছে । কোডের শেষ বিট কোনও শাখা ব্যবহার করে না ( j*নির্দেশাবলীর অনুপস্থিতিতে দ্রষ্টব্য ), সুতরাং এটি ক্ষেত্রে এটি আরও দ্রুত হবে। [অব্যাহত]
কোডি গ্রে


2
@ 8 বিট ঠিক আছে আমি উপসাগর সারি উল্লেখ ছিল। আমার সম্ভবত এটিকে ক্যাশে বলা উচিত ছিল না, তবে ফ্রেসিং সম্পর্কে ভয়াবহভাবে উদ্বিগ্ন ছিলেন না এবং স্পেসিফিক্সগুলি স্মরণ করার জন্য খুব দীর্ঘ সময় ব্যয় করেননি, কারণ আমি historicalতিহাসিক কৌতূহল ব্যতীত কাউকে খুব বেশি যত্নবান মনে করি নি। আপনি যদি বিশদ চান, মাইকেল আবরাশের জেন অ্যাসেম্বলি ল্যাঙ্গুয়েজ অমূল্য। পুরো বইটি অনলাইনে বিভিন্ন জায়গায় পাওয়া যায়; এখানে শাখা প্রশাখার ক্ষেত্রে প্রযোজ্য অংশটি রয়েছে তবে আপনার প্রিফেচিংয়ের অংশগুলিও পড়তে হবে এবং বুঝতে হবে।
কোডি গ্রে

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

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

23

একটি গুরুত্বপূর্ণ বিষয় লক্ষণীয়

(curr[i] < 479) && (l[i + shift] < 479)

এবং

(curr[i] < 479) * (l[i + shift] < 479)

শব্দার্থগত সমতুল্য না! বিশেষত, যদি আপনার যদি কখনও পরিস্থিতি থাকে তবে:

  • 0 <= iএবং i < curr.size()উভয় সত্য
  • curr[i] < 479 মিথ্যা
  • i + shift < 0বা i + shift >= l.size()সত্য

তারপরে অভিব্যক্তিটি (curr[i] < 479) && (l[i + shift] < 479)একটি সু-সংজ্ঞায়িত বুলিয়ান মান হিসাবে গ্যারান্টিযুক্ত। উদাহরণস্বরূপ, এটি বিভাজন ত্রুটি সৃষ্টি করে না।

যাইহোক, এই পরিস্থিতিতে, অভিব্যক্তি (curr[i] < 479) * (l[i + shift] < 479)হয় অনির্ধারিত আচরণ ; এটা করা হয় একটি সেগমেন্টেশন ফল্ট কারণ অনুমোদিত।

এর অর্থ হ'ল মূল কোড স্নিপেটের জন্য, উদাহরণস্বরূপ, সংকলক কেবল একটি লুপ লিখতে পারে না যা উভয় তুলনা সম্পাদন করে এবং একটি andঅপারেশন করে, যদি না সংকলকটি এটি প্রমাণ l[i + shift]করতে না পারে যে কোনও পরিস্থিতিতে সেগফ্ল্টের কারণ হবে না যেখানে এটি প্রয়োজন হয় না।

সংক্ষেপে, কোডের মূল অংশটি পরবর্তীগুলির তুলনায় অপ্টিমাইজেশনের জন্য কম সুযোগ সরবরাহ করে offers (অবশ্যই, সংকলক সুযোগটি স্বীকৃতি দেয় কি না তা সম্পূর্ণ ভিন্ন প্রশ্ন)

পরিবর্তে আপনি মূল সংস্করণটি ঠিক করতে পারেন

bool t1 = (curr[i] < 479);
bool t2 = (l[i + shift] < 479);
if (t1 && t2) {
    // ...

এই! shift(এবং max) এর মান অনুসারে এখানে ইউবি রয়েছে ...
ম্যাথিউ এম।

18

&&অপারেটর শর্ট সার্কিট মূল্যায়ন বাস্তবায়ন করছে। এর অর্থ হ'ল দ্বিতীয় অপারেন্ডটি কেবল তখনই মূল্যায়ন করা হয় যদি প্রথমটির সাথে মূল্যায়ন করা হয় true। এটি অবশ্যই সেই ক্ষেত্রে ঝাঁপিয়ে পড়ে।

এটি দেখানোর জন্য আপনি একটি ছোট উদাহরণ তৈরি করতে পারেন:

#include <iostream>

bool f(int);
bool g(int);

void test(int x, int y)
{
  if ( f(x) && g(x)  )
  {
    std::cout << "ok";
  }
}

এসেম্বলারের আউটপুট এখানে পাওয়া যাবে

আপনি উত্পন্ন কোডটি প্রথম কলগুলি দেখতে পারেন f(x), তারপরে আউটপুটটি পরীক্ষা করে এবং এর মূল্যায়নে ঝাঁপিয়ে পড়েg(x) কখন এটি হয়েছিলtrue । অন্যথায় এটি ফাংশন ছেড়ে দেয়।

পরিবর্তে "বুলিয়ান" গুণ ব্যবহার করে উভয় অপারেন্ডের মূল্যায়ন প্রতিবারই বাধ্য করে এবং এইভাবে লাফানোর দরকার পড়ে না।

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


1
আপনি কেন বলছেন যে প্রতিবার দু'টি অপারেন্ডের মূল্যায়নকে গুণ করা বাধ্যতামূলক করে? 0 * x = x * 0 = 0 নির্বিশেষে x এর মান। অপ্টিমাইজেশান হিসাবে, সংকলকটি গুণকে "শর্টকাটকিট "ও করতে পারে। উদাহরণস্বরূপ, স্ট্যাকওভারফ্লো . com / প্রশ্নগুলি / ৮১৫৫৯৯৪/২ দেখুন । তদ্ব্যতীত, &&অপারেটরের সাথে বিপরীতে , গুণটি প্রথম বা দ্বিতীয় যুক্তির সাহায্যে অলস-মূল্যায়িত হতে পারে, যার ফলে অপ্টিমাইজেশনের জন্য আরও বেশি স্বাধীনতা পাওয়া যায়।
সামউইউইউটিউজারনেম

@ জেনস - "সাধারণত শাখার পূর্বাভাস সাহায্য করে, তবে যদি আপনার ডেটা এলোমেলো হয় তবে ভবিষ্যদ্বাণী করা যায় এমন খুব বেশি কিছু নেই।" - ভাল উত্তর দেয়।
শেচপুরিন

1
@ সোমেউইটি ইউজারনেম ঠিক আছে, সংকলক অবশ্যই কোনও অপ্টিমাইজেশন করতে নিরবচ্ছিন্ন যা পর্যবেক্ষণযোগ্য আচরণ রাখে। এটি এটিকে রূপান্তর করতে পারে এবং গণনা ছাড়তে পারে। আপনি যদি গণনা করেন 0 * f()এবং fপর্যবেক্ষণযোগ্য আচরণ করেন তা সংকলকটিকে কল করতে হবে। পার্থক্যটি হ'ল শর্ট সার্কিট মূল্যায়নের জন্য বাধ্যতামূলক &&তবে যদি এটি প্রদর্শিত হয় তবে এটি এর সমতুল্য *
জেনস

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

3
@ জেনস: শর্ট সার্কিট মূল্যায়ন বাধ্যতামূলক নয়। কোডটি কেবল এমনভাবে আচরণ করা প্রয়োজন যেমন এটি শর্ট সার্কিট; সংকলকটি ফলাফল অর্জন করতে পছন্দ করে এমন কোনও উপায় ব্যবহার করার অনুমতি দেয়।

-2

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


8
লাফটি দ্বিতীয় শর্তটি যদি প্রথমটি সত্য হয় তবেই মূল্যায়ন করা হয় from কোডটি অন্যথায় অবশ্যই এটির মূল্যায়ন করবে না, সুতরাং সংকলক এটি আরও ভালতর করতে পারে না এবং এখনও সঠিক হতে পারে (যদি না এটি প্রথম বিবৃতিটি সর্বদা সত্যই নির্ধারণ করতে পারে)।
রুবেনভ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.