কেন সি সংকলকগুলি স্যুইচটি অপ্টিমাইজ করে এবং যদি আলাদাভাবে হয়


9

আমি সম্প্রতি একটি ব্যক্তিগত প্রকল্পে কাজ করছি যখন আমি একটি বিজোড় সমস্যাটি নিয়ে হোঁচট খেয়েছি।

খুব কড়া লুপে আমার 0 এবং 15 এর মধ্যে একটি মান সহ পূর্ণসংখ্যা রয়েছে 0, 5, 12, এবং 13 এর মান 0, 1, 8, এবং 9 এবং 1 এর জন্য আমাকে 1 পাওয়া দরকার।

আমি কয়েকটি বিকল্প যাচাই করতে গডবোল্টে পরিণত হয়েছিল এবং অবাক হয়ে গিয়েছিলাম যে দেখে মনে হয়েছিল যে সংকলকটি যদি কোনও সুইচ স্টেটমেন্টটিকে ইফ চেইনের মতো একইভাবে অনুকূলিত করতে পারে না।

লিঙ্কটি এখানে: https://godbolt.org/z/WYVBFl

কোডটি হ'ল:

const int lookup[16] = {-1, -1, 0, 0, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0};

int a(int num) {
    return lookup[num & 0xF];
}

int b(int num) {
    num &= 0xF;

    if (num == 0 || num == 1 || num == 8 || num == 9) 
        return -1;

    if (num == 4 || num == 5 || num == 12 || num == 13)
        return 1;

    return 0;
}

int c(int num) {
    num &= 0xF;
    switch (num) {
        case 0: case 1: case 8: case 9: 
            return -1;
        case 4: case 5: case 12: case 13:
            return 1;
        default:
            return 0;
    }
}

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

অদ্ভুতভাবে, bবিট-হ্যাকগুলিতে সংকলন করা cহয়েছিল যখন হয় aলক্ষ্যমাত্রা হার্ডওয়ারের উপর নির্ভর করে কোনওরকম বেশ আন-অপটিমাইজড বা হ্রাস পেয়েছে case

এই বৈষম্য কেন কেউ ব্যাখ্যা করতে পারেন? এই কোয়েরিটি অনুকূলিত করার 'সঠিক' উপায় কী?

সম্পাদনা করুন:

শোধন

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

আমি প্রদর্শনের জন্য একটি দ্রুত প্রোগ্রাম লিখেছি এবং টিআইওর স্থানীয় ফলাফল হিসাবে আমি একই ফলাফল পেয়েছি: অনলাইনে চেষ্টা করে দেখুন!

দেখার static inlineটেবিলটি কিছুটা গতি বাড়িয়েছে: অনলাইনে চেষ্টা করে দেখুন!


4
আমার সন্দেহ হয় উত্তরটি "কম্পাইলাররা সর্বদা বুদ্ধিমান পছন্দ করে না"। আমি শুধু সঙ্গে জিসিসি 8.3.0 সঙ্গে একটি বস্তু আপনার কোড কম্পাইল -O3, এবং এটি কম্পাইল cসম্ভবত তদপেক্ষা মন্দ কিছুর জন্য aবা b( cদুই শর্তসাপেক্ষ জাম্প প্লাস কয়েক বিট হেরফেরের, বনাম শুধুমাত্র একটি শর্তাধীন লাফ এবং জন্য সহজ বিট manip ছিল b), কিন্তু এখনও আইটেম পরীক্ষা করে নিষ্পাপ আইটেমের চেয়ে ভাল। আমি নিশ্চিত না যে আপনি এখানে সত্যই যা চাইছেন; সহজ সত্য যে একটি সর্বোচ্চকরন কম্পাইলার চালু করতে পারেন হয় কোনো মধ্যে এগুলোর কোনো অন্যদের যদি এটা তা চয়ন তাই হয়, এবং সেখানে এটি বা না হবে কি জন্য কোন ধরাবাঁধা নিয়ম আছে।
শ্যাডোর্যাঞ্জার

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

আমি কমপক্ষে ফাংশনগুলি স্থির হিসাবে, বা - আরও ভাল-ইনলাইন করে সংজ্ঞা দিয়ে শুরু করব।
ওয়াইল্ডপ্লাজার

@ উইল্ডপ্লাজার এটি গতি বাড়িয়ে তোলে, তবে ifএখনও মারধর করে switch(অদ্ভুতভাবে চেহারা আরও দ্রুত হয়ে যায়) [অনুসরণ করতে টিআইও]
লাম্বদাবেটা

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

উত্তর:


6

