C ++ में, क्या मुझे चर को कैश करना चाहिए, या कंपाइलर को ऑप्टिमाइज़ेशन करने देना चाहिए? (एलियासिंग)


114

निम्नलिखित कोड पर विचार करें ( pप्रकार unsigned char*का bitmap->widthहै और कुछ पूर्णांक प्रकार का है, जो कि अज्ञात है और कुछ बाहरी लाइब्रेरी के किस संस्करण का उपयोग कर रहा है) पर निर्भर करता है:

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

क्या यह इसे अनुकूलित करने लायक है [..]

क्या कोई ऐसा मामला हो सकता है जहाँ यह लिखकर अधिक कुशल परिणाम दे सके:

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

... या कंपाइलर को ऑप्टिमाइज़ करने के लिए यह तुच्छ है?

आप "बेहतर" कोड को क्या मानते हैं?

संपादक से ध्यान दें (Ike): स्ट्राइक टेक्स्ट के बारे में सोच रहे लोगों के लिए, मूल प्रश्न, जिसे फॉन्टेड किया गया था, खतरनाक रूप से ऑफ-टॉपिक क्षेत्र के करीब था और सकारात्मक प्रतिक्रिया के बावजूद बंद होने के बहुत करीब था। इन्हें बाहर निकाला गया है। फिर भी कृपया उन उत्तरदाताओं को दंडित न करें जिन्होंने प्रश्न के इन विकट वर्गों को संबोधित किया है।


19
यदि *pएक ही प्रकार का है, widthतो यह अनुकूलन करने के लिए तुच्छ नहीं है, क्योंकि यह लूप के अंदर pइंगित widthऔर संशोधित कर सकता है ।
इमली

31
यह पूछने के बारे में कि क्या संकलक किसी विशेष ऑपरेशन का अनुकूलन करता है, आमतौर पर गलत प्रश्न है। आप (आमतौर पर) आखिरकार किस चीज में रुचि रखते हैं, कौन सा संस्करण तेजी से चलता है, जिसे आपको बस मापना चाहिए।
सरगुजि

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

19
यदि मैं एक संकलक होता, तो मैं देखता कि आपके दो उदाहरण समान नहीं हैं। यह संभव है कि pएक ही स्मृति के रूप में इंगित करता है bitmap->width। इसलिए मैं कानूनी तौर पर पहले उदाहरण को दूसरे के लिए अनुकूलित नहीं कर सकता।
रहस्यपूर्ण

4
"पी" कहाँ संग्रहीत किया जाता है? मेरा सुझाव है कि आपको "char * प्रतिबंधित P2 = p;" और फिर अपने पाश के भीतर "पी" के बजाय "पी 2" का उपयोग करना। फिर, यदि आप चाहते हैं कि "p" में परिवर्तन वापस p पर लागू हो, तो "p + = (P2-p)" का उपयोग करें। ध्यान दें कि कोई भी पॉइंटर नहीं लिखा है जो कि पॉइंटर द्वारा कॉपी किए गए फॉर्म पी 2 से नहीं है, पी 2 से कॉपी किए गए पॉइंटर का उपयोग करके पढ़ा जा सकता है, न ही इसके विपरीत, और पी 2 के जीवनकाल के बाद किसी भी उद्देश्य के लिए किसी भी कॉपी का उपयोग नहीं किया जा सकता है, लेकिन एक कंपाइलर उन का उपयोग कर सकता है अनुकूलन को सक्षम करने के लिए तथ्य जो किसी अन्य माध्यम से पूरा नहीं किया जा सकता है।
सुपरकैट

जवाबों:


81

पहली नज़र में, मुझे लगा कि कंपाइलर दोनों संस्करणों के लिए समान असेंबली उत्पन्न कर सकता है जिसमें ऑप्टिमाइज़ेशन फ़्लैग सक्रिय हो। जब मैंने इसकी जाँच की, तो परिणाम देखकर मैं हैरान रह गया:

स्रोत unoptimized.cpp

