मैंने डफ के उपकरण पर विकिपीडिया पर लेख पढ़ा है , और मुझे नहीं मिला। मुझे वास्तव में दिलचस्पी है, लेकिन मैंने कई बार स्पष्टीकरण पढ़ा है और मुझे अभी भी यह नहीं मिला है कि डफ का उपकरण कैसे काम करता है।
अधिक विस्तृत व्याख्या क्या होगी?
मैंने डफ के उपकरण पर विकिपीडिया पर लेख पढ़ा है , और मुझे नहीं मिला। मुझे वास्तव में दिलचस्पी है, लेकिन मैंने कई बार स्पष्टीकरण पढ़ा है और मुझे अभी भी यह नहीं मिला है कि डफ का उपकरण कैसे काम करता है।
अधिक विस्तृत व्याख्या क्या होगी?
जवाबों:
अन्यत्र कुछ अच्छी व्याख्याएँ हैं, लेकिन मैं इसे आजमाता हूँ। (यह एक व्हाइटबोर्ड पर बहुत आसान है!) यहां कुछ नोटेशन के साथ विकिपीडिया उदाहरण दिया गया है।
मान लीजिए कि आप 20 बाइट्स कॉपी कर रहे हैं। पहले पास के लिए कार्यक्रम का प्रवाह नियंत्रण है:
int count; // Set to 20
{
int n = (count + 7) / 8; // n is now 3. (The "while" is going
// to be run three times.)
switch (count % 8) { // The remainder is 4 (20 modulo 8) so
// jump to the case 4
case 0: // [skipped]
do { // [skipped]
*to = *from++; // [skipped]
case 7: *to = *from++; // [skipped]
case 6: *to = *from++; // [skipped]
case 5: *to = *from++; // [skipped]
case 4: *to = *from++; // Start here. Copy 1 byte (total 1)
case 3: *to = *from++; // Copy 1 byte (total 2)
case 2: *to = *from++; // Copy 1 byte (total 3)
case 1: *to = *from++; // Copy 1 byte (total 4)
} while (--n > 0); // N = 3 Reduce N by 1, then jump up
// to the "do" if it's still
} // greater than 0 (and it is)
}
अब, दूसरा पास शुरू करें, हम केवल संकेतित कोड चलाते हैं:
int count; //
{
int n = (count + 7) / 8; //
//
switch (count % 8) { //
//
case 0: //
do { // The while jumps to here.
*to = *from++; // Copy 1 byte (total 5)
case 7: *to = *from++; // Copy 1 byte (total 6)
case 6: *to = *from++; // Copy 1 byte (total 7)
case 5: *to = *from++; // Copy 1 byte (total 8)
case 4: *to = *from++; // Copy 1 byte (total 9)
case 3: *to = *from++; // Copy 1 byte (total 10)
case 2: *to = *from++; // Copy 1 byte (total 11)
case 1: *to = *from++; // Copy 1 byte (total 12)
} while (--n > 0); // N = 2 Reduce N by 1, then jump up
// to the "do" if it's still
} // greater than 0 (and it is)
}
अब, तीसरा पास शुरू करें:
int count; //
{
int n = (count + 7) / 8; //
//
switch (count % 8) { //
//
case 0: //
do { // The while jumps to here.
*to = *from++; // Copy 1 byte (total 13)
case 7: *to = *from++; // Copy 1 byte (total 14)
case 6: *to = *from++; // Copy 1 byte (total 15)
case 5: *to = *from++; // Copy 1 byte (total 16)
case 4: *to = *from++; // Copy 1 byte (total 17)
case 3: *to = *from++; // Copy 1 byte (total 18)
case 2: *to = *from++; // Copy 1 byte (total 19)
case 1: *to = *from++; // Copy 1 byte (total 20)
} while (--n > 0); // N = 1 Reduce N by 1, then jump up
// to the "do" if it's still
} // greater than 0 (and it's not, so bail)
} // continue here...
20 बाइट्स अब कॉपी की जाती हैं।
नोट: मूल डफ डिवाइस (ऊपर दिखाया गया है) to
पते पर एक I / O डिवाइस की नकल की गई है । इस प्रकार, सूचक को बढ़ाना आवश्यक नहीं था *to
। दो मेमोरी बफ़र्स के बीच कॉपी करते समय आपको उपयोग करना होगा *to++
।
do
इतने पर मत देखो । इसके बजाय, एक ऑफसेट के साथ switch
और while
पुराने जमाने की गणना की गई GOTO
मूर्तियों या कोडांतरक jmp
कथनों को देखें। switch
कुछ गणित करता है और फिर jmp
सही जगह पर है। while
एक बूलियन की जांच करता है और फिर आँख बंद करके jmp
के बारे में जहां सही करने के लिए है do
था।
डॉ डॉब के जर्नल में स्पष्टीकरण के लिए सबसे अच्छा है कि मैं विषय पर पाया जाता है।
यह मेरा अहम क्षण है:
for (i = 0; i < len; ++i) {
HAL_IO_PORT = *pSource++;
}
हो जाता है:
int n = len / 8;
for (i = 0; i < n; ++i) {
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
HAL_IO_PORT = *pSource++;
}
n = len % 8;
for (i = 0; i < n; ++i) {
HAL_IO_PORT = *pSource++;
}
हो जाता है:
int n = (len + 8 - 1) / 8;
switch (len % 8) {
case 0: do { HAL_IO_PORT = *pSource++;
case 7: HAL_IO_PORT = *pSource++;
case 6: HAL_IO_PORT = *pSource++;
case 5: HAL_IO_PORT = *pSource++;
case 4: HAL_IO_PORT = *pSource++;
case 3: HAL_IO_PORT = *pSource++;
case 2: HAL_IO_PORT = *pSource++;
case 1: HAL_IO_PORT = *pSource++;
} while (--n > 0);
}
len%8
4 था, यह केस 4, केस 2, केस 2 और केस 1 को निष्पादित करेगा, और फिर वापस कूद जाएगा और अगले लूप से सभी मामलों को निष्पादित करेगा। यह वह हिस्सा है जिसे व्याख्या की आवश्यकता है, जिस तरह से लूप और स्विच स्टेटमेंट "इंटरैक्ट"।
len % 8
बाइट्स की प्रतिलिपि नहीं बनाई जाएगी?
डफ के उपकरण में दो प्रमुख बातें हैं। सबसे पहले, मुझे संदेह है कि समझने में आसान हिस्सा है, लूप अनियंत्रित है। यह लूप के समाप्त होने और लूप के शीर्ष पर वापस कूदने की जाँच में शामिल कुछ ओवरहेड से बचने के लिए अधिक गति के लिए बड़े कोड आकार को ट्रेड करता है। सीपीयू तेजी से चल सकता है जब यह कूदने के बजाय सीधी-रेखा कोड निष्पादित कर रहा है।
दूसरा पहलू स्विच स्टेटमेंट है। यह पहली बार कोड को लूप के बीच में कूदने की अनुमति देता है । ज्यादातर लोगों के लिए आश्चर्य की बात यह है कि ऐसी चीज की अनुमति है। खैर, इसकी अनुमति है। निष्पादन की गणना की गई केस लेबल पर शुरू होती है, और फिर यह किसी भी अन्य स्विच स्टेटमेंट की तरह, प्रत्येक क्रमिक असाइनमेंट स्टेटमेंट के माध्यम से गिरती है । अंतिम केस लेबल के बाद, निष्पादन लूप के निचले हिस्से तक पहुंच जाता है, जिस बिंदु पर यह शीर्ष पर वापस कूदता है। लूप का शीर्ष स्विच स्टेटमेंट के अंदर है, इसलिए स्विच का पुनर्मूल्यांकन नहीं किया गया है।
मूल लूप आठ बार खोलना है, इसलिए पुनरावृत्तियों की संख्या आठ से विभाजित होती है। यदि कॉपी किए जाने वाले बाइट्स की संख्या आठ से अधिक नहीं है, तो कुछ बाइट्स बचे हैं। अधिकांश एल्गोरिदम जो एक समय में बाइट्स के ब्लॉक की नकल करते हैं, बचे हुए बाइट्स को अंत में संभाल लेंगे, लेकिन डफ का डिवाइस शुरुआत में उन्हें संभालता है। फ़ंक्शन count % 8
स्विच स्टेटमेंट के लिए गणना करता है कि शेष क्या होगा, यह जानने के लिए कि कई बाइट्स के लिए केस लेबल पर कूदता है, और उन्हें कॉपी करता है। फिर लूप आठ बाइट्स के समूहों को कॉपी करना जारी रखता है।
डफ डिवाइस का बिंदु एक तंग मेम्ची कार्यान्वयन में की गई तुलनाओं की संख्या को कम करना है।
मान लीजिए कि आप 'गिनती' बाइट्स को a से b तक कॉपी करना चाहते हैं, तो सीधे फॉरवर्ड अप्रोच को निम्नलिखित करना है:
do {
*a = *b++;
} while (--count > 0);
यह 0 से ऊपर है या नहीं यह देखने के लिए आपको कितनी बार गिनती की तुलना करने की आवश्यकता है? 'गिनती' बार।
अब, डफ डिवाइस एक स्विच केस के खराब अनजाने साइड इफेक्ट का उपयोग करता है जो आपको गिनती / 8 की आवश्यकता की तुलना की संख्या को कम करने की अनुमति देता है।
अब मान लीजिए कि आप डफ डिवाइस का उपयोग करके 20 बाइट्स कॉपी करना चाहते हैं, तो आपको कितनी तुलनाओं की आवश्यकता होगी? केवल 3, चूँकि आप एक बार में आठ बाइट्स कॉपी करते हैं, सिवाय अंतिम पहले वाले के जहाँ आप 4 कॉपी करते हैं।
अद्यतन: आपको 8 तुलना / केस-इन-स्विच बयान करने की ज़रूरत नहीं है, लेकिन यह फ़ंक्शन आकार और गति के बीच एक व्यापार-बंद है।
जब मैंने इसे पहली बार पढ़ा, तो मैंने इसे इस पर ऑटोफ़ॉर्म किया
void dsend(char* to, char* from, count) {
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do {
*to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
और मुझे पता नहीं था कि क्या हो रहा है।
शायद नहीं जब यह सवाल पूछा गया था, लेकिन अब विकिपीडिया की बहुत अच्छी व्याख्या है
यह उपकरण वैध है, C में दो विशेषताओं के आधार पर कानूनी C:
- भाषा की परिभाषा में स्विच स्टेटमेंट के विशिष्ट विनिर्देश। डिवाइस के आविष्कार के समय यह द सी प्रोग्रामिंग लैंग्वेज का पहला संस्करण था जिसके लिए केवल यह आवश्यक है कि स्विच का नियंत्रित स्टेटमेंट एक सिंटैक्टिकली वैलिड (कंपाउंड) स्टेटमेंट हो, जिसके भीतर केस लेबल किसी भी सब-स्टेटमेंट को प्रीफ़िक्स कर सकते हैं। इस तथ्य के साथ संयोजन में कि, एक ब्रेक स्टेटमेंट की अनुपस्थिति में, नियंत्रण का प्रवाह गिर जाएगा-एक स्टेटमेंट लेबल द्वारा नियंत्रित स्टेटमेंट से अगले द्वारा नियंत्रित किया जाता है, इसका मतलब है कि कोड से गिनती प्रतियों का एक उत्तराधिकार निर्दिष्ट करता है मेमोरी-मैपेड आउटपुट पोर्ट के क्रमिक स्रोत पते।
- सी में एक लूप के बीच में कानूनी रूप से कूदने की क्षमता।
1: डफ डिवाइस लूप के अनियंत्रित होने का एक विशेष निहितार्थ है। लूप क्या है?
यदि आपके पास एक लूप में N बार प्रदर्शन करने के लिए एक ऑपरेशन है, तो आप लूप N / n बार निष्पादित करके गति के लिए प्रोग्राम आकार का व्यापार कर सकते हैं और फिर लूप को inlining (अनरोलिंग) में लूप कोड n बार बदल सकते हैं:
for (int i=0; i<N; i++) {
// [The loop code...]
}
साथ में
for (int i=0; i<N/n; i++) {
// [The loop code...]
// [The loop code...]
// [The loop code...]
...
// [The loop code...] // n times!
}
यदि N% n == 0 - डफ की कोई आवश्यकता नहीं है, तो यह बहुत अच्छा है! अगर यह सच नहीं है, तो आपको शेष को संभालना होगा - जो एक दर्द है।
2: डफ डिवाइस इस मानक लूप से अलग कैसे होता है?
डफ्स डिवाइस शेष लूप चक्रों से निपटने का एक चतुर तरीका है जब N% n! = 0. पूरा करते हैं / जबकि मानक लूप के अनुसार एन / एन कई बार निष्पादित होता है (क्योंकि मामला 0 लागू होता है)। लूप के माध्यम से अंतिम रन पर ('एन / एन + 1'वीं बार) केस में किक होती है और हम एन% एन केस में कूद जाते हैं और लूप कोड को' शेष 'संख्या को चलाते हैं।
हालांकि मैं 100% निश्चित नहीं हूं कि आप यहां क्या पूछ रहे हैं, यहां ...
यह समस्या कि डफ के उपकरण पते लूप अनइंडिंग में से एक है (जैसा कि आपको कोई संदेह नहीं है कि आपके द्वारा पोस्ट किए गए विकी लिंक पर देखा होगा)। यह जो मूल रूप से समान है, रन-टाइम दक्षता का अनुकूलन है, मेमोरी फुटप्रिंट पर। डफ का उपकरण किसी भी पुरानी समस्या के बजाय धारावाहिक नकल से संबंधित है, लेकिन एक उत्कृष्ट उदाहरण है कि एक लूप में किए जाने वाले समय की तुलना को कम करके कितने अनुकूलन किए जा सकते हैं।
एक वैकल्पिक उदाहरण के रूप में, जिसे समझना आसान हो सकता है, कल्पना कीजिए कि आपके पास एक आइटम है, जिसे आप लूप ओवर करना चाहते हैं, और हर बार उनमें से 1 जोड़ें ... आमतौर पर, आप लूप के लिए उपयोग कर सकते हैं, और लगभग 100 बार लूप कर सकते हैं। । यह काफी तार्किक लगता है और, यह है ... हालांकि, लूप को खोलकर एक अनुकूलन किया जा सकता है (जाहिर है बहुत दूर नहीं ... या आप बस लूप का उपयोग नहीं कर सकते हैं)।
तो पाश के लिए एक नियमित:
for(int i = 0; i < 100; i++)
{
myArray[i] += 1;
}
हो जाता है
for(int i = 0; i < 100; i+10)
{
myArray[i] += 1;
myArray[i+1] += 1;
myArray[i+2] += 1;
myArray[i+3] += 1;
myArray[i+4] += 1;
myArray[i+5] += 1;
myArray[i+6] += 1;
myArray[i+7] += 1;
myArray[i+8] += 1;
myArray[i+9] += 1;
}
डफ का उपकरण क्या करता है, इस विचार को C में लागू किया जाता है, लेकिन (जैसा कि आपने विकी पर देखा था) धारावाहिक प्रतियों के साथ। जो आप ऊपर देख रहे हैं, वह अनजाने उदाहरण के साथ, मूल की 100 की तुलना में 10 तुलना है - यह एक मामूली, लेकिन संभवतः महत्वपूर्ण, अनुकूलन की मात्रा है।
यहाँ एक गैर-विस्तृत विवरण दिया गया है जो मुझे लगता है कि डफ के उपकरण का क्रुक्स है:
बात यह है, सी मूल रूप से असेंबली लैंग्वेज (पीडीपी -7 असेंबली विशिष्ट होने के लिए एक अच्छा मुखौटा है; यदि आपने अध्ययन किया कि आप देखेंगे कि समानताएं कितनी आकर्षक हैं)। और, असेंबली लैंग्वेज में, आपके पास वास्तव में लूप नहीं हैं - आपके पास लेबल और सशर्त-शाखा निर्देश हैं। तो लूप एक लेबल और एक शाखा के साथ निर्देशों के समग्र अनुक्रम का एक हिस्सा है:
instruction
label1: instruction
instruction
instruction
instruction
jump to label1 some condition
और स्विच निर्देश कुछ हद तक आगे बढ़ रहा है:
evaluate expression into register r
compare r with first case value
branch to first case label if equal
compare r with second case value
branch to second case label if equal
etc....
first_case_label:
instruction
instruction
second_case_label:
instruction
instruction
etc...
असेंबली में यह आसानी से समझ में आता है कि इन दो नियंत्रण संरचनाओं को कैसे संयोजित किया जाए, और जब आप इस तरह से सोचते हैं, तो सी में उनका संयोजन अब इतना अजीब नहीं लगता है।
यह एक उत्तर है जिसे मैंने डफ के डिवाइस के बारे में एक और प्रश्न के लिए पोस्ट किया था, जो प्रश्न के एक डुप्लिकेट के रूप में बंद होने से पहले कुछ अपवाहों को मिला था। मुझे लगता है कि यह यहाँ कुछ मूल्यवान संदर्भ प्रदान करता है कि आपको इस निर्माण से क्यों बचना चाहिए।
"यह डफ डिवाइस है । यह लूप्स को अनरोल करने का एक तरीका है, जो कई बार तय करने के लिए एक सेकेंडरी फिक्स-अप लूप जोड़ने से बचता है, जब लूप इरीटेशन की संख्या अनियंत्रित कारक का एक सटीक मल्टीपल होना नहीं जानती है।
चूँकि यहाँ अधिकांश उत्तर आम तौर पर सकारात्मक लगते हैं, इसलिए मैं इसे नीचे की ओर उजागर करने जा रहा हूँ।
इस कोड के साथ एक कंपाइलर लूप बॉडी के लिए किसी भी अनुकूलन को लागू करने के लिए संघर्ष करने वाला है। यदि आपने सिर्फ एक सरल लूप के रूप में कोड लिखा है तो एक आधुनिक संकलक आपके लिए अनरोलिंग को संभालने में सक्षम होना चाहिए। इस तरह से आप पठनीयता और प्रदर्शन बनाए रखते हैं और लूप बॉडी पर लागू किए जा रहे अन्य अनुकूलन के कुछ आशा रखते हैं।
दूसरों द्वारा संदर्भित विकिपीडिया लेख यहां तक कहता है कि जब इस 'पैटर्न' को Xfree86 स्रोत कोड प्रदर्शन से हटा दिया गया था तो वास्तव में सुधार हुआ था।
यह परिणाम आँख बंद करके किसी भी कोड का अनुकूलन करने के लिए विशिष्ट है जो आपको लगता है कि इसकी आवश्यकता हो सकती है। यह कंपाइलर को अपना काम ठीक से करने से रोकता है, आपके कोड को कम पठनीय बनाता है और बग्स को अधिक प्रभावित करता है और आमतौर पर इसे धीमा कर देता है। यदि आप चीजों को सही तरीके से कर रहे थे, यानी सरल कोड लिखना, तो बाधाओं के लिए प्रोफाइलिंग करना, फिर अनुकूलन करना, आपने कभी ऐसा कुछ उपयोग करने के बारे में सोचा भी नहीं होगा। आधुनिक सीपीयू और कंपाइलर के साथ वैसे भी नहीं।
इसे समझना ठीक है, लेकिन मुझे आश्चर्य होगा कि अगर आप वास्तव में इसका इस्तेमाल करते हैं। "
बस प्रयोग करते हुए, एक और वैरिएंट मिल रहा है जो इंटरलेविंग स्विच और लूप के बिना हो रहा है:
int n = (count + 1) / 8;
switch (count % 8)
{
LOOP:
case 0:
if(n-- == 0)
break;
putchar('.');
case 7:
putchar('.');
case 6:
putchar('.');
case 5:
putchar('.');
case 4:
putchar('.');
case 3:
putchar('.');
case 2:
putchar('.');
case 1:
putchar('.');
default:
goto LOOP;
}