যদি আপনি স্পষ্টভাবে সমস্ত কেস গণনা করেন, জিসিসি খুব দক্ষ:

int c(int num) {
    num &= 0xF;
    switch (num) {
        case 0: case 1: case 8: case 9: 
            return -1;
        case 4: case 5: case 12: case 13:
            return 1;
            case 2: case 3: case 6: case 7: case 10: case 11: case 14: case 15: 
        //default:
            return 0;
    }
}

সবেমাত্র একটি সাধারণ সূচকযুক্ত শাখায় সংকলিত:

c:
        and     edi, 15
        jmp     [QWORD PTR .L10[0+rdi*8]]
.L10:
        .quad   .L12
        .quad   .L12
        .quad   .L9
        .quad   .L9
        .quad   .L11
        .quad   .L11
        .quad   .L9
        .quad   .L9
        .quad   .L12
etc...

মনে রাখবেন যে যদি default:আপত্তিহীন হয়, জিসিসি তার নেস্টেড শাখা সংস্করণে ফিরে আসে।


1
@ ল্যাম্বদাবেতা আপনার আমার উত্তরটি মেনে নেওয়া এবং এটি গ্রহণ করার বিষয়টি বিবেচনা করা উচিত, কারণ আধুনিক ইন্টেল সিপিইউ দুটি সমান্তরাল সূচকযুক্ত মেমরি পড়তে / চক্র করতে পারে তবে আমার ট্রিকটির থ্রিপুট সম্ভবত 1 লুক / চক্র। ফ্লিপ দিকে, সম্ভবত আমার হ্যাকটি এসএসই 2 pslld/ psradবা তাদের 8-উপায় অ্যাভিএক্স 2 সমতুল্য সহ 4-ওয়ে ভেক্টরাইজেশনের জন্য আরও অনুকূল । আপনার কোডের অন্যান্য বৈশিষ্ট্যের উপর অনেক কিছু নির্ভর করে।
আইভিলনোটেক্সিস্ট আইডোনোটেক্সবাদ

4

সি সংকলকগুলির জন্য বিশেষ কেস রয়েছে switch, কারণ তারা আশা করে প্রোগ্রামাররা এর মূর্খতা বোঝে switchএবং এটি ব্যবহার করবে।

কোড এর মতো:

if (num == 0 || num == 1 || num == 8 || num == 9) 
    return -1;

if (num == 4 || num == 5 || num == 12 || num == 13)
    return 1;

সক্ষম সি কোডার দ্বারা পর্যালোচনা পাস না; তিন বা চারটি পর্যালোচক একই সাথে "এই হওয়া উচিত switch!"

সি সংযোজকগুলির পক্ষে ifলাফ টেবিলে রূপান্তর করার জন্য বিবৃতিগুলির কাঠামো বিশ্লেষণ করা উপযুক্ত নয় worth এর জন্য শর্তগুলি ঠিক সঠিক হতে হবে, এবং একগুচ্ছ ifবিবৃতিতে যে পরিমাণ বৈচিত্রের সম্ভাবনা রয়েছে তা হল জ্যোতির্বিজ্ঞান। বিশ্লেষণটি জটিল এবং উভয়ই নেতিবাচক হিসাবে আসার সম্ভাবনা রয়েছে (যেমন: "না, আমরা এগুলিকে ifএকটিতে রূপান্তর করতে পারি না switch")।


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

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

@ ল্যাম্বদাবেতা: দেখার টেবিলটি এড়ানোর কোনও কারণ আছে? এটি তৈরি করুন staticএবং আপনি যেটি নির্ধারণ করছেন সেটি এটি আরও স্পষ্ট করতে চাইলে সি 99 মনোনীত প্রারম্ভিক ব্যবহার করুন এবং এটি স্পষ্টভাবে ঠিক আছে।
শ্যাডোর্যাঞ্জার

1
আমি কমপক্ষে স্বল্প বিটটি বাতিল করতে শুরু করব যাতে অপ্টিমাইজারের আরও কম কাজ করার দরকার নেই।
আর .. গীটহাব বন্ধ হেল্পিং আইসিসি

@ শ্যাডোএ্যাঞ্জার দুর্ভাগ্যক্রমে এটি এখনও if(সম্পাদনা দেখুন) এর চেয়ে ধীর । @ আর .. আমি সংকলকটির জন্য সম্পূর্ণ বিটওয়াইজ সলিউশনটি তৈরি করেছি, যা আমি বর্তমানে ব্যবহার করছি। দুর্ভাগ্যক্রমে আমার ক্ষেত্রে এগুলি enumমানগুলি হয়, নগ্ন পূর্ণসংখ্যা নয়, তাই বিটওয়াইস হ্যাকগুলি খুব রক্ষণাবেক্ষণযোগ্য নয়।
লাম্বদাবেতা