नोट: यह कोड निष्पादित होने के लिए नहीं है।

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    for (unsigned x = 0 ; x < static_cast<unsigned>(bitmap.width) ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

स्रोत optimized.cpp

नोट: यह कोड निष्पादित होने के लिए नहीं है।

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    const unsigned width = static_cast<unsigned>(bitmap.width);
    for (unsigned x = 0 ; x < width ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

संकलन

  • $ g++ -s -O3 unoptimized.cpp
  • $ g++ -s -O3 optimized.cpp

विधानसभा (अडॉप्टिमाइज्ड। एस)

    .file   "unoptimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    mov %eax, %edx
    addl    $1, %eax
    movq    (%rsi,%rdx,8), %rdx
    movb    $0, (%rdx)
    cmpl    bitmap(%rip), %eax
    jb  .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

विधानसभा (अनुकूलित)

    .file   "optimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    subl    $1, %eax
    leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    movq    (%rsi,%rax), %rdx
    addq    $8, %rax
    cmpq    %rcx, %rax
    movb    $0, (%rdx)
    jne .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

diff

$ diff -uN unoptimized.s optimized.s
--- unoptimized.s   2015-11-24 16:11:55.837922223 +0000
+++ optimized.s 2015-11-24 16:12:02.628922941 +0000
@@ -1,4 +1,4 @@
-   .file   "unoptimized.cpp"
+   .file   "optimized.cpp"
    .text
    .p2align 4,,15
 .globl main
@@ -10,16 +10,17 @@
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
+   subl    $1, %eax
+   leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
 .L3:
-   mov %eax, %edx
-   addl    $1, %eax
-   movq    (%rsi,%rdx,8), %rdx
+   movq    (%rsi,%rax), %rdx
+   addq    $8, %rax
+   cmpq    %rcx, %rax
    movb    $0, (%rdx)
-   cmpl    bitmap(%rip), %eax
-   jb  .L3
+   jne .L3
 .L2:
    xorl    %eax, %eax
    ret

वास्तव में (लोड करता है अनुकूलित संस्करण के लिए विधानसभा उत्पन्न lea) widthनिरंतर जो गणना करता unoptimized संस्करण के विपरीत width(प्रत्येक यात्रा पर ऑफसेट movq)।

जब मुझे समय मिलेगा, मैं अंततः उस पर कुछ बेंचमार्क पोस्ट करूंगा। अच्छा प्रश्न।


3
यह देखने के लिए कि कोड को अलग ढंग से उत्पन्न की गई है, तो आप कास्ट दिलचस्प होगा const unsignedबजाय सिर्फ unsignedunoptimized मामले में।
मार्क रैनसम

2
@ मर्कांसोम का मानना ​​है कि मुझे इससे कोई फ़र्क नहीं पड़ना चाहिए: कॉन्स्टेबल होने का "वादा" केवल एक ही तुलना के दौरान है, पूरे लूप के लिए नहीं
हेगन वॉन एटिज़ेन

13
कृपया कभी फ़ंक्शन का उपयोग mainएक अनुकूलन के लिए परीक्षण करने के लिए। Gcc उद्देश्यपूर्ण रूप से ठंड के रूप में चिह्नित करता है और इस प्रकार इसके लिए कुछ अनुकूलन अक्षम करता है। मुझे नहीं पता कि अगर यह मामला है, लेकिन यह एक महत्वपूर्ण आदत है।
मार्क ग्लिससे

3
@MarcGlisse आप 100% सही हैं। मैंने इसे जल्दी में लिखा है, मैं इसमें सुधार करूंगा।
YSC

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

38

आपके कोड स्निपेट से वास्तव में अपर्याप्त जानकारी है जो बताने में सक्षम है, और एक चीज जो मैं सोच सकता हूं वह है अलियासिंग। हमारे दृष्टिकोण से, यह बहुत स्पष्ट है कि आप नहीं चाहते हैं pऔर bitmapस्मृति में एक ही स्थान पर इंगित करते हैं, लेकिन संकलक को यह पता नहीं है और (क्योंकि pप्रकार का है char*) संकलक को इस कोड को काम करना पड़ता है, भले ही pऔर bitmapओवरलैप करें।

इस मामले में इसका मतलब यह है कि यदि लूप bitmap->widthपॉइंटर के माध्यम से बदलता है pतो bitmap->widthबाद में री-रीडिंग करते समय इसे देखना होगा, जिसका अर्थ है कि स्थानीय चर में इसे संग्रहीत करना अवैध होगा।

यह कहा जा रहा है, मेरा मानना ​​है कि कुछ कंपाइलर वास्तव में कभी-कभी एक ही कोड के दो संस्करण उत्पन्न करेंगे (मैंने इस बारे में परिस्थितिजन्य साक्ष्य देखे हैं, लेकिन इस मामले में कंपाइलर क्या कर रहा है, इस बारे में सीधे तौर पर कभी भी जानकारी नहीं मांगी है) और जल्दी से जाँच करें कि क्या पॉइंटर्स उपनाम और तेज़ कोड चलाएं यदि यह निर्धारित करता है कि यह ठीक है।

कहा जा रहा है, मैं केवल दो संस्करणों के प्रदर्शन को मापने के बारे में अपनी टिप्पणी के साथ खड़ा हूं, मेरा पैसा कोड के दो संस्करणों के बीच कोई निरंतर प्रदर्शन अंतर नहीं देख रहा है।

मेरी राय में, इन प्रश्नों को ठीक है यदि आपका उद्देश्य संकलक अनुकूलन सिद्धांतों और तकनीकों के बारे में सीखना है, लेकिन समय की बर्बादी है (एक बेकार माइक्रो-अनुकूलन) यदि आपका अंतिम लक्ष्य यहां कार्यक्रम को तेजी से चलाना है।


1
@GuyGreer: यह एक प्रमुख अनुकूलन-अवरोधक है; मैं इसे दुर्भाग्यपूर्ण मानता हूं कि भाषा के नियम प्रभावी प्रकारों के बारे में नियमों पर ध्यान केंद्रित करते हैं, न कि उन परिस्थितियों की पहचान करने के लिए जहां विभिन्न वस्तुओं के लिखने और पढ़ने के तरीके हैं या अप्रकाशित नहीं हैं। ऐसे टर्म में लिखे गए नियम कंपाइलर और प्रोग्रामर की जरूरतों को पूरा करने में बेहतर काम कर सकते हैं।
सुपरकैट

3
@GuyGreer - restrictक्वालीफायर इस मामले में अलियासिंग समस्या का जवाब नहीं होगा ?
एलटीहोड 20

4
मेरे अनुभव में, restrictकाफी हद तक हिट-एंड-मिस है। MSVC एकमात्र कंपाइलर है जिसे मैंने देखा है कि यह ठीक से करता है। आईसीसी फ़ंक्शन कॉल के माध्यम से जानकारी को खो देता है, भले ही वे अंतर्निर्मित हों। और जीसीसी आमतौर पर कोई भी लाभ प्राप्त करने में विफल रहता है जब तक कि आप प्रत्येक एकल इनपुट पैरामीटर को restrict( thisसदस्य कार्यों के लिए) घोषित नहीं करते हैं ।
22

1
@ मिस्टिक: एक बात याद रखें कि charउपनाम सभी प्रकार का होता है, इसलिए यदि आपके पास एक चार * है तो आपको restrictहर चीज का उपयोग करना होगा। या यदि आपने जीसीसी के सख्त अलियासिंग नियमों को बंद करने के लिए मजबूर किया है -fno-strict-aliasingतो सब कुछ एक संभावित उपनाम माना जाता है।
ज़ैन लिंक्स

1
@R restrictC ++ में समान शब्दार्थों के लिए सबसे हालिया प्रस्ताव N4150 है
टीसी

24

ठीक है, दोस्तों, इसलिए मैंने GCC -O3(लिनक्स x64 पर जीसीसी 4.9 का उपयोग करके) मापा है ।

बाहर मुड़ता है, दूसरा संस्करण 54% तेजी से चलता है!

इसलिए, मुझे लगता है कि एलियासिंग वह चीज है, जिसके बारे में मैंने सोचा नहीं था।

[संपादित करें]

मैंने फिर से पहले संस्करण की कोशिश की है जिसमें सभी बिंदुओं के साथ परिभाषित किया गया है __restrict__, और परिणाम समान हैं। अजीब .. या तो एलियासिंग समस्या नहीं है, या, किसी कारण से, कंपाइलर इसे अच्छी तरह से अनुकूलित नहीं करता है __restrict__

[संपादित करें 2]

ठीक है, मुझे लगता है कि मैं बहुत साबित करने में सक्षम था कि अलियासिंग समस्या है। मैंने अपना मूल परीक्षण दोहराया, इस बार एक सूचक के बजाय एक सरणी का उपयोग करते हुए:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

और मापा (इसे लिंक करने के लिए "-mcmodel = बड़े" का उपयोग करना पड़ा)। फिर मैंने कोशिश की:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

माप के परिणाम समान थे - ऐसा लगता है जैसे संकलक इसे स्वयं द्वारा अनुकूलित करने में सक्षम था।

फिर मैंने मूल कोड (एक सूचक के साथ p) की कोशिश की , इस बार जब pप्रकार का है std::uint16_t*। फिर से, परिणाम समान थे - सख्त अलियासिंग के कारण। फिर मैंने "-फेनो-सख्त-अलियासिंग" के साथ निर्माण की कोशिश की, और फिर से समय में अंतर देखा।


4
ऐसा लगता है कि यह एक टिप्पणी होनी चाहिए, हालांकि यह तकनीकी रूप से सवाल का जवाब देता है। ध्यान दें, दुर्भाग्यवश आपने यह प्रदर्शित नहीं किया कि एलियासिंग बात थी। यह संभावना है, निश्चित रूप से प्रशंसनीय है, लेकिन यह निष्कर्ष निकालने से अलग है कि यह वह था।
सरगुई

@GuyGreer: मेरे [2 संपादित करें] देखें - अब मुझे लगता है कि यह बहुत सिद्ध है।
यारोन कोहेन-ताल

2
मुझे आश्चर्य है कि आपने अपने लूप में "x" होने पर "i" का उपयोग क्यों शुरू कर दिया?
जेसपर मैडसेन

1
क्या यह सिर्फ मुझे पता है कि वाक्यांश 54% तेजी से समझना मुश्किल है? क्या इसका मतलब यह है कि यह अडॉप्ट की गई गति का 1.54 गुना है, या कुछ और है?
रोडी

3
@ YaronCohen-Tal इतनी तेजी से दो बार? प्रभावशाली, लेकिन ऐसा नहीं है जिसे मैंने "54% तेज" समझा है!
रोडी

24

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

उन्होंने यह भी बताया है कि लूप से ऑपरेशन को बाहर निकालना आमतौर पर प्रदर्शन के दृष्टिकोण से सुधार नहीं होता है और यह पठनीयता के दृष्टिकोण से अक्सर नकारात्मक होता है।

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

for (unsigned x = static_cast<unsigned>(bitmap->width);x > 0;  x--)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

ध्यान दें कि लूप का यह संस्करण रेंज 0 में एक्स वैल्यू देता है। रेंज 0 के बजाय। (चौड़ाई -1)। यह आपके मामले में कोई फर्क नहीं पड़ता क्योंकि आप वास्तव में किसी भी चीज़ के लिए x का उपयोग नहीं कर रहे हैं, लेकिन इसके बारे में पता होना चाहिए। यदि आप 0 रेंज में x मान वाले काउंट डाउन लूप चाहते हैं .. (चौड़ाई -1) जो आप कर सकते हैं।

for (unsigned x = static_cast<unsigned>(bitmap->width); x-- > 0;)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

आप उपरोक्त उदाहरणों में जातियों से छुटकारा पा सकते हैं यदि आप चाहते हैं कि आप इसकी परवाह किए बिना तुलना नियमों पर प्रभाव डालते हैं क्योंकि आप बिटमैप के साथ क्या कर रहे हैं-> चौड़ाई इसे एक चर पर सीधे असाइन कर रही है।


2
मैंने देखा है कि दूसरे मामले के रूप में स्वरूपित किया गया है x --> 0, जिसके परिणामस्वरूप "डाउनटो" ऑपरेटर है। बहुत मजाकिया। PS मैं पठनीयता के लिए नकारात्मक होने के लिए अंतिम स्थिति के लिए एक चर बनाने पर विचार नहीं करता, यह वास्तव में विपरीत हो सकता है।
मार्क रैनसम

यह वास्तव में निर्भर करता है, कभी-कभी एक बयान इतना भयानक हो जाता है कि कई बयानों में इसे तोड़ने से पठनीयता में सुधार होता है लेकिन मैं ऐसा नहीं मानता।
प्लगवॉश करें

1
+1 अच्छा अवलोकन, हालांकि मैं यह दलील दूंगा कि लूप को बदलने static_cast<unsigned>(bitmap->width)और उसका उपयोग widthकरने से वास्तव में पठनीयता में सुधार होता है क्योंकि अब पाठक के लिए प्रति पंक्ति पार्स करने के लिए कम चीजें हैं। दूसरों के विचार हालांकि अलग हो सकते हैं।
सरगुजी

1
कई अन्य स्थितियां हैं, जहां नीचे की ओर गिनती करना बेहतर है (जैसे किसी सूची से आइटम हटाते समय)। मुझे नहीं पता कि ऐसा क्यों नहीं किया जाता है।
इयान गोल्डबी

3
यदि आप लूप लिखना चाहते हैं जो कि इष्टतम एएसएम, उपयोग की तरह दिखते हैं do { } while(), क्योंकि एएसएम में आप अंत में एक सशर्त शाखा के साथ लूप बनाते हैं। सामान्य for(){}और while(){}लूप्स को लूप की स्थिति को लूप से पहले एक बार जांचने के लिए अतिरिक्त निर्देशों की आवश्यकता होती है, यदि कंपाइलर साबित नहीं कर सकता है कि यह हमेशा कम से कम एक बार चलता है। हर तरह से, उपयोग करें for()या while()जब यह जांचने के लिए उपयोगी हो कि क्या लूप एक बार भी चलना चाहिए, या जब यह अधिक पठनीय हो।
पीटर कॉर्डेस

11

यहां केवल एक चीज जो अनुकूलन को रोक सकती है वह है सख्त अलियासिंग नियमसंक्षेप में :

"सख्त अलियासिंग एक धारणा है, जो सी (या सी ++) कंपाइलर द्वारा बनाई गई है, जो विभिन्न प्रकारों की वस्तुओं के लिए डेरेफेरिंग पॉइंट कभी भी एक ही मेमोरी लोकेशन (यानी एक दूसरे को उर्फ) का उल्लेख नहीं करेंगे।"

[...]

नियम का अपवाद एक है char*, जिसे किसी भी प्रकार को इंगित करने की अनुमति है।

अपवाद भी लागू होता है unsigned और signed charबिंदुओं ।

आपके कोड में यह मामला है: आप संशोधित कर रहे हैं जिसके *pमाध्यम pसे एक है unsigned char*, इसलिए संकलक को यह मान लेना चाहिए कि यह इंगित कर सकता हैbitmap->width । इसलिए कैशिंग bitmap->widthएक अमान्य अनुकूलन है। यह अनुकूलन-रोकथाम व्यवहार YSC के उत्तर में दिखाया गया है ।

यदि और केवल अगर pगैर charऔर गैर- decltype(bitmap->width)प्रकार को इंगित किया जाता है , तो क्या कैशिंग संभव अनुकूलन होगा।


10

मूल रूप से पूछा गया प्रश्न:

क्या यह इसके अनुकूलन के लायक है?

और उस पर मेरा जवाब (ऊपर और नीचे वोट दोनों का एक अच्छा मिश्रण) ..

कंपाइलर को इसकी चिंता करने दें।

कंपाइलर निश्चित रूप से आपसे बेहतर काम करेगा। और इस बात की कोई गारंटी नहीं है कि आपका 'अनुकूलन' 'स्पष्ट' कोड से बेहतर है - क्या आपने इसे मापा है ??

इससे भी महत्वपूर्ण बात, क्या आपके पास कोई प्रमाण है कि आप जिस कोड का अनुकूलन कर रहे हैं उसका आपके कार्यक्रम के प्रदर्शन पर कोई प्रभाव पड़ता है?

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

एक अलग सवाल, ज़ाहिर है, यह होगा:

अगर यह कोड के टुकड़े को अनुकूलित करने के लायक है तो मैं कैसे बता सकता हूं?

सबसे पहले, क्या आपके आवेदन या पुस्तकालय को वर्तमान में चलने की तुलना में तेजी से चलाने की आवश्यकता है? क्या उपयोगकर्ता बहुत लंबे समय तक इंतजार कर रहा है? क्या आपका सॉफ़्टवेयर कल के बजाय कल के मौसम का पूर्वानुमान करता है?

केवल आप वास्तव में यह बता सकते हैं कि यह इस बात पर आधारित है कि आपका सॉफ़्टवेयर क्या है और आपके उपयोगकर्ता क्या अपेक्षा करते हैं।

आपके सॉफ़्टवेयर को कुछ अनुकूलन की आवश्यकता है, यह मानते हुए कि अगली चीज़ को मापना शुरू करना है। प्रोफाइलर आपको बताएंगे कि आपका कोड कहाँ समय बिताता है। यदि आपका टुकड़ा एक अड़चन के रूप में नहीं दिख रहा है, तो यह सबसे अच्छा अकेला बचा है। प्रोफाइलर और अन्य मापने के उपकरण आपको यह भी बताएंगे कि क्या आपके परिवर्तनों में कोई बदलाव आया है। कोड को ऑप्टिमाइज़ करने के लिए घंटों तक खर्च करना संभव है, केवल यह पता लगाने के लिए कि आपने कोई अंतर नहीं किया है।

वैसे भी 'अनुकूलन' से आपका क्या अभिप्राय है?

यदि आप 'अनुकूलित' कोड नहीं लिख रहे हैं, तो आपका कोड जितना स्पष्ट, साफ और संक्षिप्त होना चाहिए, उतना ही आप इसे बना सकते हैं। "प्रीमेच्योर ऑप्टिमाइजेशन इज़ एविल" तर्क मैला या अक्षम कोड का बहाना नहीं है।

अनुकूलित कोड सामान्य रूप से प्रदर्शन के लिए उपरोक्त कुछ विशेषताओं का त्याग करता है। इसमें अतिरिक्त स्थानीय चरों को शामिल करना शामिल हो सकता है, जिसमें अपेक्षित दायरे से अधिक व्यापक वस्तुएं होती हैं या सामान्य लूप ऑर्डर को उलट देती हैं। ये सभी कम स्पष्ट या संक्षिप्त हो सकते हैं, इसलिए कोड (संक्षेप में!) का दस्तावेजीकरण करें कि आप ऐसा क्यों कर रहे हैं।

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


12
जब आप एक बिटमैप छवि की चौड़ाई पर पुनरावृत्ति कर रहे हैं, तो लूपिंग लॉजिक लूप में बिताए गए समय का एक महत्वपूर्ण हिस्सा हो सकता है। समय से पहले अनुकूलन के बारे में चिंता करने के बजाय, इस मामले में सबसे अच्छा अभ्यास विकसित करना बेहतर है जो शुरू से ही कुशल हो।
मार्क रैनसम

4
@MarkRansom ने सहमति व्यक्त की, भाग में: लेकिन "सर्वोत्तम प्रथाओं" या तो एक होगा: छवियों को भरने के लिए एक मौजूदा पुस्तकालय या एपीआई कॉल का उपयोग करें, या b: आप के लिए यह करने के लिए GPU प्राप्त करें। ओपी का सुझाव है कि इसे किसी भी तरह का अनमिश्रित माइक्रो-ऑप्टिमाइज़ेशन नहीं होना चाहिए। और आप कैसे जानते हैं कि यह कोड कभी भी एक से अधिक बार निष्पादित किया जाता है, या बिटमैप्स के साथ फिर 16 पिक्सेल चौड़ा होता है ...?
रोडी

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

@ राशि मैं संपादन की सराहना करता हूं, वे मदद करते हैं (और मेरी टिप्पणी शायद वैसे भी बहुत कठोर लग रही थी)। मैं अभी भी बाड़ पर हूँ, क्योंकि यह वास्तव में एक सवाल का जवाब है जो स्टैक ओवरफ्लो के लिए उपयुक्त नहीं है। ऐसा लगता है कि एक उचित उत्तर स्निपेट के लिए विशिष्ट होगा, क्योंकि यहां अत्यधिक मतदान वाले उत्तर हैं।
विड्रैक 12

6

मैं इस तरह की स्थिति में निम्नलिखित पैटर्न का उपयोग करता हूं। यह आपके पहले मामले की तुलना में लगभग कम है, और दूसरे मामले की तुलना में बेहतर है, क्योंकि यह अस्थायी चर को लूप में रखता है।

for (unsigned int x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
{
  *p++ = 0xAA;
  *p++ = 0xBB;
  *p++ = 0xCC;
}

यह कम स्मार्ट कंपाइलर, डीबग बिल्ड या कुछ संकलन झंडों से कम तेज़ होगा।

Edit1 : लूप के बाहर एक निरंतर ऑपरेशन को रखना एक अच्छा प्रोग्रामिंग पैटर्न है। यह मशीन संचालन की मूल बातें, विशेषकर C / C ++ में समझ को दर्शाता है। मेरा तर्क है कि खुद को साबित करने का प्रयास ऐसे लोगों पर होना चाहिए जो इस प्रथा का पालन नहीं करते हैं। यदि कंपाइलर अच्छे पैटर्न के लिए सज़ा देता है, तो यह कंपाइलर में एक बग है।

Edit2:: मैंने अपने सुझाव को मूल कोड के खिलाफ vs2013 में मापा है,% 1 सुधार हुआ है। क्या हम बेहतर कर सकते हैं? एक साधारण मैनुअल अनुकूलन विदेशी निर्देशों का सहारा लिए बिना x64 मशीन पर मूल लूप पर 3 गुना सुधार देता है। नीचे दिया गया कोड थोड़ा एंडियन सिस्टम मानता है और ठीक से बिटमैप गठबंधन करता है। टेस्ट 0 मूल (9 सेकंड) है, टेस्ट 1 तेज है (3 सेकंड)। मुझे यकीन है कि कोई इसे और भी तेज कर सकता है, और परीक्षण का परिणाम बिटमैप के आकार पर निर्भर करेगा। निश्चित रूप से भविष्य में जल्द ही, कंपाइलर लगातार सबसे तेज़ कोड का उत्पादन करने में सक्षम होगा। मुझे डर है कि यह भविष्य होगा जब कंपाइलर भी एक प्रोग्रामर एआई होगा, इसलिए हम काम से बाहर हो जाएंगे। लेकिन अभी के लिए, बस कोड लिखें जो दिखाता है कि आप जानते हैं कि लूप में अतिरिक्त ऑपरेशन की आवश्यकता नहीं है।

#include <memory>
#include <time.h>

struct Bitmap_line
{
  int blah;
  unsigned int width;
  Bitmap_line(unsigned int w)
  {
    blah = 0;
    width = w;
  }
};

#define TEST 0 //define 1 for faster test

int main(int argc, char* argv[])
{
  unsigned int size = (4 * 1024 * 1024) / 3 * 3; //makes it divisible by 3
  unsigned char* pointer = (unsigned char*)malloc(size);
  memset(pointer, 0, size);
  std::unique_ptr<Bitmap_line> bitmap(new Bitmap_line(size / 3));
  clock_t told = clock();
#if TEST == 0
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    for (unsigned x = 0; x < static_cast<unsigned>(bitmap->width); ++x)
    //for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#else
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    unsigned x = 0;
    for (const unsigned n = static_cast<unsigned>(bitmap->width) - 4; x < n; x += 4)
    {
      *(int64_t*)p = 0xBBAACCBBAACCBBAALL;
      p += 8;
      *(int32_t*)p = 0xCCBBAACC;
      p += 4;
    }

    for (const unsigned n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#endif
  double ms = 1000.0 * double(clock() - told) / CLOCKS_PER_SEC;
  printf("time %0.3f\n", ms);

  {
    //verify
    unsigned char* p = pointer;
    for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      if ((*p++ != 0xAA) || (*p++ != 0xBB) || (*p++ != 0xCC))
      {
        printf("EEEEEEEEEEEEERRRRORRRR!!!\n");
        abort();
      }
    }
  }

  return 0;
}

यदि आप int64_t और int32_t के बजाय तीन int64_t का उपयोग करते हैं, तो आप 64 बिट पर एक और 25% बचा सकते हैं।
एंटोनिन लेजसेक

5

विचार करने के लिए दो चीजें हैं।

ए) अनुकूलन कितनी बार चलेगा?

यदि उत्तर बहुत बार नहीं है, जैसे कि जब कोई उपयोगकर्ता बटन क्लिक करता है, तो परेशान न हों यदि यह आपके कोड को अपठनीय बनाता है। यदि उत्तर 1000 बार दूसरा है तो आप शायद अनुकूलन के साथ जाना चाहेंगे। यदि यह थोड़ा जटिल है, तो यह बताने के लिए कि आने वाले लड़के की मदद करने के लिए क्या हो रहा है, यह बताने के लिए एक टिप्पणी डालना सुनिश्चित करें।

बी) क्या यह कोड को मुश्किल / समस्या निवारण के लिए कठिन बना देगा?

यदि आपको प्रदर्शन में भारी बढ़त नहीं दिख रही है, तो कुछ घड़ी की टिक को बचाने के लिए अपने कोड को सरल बनाने के लिए एक अच्छा विचार नहीं है। बहुत से लोग आपको बताएंगे कि किसी भी अच्छे प्रोग्रामर को कोड को देखने और यह पता लगाने में सक्षम होना चाहिए कि क्या चल रहा है। यह सच है। समस्या यह है कि व्यापार की दुनिया में अतिरिक्त समय लगाने से पैसा खर्च होता है। इसलिए, यदि आप इसे पढ़ने के लिए पहले से तैयार कर सकते हैं तो करें। आपके मित्र आपको इसके लिए धन्यवाद देंगे।

मैंने कहा कि मैं व्यक्तिगत रूप से बी उदाहरण का उपयोग करूंगा।


4

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


4

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

एक बार जब आप यह देखते हैं कि प्रदर्शन मायने रखता है, तो आपको यह निर्धारित करने के लिए कोड पर एक प्रोफाइलर चलाना चाहिए कि कौन से छोर अक्षम हैं, और उन लोगों को अलग-अलग अनुकूलित करें। वास्तव में ऐसे मामले हो सकते हैं जहां आप उस अनुकूलन को करना चाहते हैं (विशेषकर यदि आप सी ++ की ओर पलायन करते हैं, जहां एसटीएल कंटेनर शामिल हो जाते हैं), लेकिन पठनीयता के संदर्भ में लागत महान है।

इसके अलावा, मैं पैथोलॉजिकल स्थितियों के बारे में सोच सकता हूं जहां यह वास्तव में कोड को धीमा कर सकता है। उदाहरण के लिए, उस मामले पर विचार करें जहां संकलक यह साबित नहीं कर सका कि bitmap->widthप्रक्रिया के माध्यम से स्थिर था। widthवैरिएबल को जोड़कर आप कंपाइलर को उस दायरे में एक स्थानीय वैरिएबल को बनाए रखने के लिए मजबूर करते हैं। यदि, किसी प्लेटफ़ॉर्म विशिष्ट कारण के लिए, अतिरिक्त चर ने कुछ स्टैक-स्पेस ऑप्टिमाइज़ेशन को रोक दिया, तो उसे पुनर्गठित करना पड़ सकता है कि यह कैसे बायोटेक्स उत्सर्जित कर रहा है, और कुछ कम कुशल उत्पादन करता है।

एक उदाहरण के रूप में, विंडोज x64 पर, एक __chkstkफ़ंक्शन की प्रस्तावना में , एक विशेष एपीआई कॉल को कॉल करने के लिए बाध्य है, यदि फ़ंक्शन स्थानीय चर के 1 से अधिक पृष्ठ का उपयोग करेगा। यह फ़ंक्शन खिड़कियों को गार्ड पृष्ठों को प्रबंधित करने का मौका देता है, जिनका उपयोग वे ज़रूरत पड़ने पर स्टैक का विस्तार करने के लिए करते हैं। यदि आपका अतिरिक्त वैरिएबल स्टैक के उपयोग को 1 पेज से नीचे या ऊपर-ऊपर 1 पृष्ठ पर धकेलता है, तो आपका फ़ंक्शन अब __chkstkहर बार दर्ज किए जाने पर कॉल करने के लिए बाध्य है । यदि आप धीमे पथ पर इस लूप का अनुकूलन करने के लिए थे, तो आप वास्तव में धीमे पथ पर सहेजे गए से अधिक तेज़ पथ को धीमा कर सकते हैं!

बेशक, यह थोड़ा पैथोलॉजिकल है, लेकिन उस उदाहरण की बात यह है कि आप वास्तव में कंपाइलर को धीमा कर सकते हैं। यह सिर्फ यह दर्शाता है कि अनुकूलन करने के लिए आपको अपने काम को निर्धारित करना होगा। इस समय में, कृपया अनुकूलन के लिए किसी भी तरह से पठनीयता का त्याग न करें जो कि मायने रखता हो या न हो।


4
मेरी इच्छा है कि C और C ++ प्रोग्रामर की परवाह नहीं करने वाली चीजों को स्पष्ट रूप से पहचानने के और अधिक तरीके प्रदान करेगा। न केवल वे चीजों को अनुकूलित करने के लिए कंपाइलरों के लिए अधिक संभावनाएं प्रदान करेंगे, लेकिन यह अन्य प्रोग्रामर को बचाएगा, जो कोड को पढ़ने से यह अनुमान लगाता है कि क्या यह बिटमैप को रीचेकिंग हो सकता है-> यह सुनिश्चित करने के लिए कि इसे बदलने के लिए हर बार लूप को प्रभावित करना, या चाहे वह बिटमैप कैशिंग हो सकता है-> यह सुनिश्चित करने के लिए कि उसमें परिवर्तन लूप को प्रभावित नहीं करता है। कहने का मतलब है कि "यह कैश करें या नहीं - मुझे परवाह नहीं है" यह प्रोग्रामर की पसंद का कारण स्पष्ट करेगा।
सुपरकाट

@supercat मैं तहे दिल से सहमत हूँ, जैसा कि कोई देख सकता है कि अगर मैं इसे हल करने के लिए लिखने की मांग की गई फटी हुई असफल भाषाओं के ढेर को देखता हूं। मैंने पाया है कि "क्या" को परिभाषित करने के लिए यह बहुत मुश्किल है कि कोई भी इतने लापरवाह वाक्यविन्यास के बारे में परवाह नहीं करता है कि यह सिर्फ इसके लायक नहीं है। मैं व्यर्थ में अपनी खोज जारी रखता हूं।
कॉर्ट अमोन

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

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

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

4

तुलना गलत है दो कोड स्निपेट के बाद से

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)

