C99 'प्रतिबंधित' कीवर्ड का यथार्थवादी उपयोग?


182

मैं कुछ प्रलेखन और प्रश्नों / उत्तरों के माध्यम से ब्राउज़ कर रहा था और इसका उल्लेख किया था। मैंने एक संक्षिप्त विवरण पढ़ा, जिसमें कहा गया है कि यह मूल रूप से प्रोग्रामर से एक वादा होगा कि पॉइंटर का उपयोग कहीं और इंगित करने के लिए नहीं किया जाएगा।

किसी को भी कुछ यथार्थवादी मामलों की पेशकश कर सकते हैं जहां इसके लायक वास्तव में इस का उपयोग कर?


4
memcpyबनाम memmoveएक विहित उदाहरण है।
अलेक्जेंड्रे सी।

@AlexandreC।: मुझे नहीं लगता कि यह एक विशेष रूप से लागू है, क्योंकि "प्रतिबंधित" क्वालिफायर की कमी का मतलब यह नहीं है कि प्रोग्राम लॉजिक ओवरलोडिंग स्रोत और गंतव्य के साथ काम करेगा, और न ही इस तरह के क्वालीफायर की उपस्थिति से एक तथाकथित विधि को रोका जा सकेगा यह निर्धारित करते हुए कि स्रोत और गंतव्य ओवरलैप करते हैं या नहीं, यदि हां, तो src + (dest-src) के साथ भाग्य को प्रतिस्थापित करें, जो कि src से लिया गया है, इसे इसे उपनाम देने की अनुमति होगी।
सुपरकैट

@ सुपरकैट: इसीलिए मैंने इसे एक टिप्पणी के रूप में रखा। हालाँकि, 1) restrict-सुधार तर्क को memcpyसिद्धांत रूप में सक्षम करने के लिए एक भोली कार्यान्वयन को आक्रामक तरीके से अनुकूलित करने में सक्षम बनाता है, और 2) कॉलिंग केवल memcpyकंपाइलर को यह मानने में सक्षम बनाता है कि दिए गए तर्क उर्फ ​​नहीं हैं, जो memcpyकॉल के आसपास कुछ अनुकूलन की अनुमति दे सकता है ।
अलेक्जेंड्रे सी।

@AlexandreC: अधिकांश प्लेटफार्मों पर एक संकलक के लिए बहुत कठिन होगा कि वे एक भोले-भाले मेमॉपी को अनुकूलित करें - यहां तक ​​कि "प्रतिबंधित" के साथ - लक्ष्य के अनुरूप संस्करण के रूप में कहीं भी कुशल हो। कॉल-साइड ऑप्टिमाइज़ेशन के लिए "प्रतिबंधित" कीवर्ड की आवश्यकता नहीं होगी, और कुछ मामलों में यह सुविधा प्रदान करने के प्रयास काउंटर-प्रोडक्टिव हो सकते हैं। उदाहरण के लिए, मेमरी के कई कार्यान्वयन शून्य अतिरिक्त लागत पर, memcpy(anything, anything, 0);नो-ऑप के रूप में हो सकते हैं, और यह सुनिश्चित करते हैं कि अगर pकम से कम nलिखने योग्य बाइट्स के लिए एक संकेतक है memcpy(p,p,n); कोई प्रतिकूल प्रभाव नहीं होगा। इस तरह के मामले हो सकते हैं ...
सुपरकैट

... स्वाभाविक रूप से कुछ विशेष प्रकार के अनुप्रयोग कोड में (जैसे एक प्रकार की दिनचर्या स्वयं के साथ एक आइटम स्वैप करना), और कार्यान्वयन में जहां उनके कोई दुष्प्रभाव नहीं होते हैं, उन मामलों को सामान्य-केस कोड द्वारा नियंत्रित किया जाता है जो होने से अधिक कुशल हो सकते हैं विशेष-केस परीक्षणों को जोड़ने के लिए। दुर्भाग्य से, कुछ संकलक लेखकों को लगता है कि प्रोग्रामर को कोड जोड़ने के लिए यह आवश्यक है कि संकलक बाहर अनुकूलन करने में सक्षम न हो, इसलिए "अनुकूलन के अवसरों" की सुविधा के लिए जो संकलक वैसे भी बहुत कम ही शोषण कर पाएंगे।
सुपरकैट

जवाबों:


181

restrictकहता है कि सूचक एकमात्र ऐसी चीज है जो अंतर्निहित वस्तु तक पहुंचती है। यह कंपाइलर द्वारा बेहतर अनुकूलन को सक्षम करने, पॉइंटर एलियासिंग की क्षमता को समाप्त करता है।