4

নিম্নলিখিত কোডটি আপনার লুক্কায়িত শাখা-প্রশাখা, LUT-free, clock 3 ঘড়ির চক্র, ~ 4 দরকারী নির্দেশিকা এবং উচ্চ inline-যোগ্য x86 মেশিন কোডের 13 বাইট গণনা করবে ।

এটি 2 এর পরিপূরক পূর্ণসংখ্যার উপস্থাপনার উপর নির্ভর করে।

তবে আপনাকে অবশ্যই তা নিশ্চিত করতে হবে যে u32এবং s32টাইপিডেফগুলি 32-বিট স্বাক্ষরবিহীন এবং স্বাক্ষরিত পূর্ণসংখ্যার প্রকারগুলিতে নির্দেশ করে। stdint.hপ্রকারগুলি uint32_tএবং int32_tউপযুক্ত হতে পারে তবে শিরোনাম যদি আপনার কাছে উপলব্ধ থাকে তবে আমার কোনও ধারণা নেই।

const int lookup[16] = {-1, -1, 0, 0, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0};

int a(int num) {
    return lookup[num & 0xF];
}


int d(int num){
    typedef unsigned int u32;
    typedef signed   int s32;

    // const int lookup[16]     = {-1, -1, 0, 0, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0};
    // 2-bit signed 2's complement: 11 11 00 00 01 01 00 00 11 11 00 00 01 01 00 00
    // Hexadecimal:                   F     0     5     0     F     0     5     0
    const u32 K = 0xF050F050U;

    return (s32)(K<<(num+num)) >> 30;
}

int main(void){
    for(int i=0;i<16;i++){
        if(a(i) != d(i)){
            return !0;
        }
    }
    return 0;
}

নিজের জন্য এখানে দেখুন: https://godbolt.org/z/AcJWWf


ধ্রুবক নির্বাচন উপর

আপনার চেহারা -1 এবং +1 সহ অন্তর্ভুক্ত 16 খুব ছোট স্থির জন্য। প্রতিটি 2 বিটের মধ্যে ফিট করে এবং এর মধ্যে 16 টি রয়েছে, যা আমরা নীচে রেখে দিতে পারি:

// const int lookup[16]     = {-1, -1, 0, 0, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0};
// 2-bit signed 2's complement: 11 11 00 00 01 01 00 00 11 11 00 00 01 01 00 00
// Hexadecimal:                   F     0     5     0     F     0     5     0
u32 K = 0xF050F050U;

এগুলিকে সর্বাধিক উল্লেখযোগ্য বিটের নিকটতম সূচক 0 দিয়ে রেখে একক শিফট 2*numআপনার 2-বিট সংখ্যার সাইন বিটটিকে নিবন্ধকের সাইন বিটটিতে রাখবে। 2-বিট সংখ্যাটি 32-2 = 30 বিট দ্বারা ডান স্থানান্তরিত করা এটিকে সম্পূর্ণরূপে প্রসারিত করে int, কৌশলটি সম্পূর্ণ করে।


এটি magicকীভাবে পুনর্জাগরণ করতে হবে তার ব্যাখ্যা দিয়ে একটি মন্তব্য করার মাধ্যমে এটি করার সবচেয়ে পরিষ্কার উপায় হতে পারে। আপনি কীভাবে এটি সামনে এসেছিলেন তা ব্যাখ্যা করতে পারেন?
লাম্বদাবেতা

গ্রহণযোগ্য হিসাবে এটি দ্রুত থাকা অবস্থায় এটি 'পরিষ্কার' করা যায়। (কিছু
প্রিপ্রোসেসর

1
আমার !!(12336 & (1<<x))-!!(771 & (1<<x));
শাখাবিহীন প্রয়াসকে বীট দেয়

0

আপনি কেবল গাণিতিক ব্যবহার করে একই প্রভাব তৈরি করতে পারেন:

// produces : -1 -1 0 0 1 1 0 0 -1 -1 0 0 1 1 0 0 ...
int foo ( int x )
{
    return 1 - ( 3 & ( 0x46 >> ( x & 6 ) ) );
}

যদিও, প্রযুক্তিগতভাবে, এটি এখনও (বিটওয়াইজ) লুক্কায়িত।

যদি উপরেরটি খুব তীব্র মনে হয় তবে আপনি এটি করতেও পারেন:

int foo ( int x )
{
    int const y = x & 6;
    return (y == 4) - !y;
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.