तथा

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x<width ;  ++x)

समतुल्य नहीं हैं

पहले मामले widthमें निर्भर है और कब्ज नहीं है, और कोई यह नहीं मान सकता है कि बाद के पुनरावृत्तियों के बीच यह बदल नहीं सकता है। इस प्रकार यह अनुकूलित नहीं किया जा सकता है, लेकिन हर लूप में जांच की जानी चाहिए

आपके अनुकूलित मामले में एक स्थानीय चर को bitmap->widthकार्यक्रम के निष्पादन के दौरान कुछ बिंदु पर मान दिया जाता है। संकलक यह सत्यापित कर सकता है कि यह वास्तव में नहीं बदलता है।

क्या आपने मल्टी थ्रेडिंग के बारे में सोचा, या शायद मूल्य बाहरी रूप से इस तरह निर्भर हो सकता है कि इसका मूल्य अस्थिर है। यदि आप नहीं बताएंगे तो कंपाइलर इन सभी चीजों का पता कैसे लगाएगा?

कंपाइलर केवल उतना ही अच्छा कर सकता है जितना आपका कोड इसे देता है।


2

जब तक आप नहीं जानते कि कंपाइलर कोड का वास्तव में कैसे अनुकूलन करता है, तो कोड की पठनीयता और डिज़ाइन को ध्यान में रखते हुए अपनी स्वयं की अनुकूलन करना बेहतर होता है। व्यावहारिक रूप से हम नए संकलक संस्करणों के लिए लिखने वाले प्रत्येक फ़ंक्शन के लिए विधानसभा कोड की जांच करना कठिन है।


1

संकलक अनुकूलन नहीं कर सकता bitmap->widthक्योंकि widthपुनरावृत्तियों के बीच मूल्य को बदला जा सकता है। कुछ सबसे सामान्य कारण हैं:

  1. बहु सूत्रण। कंपाइलर यह अनुमान नहीं लगा सकता है कि अन्य थ्रेड मान बदलने वाला है या नहीं।
  2. लूप के अंदर संशोधन, कभी-कभी यह बताना आसान नहीं है कि क्या लूप के अंदर परिवर्तन किया जाएगा।
  3. यह फ़ंक्शन कॉल है, जैसे iterator::end()या container::size()तो यह भविष्यवाणी करना मुश्किल है कि क्या यह हमेशा एक ही परिणाम देगा।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.