সংকলক কেন অনুমানযোগ্য সংযোজন লুপকে একটি গুণে অপ্টিমাইজ করতে পারে (বা না)?


133

এই প্রশ্নটি মাইস্টিয়ালের উজ্জ্বল উত্তরটি পড়ার সময় মাথায় আসে এমন প্রশ্নের এই প্রশ্ন: একটি বাছাই করা অ্যারের চেয়ে বাছাই করা অ্যারে প্রক্রিয়া করা কেন দ্রুত হয় ?

জড়িত প্রকারের জন্য প্রসঙ্গ:

const unsigned arraySize = 32768;
int data[arraySize];
long long sum = 0;

তার উত্তরে তিনি ব্যাখ্যা করেছেন যে ইন্টেল সংকলক (আইসিসি) এটি অনুকূল করে:

for (int i = 0; i < 100000; ++i)
    for (int c = 0; c < arraySize; ++c)
        if (data[c] >= 128)
            sum += data[c];

... এর সমতুল্য কিছুতে:

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

অপ্টিমাইজারটি সনাক্ত করছে যে এগুলি সমতুল্য এবং তাই লুপগুলি বিনিময় করছে , শাখাটি অভ্যন্তরের লুপের বাইরে নিয়ে যাচ্ছে moving খুব চালাক!

কিন্তু কেন এটি করে না?

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000 * data[c];

আশা করি মিস্টিয়াল (বা অন্য কেউ) সমান উজ্জ্বল উত্তর দিতে পারে। এর আগে অন্য প্রশ্নে আলোচিত অপটিমাইজেশনগুলি সম্পর্কে আমি কখনই জানতে পারি নি, তাই এর জন্য আমি সত্যিই কৃতজ্ঞ।


14
এটি এমন কিছু যা সম্ভবত কেবল ইন্টেলই জানে। আমি জানি না এটি কী অর্ডার চালায় এটির অপ্টিমাইজেশন পাস হয়। এবং স্পষ্টতই, এটি লুপ-ইন্টারচেঞ্জের পরে লুপ-সংঘর্ষ পাস চালায় না।
রহস্যময়

7
এই অপ্টিমাইজেশনটি কেবলমাত্র বৈধ যদি ডেটা অ্যারেতে থাকা মানগুলি পরিবর্তনযোগ্য হয়। উদাহরণস্বরূপ, প্রতিবার ইনপুট / আউটপুট ডিভাইসে মেমরি ম্যাপ করা থাকলে আপনি যখন তথ্য পড়েন তবে [0] একটি আলাদা মান তৈরি করবে ...
থমাস সিজি ডি ভিলেনা

2
এটি কোন ধরণের ডেটা টাইপ, পূর্ণসংখ্যা বা ভাসমান-বিন্দু? ভাসমান-পয়েন্টে বারবার সংযোজন গুণনের থেকে খুব আলাদা ফলাফল দেয়।
বেন ভয়েগট

6
@ থমাস: যদি ডেটা থাকত volatileতবে লুপ ইন্টারচেঞ্জটিও একটি অবৈধ অপটিমাইজেশন হতে পারে।
বেন ভয়েগট

3
জিএনএটি (জিসিসি ৪.6 সহ অ্যাডা সংকলক) ও 3 এ লুপগুলি স্যুইচ করবে না, তবে যদি লুপগুলি পরিবর্তন করা হয় তবে এটি এটিকে একটি গুণে রূপান্তরিত করবে।
পেশাদাররা

উত্তর:


105

সংকলক সাধারণত রূপান্তর করতে পারে না

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

মধ্যে

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000 * data[c];

কারণ দ্বিতীয়টি স্বাক্ষরিত পূর্ণসংখ্যার উপচে পড়তে পারে যেখানে পূর্বেরটি নেই। এমনকি স্বাক্ষরিত দু'জনের পরিপূরক পূর্ণসংখ্যার ওভারফ্লোর জন্য গ্যারান্টযুক্ত মোড়কের আশেপাশের আচরণের ফলে, ফলাফলটি পরিবর্তিত হবে (যদি data[c]30000 হয় তবে পণ্যটি প্রায় -129496729632-বিট intর‌্যাপের সাথে প্রায় হয়ে যাবে, যখন 100000 বার 30000 যোগ sumকরবে, যদি তা উপচে পড়া নয়, sum3000000000 দ্বারা বৃদ্ধি করা)। নোট করুন যে একই স্বাক্ষরযুক্ত স্বতন্ত্র পরিমাণের জন্য, বিভিন্ন সংখ্যার সাথে, ওভারফ্লো 100000 * data[c]সাধারণত একটি হ্রাস মডুলোর প্রবর্তন করবে 2^32যা চূড়ান্ত ফলাফলের মধ্যে উপস্থিত হবে না।