उदाहरण के लिए, मान लें कि मेरे पास एक विशेष निर्देश वाली मशीन है जो मेमोरी में संख्याओं के वैक्टर को बढ़ा सकती है, और मेरे पास निम्नलिखित कोड हैं:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

संकलक की जरूरत है ठीक से संभाल करने के लिए यदि dest, src1, और src2ओवरलैप, जिसका अर्थ यह शुरू से ही समाप्त करने के लिए एक समय में एक गुणा करना चाहिए,। होने से restrict, कंपाइलर वेक्टर निर्देश का उपयोग करके इस कोड को अनुकूलित करने के लिए स्वतंत्र है।

विकिपीडिया पर एक restrictऔर उदाहरण के साथ एक प्रविष्टि है , यहाँ


3
@ मिचेल - अगर मैं गलत नहीं हूँ, तो समस्या केवल तब होगी जब destस्रोत वैक्टर में से किसी एक को ओवरलैप कर रहे हों। क्यों वहाँ एक समस्या है, तो हो सकता है src1और src2ओवरलैप?
ysap

1
प्रतिबंधित आम तौर पर केवल एक वस्तु की ओर इशारा करते हुए एक प्रभाव होता है जिसे संशोधित किया जाता है, जिस स्थिति में यह दावा करता है कि किसी छिपे हुए साइड इफेक्ट को ध्यान में नहीं रखा जाना चाहिए। अधिकांश संकलक इसका उपयोग वैश्वीकरण की सुविधा के लिए करते हैं। Msvc उस उद्देश्य के लिए डेटा ओवरलैप के लिए रन टाइम चेक का उपयोग करता है।
टिम

लूप वैरिएबल के लिए रजिस्टर कीवर्ड को जोड़ने से प्रतिबंधित जोड़ने के साथ-साथ यह तेज भी हो जाता है।

2
दरअसल, रजिस्टर कीवर्ड केवल एडवाइजरी है। और वर्ष 2000 के बाद से संकलक में, उदाहरण में i (और तुलना के लिए n) एक रजिस्टर में अनुकूलित किया जाएगा चाहे आप रजिस्टर कीवर्ड का उपयोग करें या नहीं।
मार्क फिशर

152

विकिपीडिया उदाहरण है बहुत रोशन।

यह स्पष्ट रूप से दिखाता है कि यह कैसे एक विधानसभा निर्देश को बचाने की अनुमति देता है

बिना प्रतिबंधित:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

छद्म विधानसभा:

load R1  *x    ; Load the value of x pointer
load R2  *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2  *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because a may be equal to x.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

प्रतिबंधित के साथ:

void fr(int *restrict a, int *restrict b, int *restrict x);

छद्म विधानसभा:

load R1  *x
load R2  *a
add R2 += R1
set R2  *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; load R1  *x
load R2  *b
add R2 += R1
set R2  *b

क्या जीसीसी वास्तव में ऐसा करता है?

GCC 4.8 लिनक्स x86-64:

gcc -g -std=c99 -O0 -c main.c
objdump -S main.o

के साथ -O0, वे समान हैं।

के साथ -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *restrict a, int *restrict b, int *restrict x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

बिन बुलाए के लिए, सम्मेलन बुला रहा है:

  • rdi = पहला पैरामीटर
  • rsi = दूसरा पैरामीटर
  • rdx = तीसरा पैरामीटर

जीसीसी आउटपुट विकी लेख से भी अधिक स्पष्ट था: 4 निर्देश बनाम 3 निर्देश।

Arrays

अब तक हमारे पास एकल निर्देश बचत है, लेकिन यदि सूचक सरणियों को लूप ओवर करने का प्रतिनिधित्व करता है, एक सामान्य उपयोग का मामला है, तो निर्देशों का एक गुच्छा बचाया जा सकता है, जैसा कि सुपरकैट द्वारा उल्लिखित है

उदाहरण के लिए विचार करें:

void f(char *restrict p1, char *restrict p2) {
    for (int i = 0; i < 50; i++) {
        p1[i] = 4;
        p2[i] = 9;
    }
}

के कारण restrict, एक स्मार्ट संकलक (या मानव), को अनुकूलित कर सकता है:

memset(p1, 4, 50);
memset(p2, 9, 50);

जो संभावित रूप से बहुत अधिक कुशल है क्योंकि यह एक सभ्य libc कार्यान्वयन (जैसे glibc) पर अनुकूलित किया जा सकता है: क्या प्रदर्शन के संदर्भ में std :: memcpy () या std :: copy () का उपयोग करना बेहतर है?

क्या जीसीसी वास्तव में ऐसा करता है?

GCC 5.2.1.लिन्क्स x86-64 उबंटू 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

साथ में -O0 , दोनों समान हैं।

