الاستخدام الواقعي للكلمة الأساسية C99 "تقييد"؟
-
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*
) لأن قواعد التعرجات الصارمة تقول أن التعرجات غير متوافقة هي السلوك غير المحدد افتراضيا، وبالتالي يمكن للمجمعات التحويل البرمجيات أن تفترض أنها لا تحدث وتحسين بعيدا.
يرى: ما هي قاعدة التعرج الصارمة؟
أنظر أيضا
- C ++ 14 لم يكن لديك بعد التناظرية
restrict
, ، ولكن دول مجلس التعاون الخليجي لديها__restrict__
كملحق: ماذا تعني كلمة رئيسية تقييدها في C ++؟ - العديد من الأسئلة التي تسأل: وفقا لتفاصيل Gory، هل هذا الرمز UB أم لا؟
- سؤال "عند استخدامه": متى تستخدم تقييد وعندما لا
- خليك دول مجلس التعاون الخليجي
__attribute__((malloc))
, ، والتي تقول أن قيمة العودة وظيفة ليست مستعثية لأي شيء: دول مجلس التعاون الخليجي: __attribute __ ((malloc))