الاستخدام الواقعي للكلمة الأساسية C99 "تقييد"؟

StackOverflow https://stackoverflow.com/questions/745870

  •  09-09-2019
  •  | 
  •  

سؤال

كنت أتصفح من خلال بعض الوثائق والأسئلة / الإجابات ورأيتها المذكورة. قرأت وصفا موجزا، تفيد بأنه سيكون في الأساس وعد من المبرمج بأن المؤشر لن يتم استخدامه للإشارة إلى مكان آخر.

هل يمكن لأي شخص أن يقدم بعض الحالات الواقعية التي يستحقها في الواقع باستخدام هذا؟

هل كانت مفيدة؟

المحلول

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, ، مع مثال آخر، هنا.

نصائح أخرى

ال مثال ويكيبيديا يكون جدا الإضاءة.

يوضح بوضوح كيف يسمح بإنقاذ تعليمات التجميع واحدة.

دون تقييد:

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 Linux 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 تعليمات.

صفائف

حتى الآن لدينا وفورات تعليمية واحدة، ولكن إذا تمثل المؤشر صفائف ليتم حلقها، فهذا يمكن حفظ حالة الاستخدام المشتركة، ثم يمكن حفظ مجموعة من التعليمات، كما ذكرها القط الخارق.

النظر في مثال:

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 :: نسخة () من حيث الأداء؟

هل دول مجلس التعاون الخليجي تفعل ذلك حقا؟

دول مجلس التعاون الخليجي 5.2.1.linux 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 تكرار واسعة حلقة dearling. الذي لا أنوي إعادة التكاثر هنا :-)

لم يكن لدي الصبر لقياسها، لكنني أعتقد أن إصدار تقييد سيكون أسرع.

C99.

دعونا ننظر إلى المعيار للتأكد من اكتمالها.

restrict يقول أن مؤشرين لا يمكن أن يشير إلى تداخل مناطق الذاكرة. الاستخدام الأكثر شيوعا هو لحجج الوظيفة.

هذا يقيد كيفية استدعاء الوظيفة، ولكن يسمح بتحسين المزيد من التحسينات الزمنية.

إذا كان المتصل لا يتبع restrict العقد، السلوك غير محدد.

ال C99 N1256 مسودة 6.7.3 / 7 "نوع التصفيات" يقول:

الاستخدام المقصود للتأهل المقصود (مثل فئة تخزين التسجيل) هو تعزيز التحسين، وحذف جميع مثيلات التصفيات من جميع وحدات الترجمة المعالجة مسبقا، لا يغير برنامج مطابق لمعناه (أي سلوك يمكن ملاحظته).

و 6.7.3.1 "التعريف الرسمي لتقييد" يعطي تفاصيل Gory.

قاعدة التعرج الصارمة

ال restrict الكلمة الرئيسية تؤثر فقط على مؤشرات أنواع متوافقة (مثل اثنين int*) لأن قواعد التعرجات الصارمة تقول أن التعرجات غير متوافقة هي السلوك غير المحدد افتراضيا، وبالتالي يمكن للمجمعات التحويل البرمجيات أن تفترض أنها لا تحدث وتحسين بعيدا.

يرى: ما هي قاعدة التعرج الصارمة؟

أنظر أيضا

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top