এটি এটিকে রূপান্তর করতে পারে

for (int c = 0; c < arraySize; ++c)
    if (data[c] >= 128)
        sum += 100000LL * data[c];  // resp. 100000ull

যদিও, যথারীতি, এর long longচেয়ে যথেষ্ট বড় int

এটি কেন এটি করে না, আমি বলতে পারি না, আমি অনুমান করি যে এটি মাইস্টিকাল যা বলেছিল , "স্পষ্টতই, এটি লুপ-ইন্টারচেঞ্জের পরে একটি লুপ-সংঘর্ষ পাস চালায় না"।

নোট করুন যে লুপ-ইন্টারচেঞ্জ নিজেই সাধারণত বৈধ নয় (স্বাক্ষরিত পূর্ণসংখ্যার জন্য), যেহেতু

for (int c = 0; c < arraySize; ++c)
    if (condition(data[c]))
        for (int i = 0; i < 100000; ++i)
            sum += data[c];

যেখানে ওভারফ্লো হতে পারে

for (int i = 0; i < 100000; ++i)
    for (int c = 0; c < arraySize; ++c)
        if (condition(data[c]))
            sum += data[c];

করত না। এটি এখানে কোশার, যেহেতু শর্তটি নিশ্চিত করে data[c]যে যোগ করা সমস্ত কিছুতে একই চিহ্ন রয়েছে, সুতরাং যদি কোনও প্রবাহিত হয়, উভয়ই করে।

আমি খুব নিশ্চিত হতে পারি না যে সংকলকটি এটি অ্যাকাউন্টে নিয়েছে, যদিও (@ মিস্টিয়াল, আপনি কি এমন অবস্থা দিয়ে চেষ্টা করতে পারেন data[c] & 0x80বা এটি ইতিবাচক এবং নেতিবাচক মানের জন্য সত্য হতে পারে?)। আমি কম্পাইলার অবৈধ optimisations করতে (উদাহরণস্বরূপ, বছর দুয়েক আগে, আমি একটি আইসিসি (11.0 ছিল, iirc) সাইন-32-বিট-int- এ-টু-দ্বিগুন রূপান্তর ব্যবহার ছিল 1.0/nযেখানে nছিল একটি unsigned int। জিসিসি এর মত প্রায় দ্বিগুণ দ্রুত ছিল আউটপুট। কিন্তু ভুল, প্রচুর মান 2^31ওও এর চেয়ে বড় ছিল )


4
আমার মনে আছে এমপিডাব্লু সংকলনের একটি সংস্করণ যা 32K এর চেয়ে বড় স্ট্যাক ফ্রেমগুলিকে অনুমতি দেওয়ার জন্য একটি বিকল্প যুক্ত করেছে [স্থানীয় সংস্করণের জন্য @ পূর্ববর্তী সংস্করণগুলি @ A7 + int16 ঠিকানা ব্যবহার করে সীমাবদ্ধ ছিল]। এটি 32 কে বা K৪ কেওরও বেশি স্ট্যাক ফ্রেমের জন্য সবকিছু ঠিকঠাক পেয়েছিল, তবে একটি 40 কে স্ট্যাক ফ্রেমের জন্য এটি ব্যবহার ADD.W A6,$A000করে অ্যাড্রেসের নিবন্ধের শব্দের ক্রিয়াকলাপটি অ্যাডের আগে 32 বিটগুলিতে সাইন-প্রসারিত করে। সমস্যা সমাধানের জন্য কিছুটা সময় নিয়েছিল, যেহেতু কোডটি কেবল তখনই করেছিল ADDএবং পরের বার যখন স্ট্যাকটি এ -6 পপ করেছিল তখন কলারের রেজিস্টারগুলি সে ফ্রেমে সংরক্ষণ করা পুনরুদ্ধার করা ছিল ...
সুপারক্যাট