के साथ -O3:

  • प्रतिबंध के साथ:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    memsetउम्मीद के मुताबिक दो कॉल।

  • बिना प्रतिबंधित: कोई stdlib कॉल, सिर्फ 16 पुनरावृत्ति वाइड लूप unrolling जो मैं यहाँ पुन: पेश करने का इरादा नहीं है :-)

मेरे पास उन्हें बेंचमार्क करने का धैर्य नहीं था, लेकिन मेरा मानना ​​है कि प्रतिबंधित संस्करण और तेज़ होगा।

C99

आइए संपूर्णता के लिए मानक देखें।

restrictकहते हैं कि दो पॉइंटर्स ओवरलैपिंग मेमोरी क्षेत्रों को इंगित नहीं कर सकते हैं। फ़ंक्शन तर्कों के लिए सबसे आम उपयोग है।

यह प्रतिबंधित करता है कि फ़ंक्शन को कैसे बुलाया जा सकता है, लेकिन अधिक संकलन-समय अनुकूलन के लिए अनुमति देता है।

यदि कॉलर restrictअनुबंध, अपरिभाषित व्यवहार का पालन नहीं करता है।

C99 N1256 मसौदा 6.7.3 / 7 "प्रकार क्वालिफायर" कहते हैं:

प्रतिबंधित क्वालीफायर का उपयोग (रजिस्टर स्टोरेज क्लास की तरह) अनुकूलन को बढ़ावा देने के लिए है, और एक अनुरूप कार्यक्रम की रचना करने वाले सभी प्रीप्रोसेसिंग अनुवाद इकाइयों से क्वालिफायर के सभी उदाहरणों को हटाने से इसका अर्थ (यानी, नमूदार व्यवहार) नहीं बदलता है।

और 6.7.3.1 "प्रतिबंधित की औपचारिक परिभाषा" गोर विवरण देती है।

सख्त अलियासिंग नियम

restrictकीवर्ड ही संगत प्रकार के संकेत दिए गए हैं (जैसे दो को प्रभावित करता है int*), क्योंकि सख्त अलियासिंग नियमों का कहना है कि असंगत प्रकार aliasing डिफ़ॉल्ट रूप से अपरिभाषित व्यवहार है, और इसलिए compilers मान सकते हैं यह हो और अनुकूलित नहीं करता दूर।

देखें: सख्त अलियासिंग नियम क्या है?

यह सभी देखें


9
"प्रतिबंधित" क्वालिफायर वास्तव में बहुत बड़ी बचत की अनुमति दे सकता है। उदाहरण के लिए, दिया गया void zap(char *restrict p1, char *restrict p2) { for (int i=0; i<50; i++) { p1[i] = 4; p2[i] = 9; } }, प्रतिबंधित क्वालिफायर कंपाइलर को कोड को "मेमसेट (p1,4,50); रिमसेट (P2,9,50);" प्रतिबंध प्रकार-आधारित एलियासिंग से काफी हद तक बेहतर है; यह एक शर्म की बात है कि उत्तरार्द्ध पर अधिक ध्यान केंद्रित करता है।
सुपरकैट

@supercat महान उदाहरण, जवाब में जोड़ा गया।
सिरो सेंटिल्ली 郝海东 冠状 iro i 法轮功 '

2
@ tim18: "प्रतिबंधित" कीवर्ड कई अनुकूलन कर सकता है जो कि आक्रामक प्रकार-आधारित अनुकूलन भी नहीं कर सकता है। इसके अलावा, भाषा में "प्रतिबंधित" का अस्तित्व - आक्रामक टाइप-आधारित अलियासिंग के विपरीत - कार्यों को पूरा करने के लिए कभी भी असंभव नहीं बनाता है क्योंकि उनकी अनुपस्थिति में संभव होगा (चूंकि कोड "प्रतिबंधित" से टूट जाएगा) इसका उपयोग न करें, जबकि कोड जो आक्रामक टीबीएए द्वारा तोड़ा जाता है, उसे अक्सर कम-कुशल फैशन में लिखा जाना चाहिए)।
सुपरकैट

2
@ tim18: चारों ओर की चीजें, जिनमें बैकटिक्स में डबल-अंडरलाइन्स शामिल हैं, जैसे __restrict। अन्यथा डबल-अंडरलाइन्स को एक संकेत के रूप में गलत समझा जा सकता है कि आप चिल्ला रहे हैं।
सुपरकैट

1
चिल्लाने से ज्यादा महत्वपूर्ण यह है कि अंडरस्कोर का सीधा मतलब उस बिंदु से प्रासंगिक है जिसे आप बनाने की कोशिश कर रहे हैं।
बजे
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.