3
... এবং কলকারী কেবলমাত্র রেজিস্টারই যত্নশীল ছিলেন যা ছিল একটি স্থির অ্যারের [লোড-টাইম ধ্রুবক] ঠিকানা। সংকলক জানত যে অ্যারের ঠিকানাটি একটি রেজিস্টারে সংরক্ষণ করা হয়েছে যাতে এটির উপর ভিত্তি করে এটি অনুকূলিত করা যায় তবে ডিবাগারটি কেবল একটি ধ্রুবকের ঠিকানা জানত। সুতরাং, একটি বিবৃতি দেওয়ার আগে MyArray[0] = 4;আমি অ্যাড্রেসটি পরীক্ষা করতে MyArrayএবং বিবৃতি কার্যকর করার আগে এবং পরে সেই অবস্থানটি দেখতে পারি; এটি পরিবর্তন হবে না। কোড এর মতো কিছু ছিল move.B @A3,#4এবং MyArrayনির্দেশিকা কার্যকর হওয়া যে কোনও সময় A3 এর সর্বদা নির্দেশ করা উচিত ছিল , কিন্তু তা হয়নি। মজাদার।
সুপারক্যাট

তাহলে কেন ঝাঁকুনি এই ধরণের অপ্টিমাইজেশন করে?
জেসন এস

সংকলকটি তার অভ্যন্তরীণ মধ্যবর্তী উপস্থাপনাগুলিতে সেই পুনর্লিখনটি সম্পাদন করতে পারে, কারণ এটি এর অভ্যন্তরীণ মধ্যবর্তী উপস্থাপনাগুলিতে কম অপরিজ্ঞাত আচরণের অনুমতি পেয়েছে।
ব্যবহারকারী 253751

48

এই উত্তরটি সংযুক্ত নির্দিষ্ট কেসের ক্ষেত্রে প্রযোজ্য নয়, তবে এটি প্রশ্নের শিরোনামের ক্ষেত্রে প্রযোজ্য এবং ভবিষ্যতের পাঠকদের জন্য এটি আকর্ষণীয় হতে পারে:

সীমাবদ্ধ নির্ভুলতার কারণে, বারবার ভাসমান-পয়েন্ট সংযোজন গুণকের সমতুল্য নয় । বিবেচনা:

float const step = 1e-15;
float const init = 1;
long int const count = 1000000000;

float result1 = init;
for( int i = 0; i < count; ++i ) result1 += step;

float result2 = init;
result2 += step * count;

cout << (result1 - result2);

ডেমো


10
এটি জিজ্ঞাসিত প্রশ্নের কোনও উত্তর নয়। আকর্ষণীয় তথ্য থাকা সত্ত্বেও (এবং কোনও সি / সি ++ প্রোগ্রামারটির জন্য অবশ্যই একটি তথ্য অবশ্যই জানা উচিত) এটি কোনও ফোরাম নয় এবং এটি এখানে নেই।
orlp

30
@ নাইটক্র্যাকার: স্ট্যাকওভারফ্লো এর উল্লিখিত লক্ষ্য হ'ল ভবিষ্যতের ব্যবহারকারীদের জন্য দরকারী উত্তরগুলির সন্ধানযোগ্য গ্রন্থাগার তৈরি করা। এবং এটি জিজ্ঞাসিত প্রশ্নের একটি উত্তর ... এটি ঠিক তাই ঘটে যে কিছু অবিরাম তথ্য রয়েছে যা এই উত্তরটি মূল পোস্টারের জন্য প্রয়োগ করে না। এটি এখনও একই প্রশ্ন সহ অন্যদের জন্য আবেদন করতে পারে।
বেন ভয়েগট

12
এটি প্রশ্নের শিরোনামের উত্তর হতে পারে , তবে প্রশ্ন নয়, না।
orlp

7
আমি যেমন বলেছি, এটি আকর্ষণীয় তথ্য। তবুও এটি এখনও আমার কাছে ভুল বলে মনে হচ্ছে যে প্রশ্নের শীর্ষের উত্তরের নোটা এখন যেমন দাঁড়িয়েছে প্রশ্নের উত্তর দেয় না । এটি কেবলমাত্র কারণটি নয় যে ইন্টেল সংকলক সিদ্ধান্ত নেবে না, বাস্তবে।
orlp

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

6

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

অপ্টিমাইজেশন যা করা হয়েছিল তা ছিল লুপ ইনগ্রেন্ট কোড গতি। কৌশলগুলির একটি সেট ব্যবহার করে এটি করা যেতে পারে।


4

ঠিক আছে, আমি অনুমান করেছি যে কিছু সংকলক এই ধরণের অপ্টিমাইজেশানটি করতে পারে, ধরেই নেওয়া যে আমরা ইন্টিজার অ্যারিমেটিক্সের বিষয়ে কথা বলছি।

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

.তিহাসিকভাবে, জিসিসি নিজেকে এমন একটি সংকলক হিসাবে দেখিয়েছে যা এরকম কঠোর পদক্ষেপ নিতে পারে, তবে অন্যান্য সংকলকরা ভাষার দ্বারা সংজ্ঞায়িত না হলেও এমনকি "ব্যবহারকারী-উদ্দেশ্যে" আচরণটি ধরে রাখতে পছন্দ করতে পারেন prefer


আমি দুর্ঘটনাক্রমে আমি অপরিজ্ঞাত আচরণের উপর নির্ভর করে কিনা তা জানতে আগ্রহী, তবে আমার ধারণা
সংকলকটি

2
@ জ্যাববোট: যদি ওভারফ্লো হয়, তবে সেখানে অপরিবর্তিত আচরণ রয়েছে। আচরণটি সংজ্ঞায়িত হয়েছে কিনা তা রানটাইম অবধি অজানা (ধরে নিলে সংখ্যাগুলি রানটাইমের সময় ইনপুট হয়): পি।
orlp

3

এটি এখন করে - কমপক্ষে, ঝনঝনানি করে :

long long add_100k_signed(int *data, int arraySize)
{
    long long sum = 0;

    for (int c = 0; c < arraySize; ++c)
        if (data[c] >= 128)
            for (int i = 0; i < 100000; ++i)
                sum += data[c];
    return sum;
}

-O1 থেকে কম্পাইল করে

add_100k_signed:                        # @add_100k_signed
        test    esi, esi
        jle     .LBB0_1
        mov     r9d, esi
        xor     r8d, r8d
        xor     esi, esi
        xor     eax, eax
.LBB0_4:                                # =>This Inner Loop Header: Depth=1
        movsxd  rdx, dword ptr [rdi + 4*rsi]
        imul    rcx, rdx, 100000
        cmp     rdx, 127
        cmovle  rcx, r8
        add     rax, rcx
        add     rsi, 1
        cmp     r9, rsi
        jne     .LBB0_4
        ret
.LBB0_1:
        xor     eax, eax
        ret

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

int add_100k_signed(int *data, int arraySize)
{
    int sum = 0;

    for (int c = 0; c < arraySize; ++c)
        if (data[c] >= 128)
            for (int i = 0; i < 100000; ++i)
                sum += data[c];
    return sum;
}

-O1 থেকে কম্পাইল করে

add_100k_signed:                        # @add_100k_signed
        test    esi, esi
        jle     .LBB0_1
        mov     r9d, esi
        xor     r8d, r8d
        xor     esi, esi
        xor     eax, eax
.LBB0_4:                                # =>This Inner Loop Header: Depth=1
        mov     edx, dword ptr [rdi + 4*rsi]
        imul    ecx, edx, 100000
        cmp     edx, 127
        cmovle  ecx, r8d
        add     eax, ecx
        add     rsi, 1
        cmp     r9, rsi
        jne     .LBB0_4
        ret
.LBB0_1:
        xor     eax, eax
        ret

2

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


3
বদ্ধ-ফর্ম গণনার সাথে একটি লুপ প্রতিস্থাপন করা শক্তি শক্তি হ্রাসও তাই না?
বেন ভয়েগট

সাধারণত, হ্যাঁ, আমি মনে করি, তবে আমি কখনও কাউকে সেভাবে কথা বলতে শুনিনি। (আমি, সাহিত্য উপর তারিখ সীমার বাইরে একটি বিট করছি যদিও।)
zwol

1

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

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