यदि मैं गति के बजाय आकार के लिए अनुकूलन करता हूं तो जीसीसी 15-20% तेज कोड क्यों उत्पन्न करता है?


445

मैंने पहली बार 2009 में देखा कि जीसीसी (कम से कम मेरी परियोजनाओं पर और मेरी मशीनों पर) गति को ( या ) के बजाय यदि मैं आकार ( -Os) के लिए अनुकूलित करता हूं, तो मैं काफी तेज कोड उत्पन्न करने की प्रवृत्ति रखता हूं और मैं सोच रहा हूं कि क्यों।-O2-O3

मैं (बल्कि मूर्खतापूर्ण) कोड बनाने में कामयाब रहा हूं जो इस आश्चर्यजनक व्यवहार को दिखाता है और यहां पोस्ट किए जाने के लिए पर्याप्त रूप से छोटा है।

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int add(const int& x, const int& y) {
    return x + y;
}

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

अगर मैं इसे संकलित करता हूं -Os, तो इस कार्यक्रम को निष्पादित करने में 0.38 सेकेंड लगते हैं, और यदि यह -O2या इसके साथ संकलित किया जाता है, तो 0.44 एस -O3। ये समय लगातार प्राप्त होता है और व्यावहारिक रूप से कोई शोर नहीं होता है (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M)।

(अपडेट: मैंने सभी विधानसभा कोड को स्थानांतरित कर दिया है GitHub में कर दिए हैं: उन्होंने पोस्ट को फूला हुआ बना दिया और जाहिरा तौर पर सवालों के बहुत कम मूल्य जोड़ दिए क्योंकि fno-align-*झंडे का प्रभाव समान है।)

यहाँ के साथ उत्पन्न विधानसभा है -Os और -O2

दुर्भाग्य से, विधानसभा की मेरी समझ बहुत सीमित है, इसलिए मैं कोई अंदाज़ा नहीं है कि क्या मैं आगे क्या किया था सही था: मैं के लिए विधानसभा को पकड़ा -O2और विधानसभा में अपने सभी मतभेदों को विलय कर के लिए -Os छोड़कर.p2align लाइनों, परिणाम यहाँ । यह कोड अभी भी 0.38 में चलता है और एकमात्र अंतर है .p2align सामान है।

अगर मैं सही ढंग से अनुमान लगाऊं, तो ये स्टैक अलाइनमेंट के लिए पैडिंग हैं। क्यों के अनुसार GCC पैड NOP के साथ कार्य करता है? यह इस उम्मीद में किया जाता है कि कोड तेजी से चलेगा, लेकिन जाहिर तौर पर यह अनुकूलन मेरे मामले में बैकफायर पर है।

क्या यह पेडिंग है जो इस मामले में दोषी है? क्यों और कैसे?

शोर यह बहुत अधिक बनाता है समय सूक्ष्म अनुकूलन को असंभव बनाता है।

जब मैं C या C ++ स्रोत कोड पर माइक्रो-ऑप्टिमाइज़ेशन (स्टैक अलाइनमेंट के लिए असंबंधित) करता हूं, तो मैं यह कैसे सुनिश्चित कर सकता हूं कि ऐसे आकस्मिक भाग्यशाली / अशुभ संरेखण हस्तक्षेप नहीं कर रहे हैं?


अपडेट करें:

बाद पास्कल Cuoq का जवाब मैं संरेखण के साथ एक छोटा सा tinkered। Gcc में पास होने -O2 -fno-align-functions -fno-align-loopsसे, सभी .p2alignअसेंबली से चले जाते हैं और उत्पन्न निष्पादन योग्य 0.38 में चलता है। Gcc प्रलेखन के अनुसार :

-O सभी -O2 अनुकूलन सक्षम बनाता है [लेकिन] -O निम्नलिखित अनुकूलन झंडे अक्षम करता है:

  -falign-functions  -falign-jumps  -falign-loops
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition
  -fprefetch-loop-arrays

तो, यह बहुत (गलत) संरेखण मुद्दे की तरह लगता है।

मुझे अभी भी इस बात पर संदेह है -march=nativeकि मारत दुखन के उत्तर में क्या सुझाव दिया गया था । मुझे यकीन नहीं है कि यह इस (गलत) संरेखण मुद्दे के साथ हस्तक्षेप नहीं कर रहा है; मेरी मशीन पर इसका कोई प्रभाव नहीं है। (फिर भी, मैंने उनके उत्तर को गलत बताया।)


अद्यतन 2:

हम -Osतस्वीर से बाहर निकाल सकते हैं । निम्नलिखित समय के साथ संकलन करके प्राप्त किया जाता है

  • -O2 -fno-omit-frame-pointer 0.37s

  • -O2 -fno-align-functions -fno-align-loops 0.37s

  • -S -O2तब मैन्युअल रूप से add()बाद की विधानसभा को स्थानांतरित करनाwork() 0.37 स्थानांतरित करना

  • -O2 0.44s

ऐसा लगता add()है कि कॉल साइट से मेरी दूरी बहुत मायने रखती है। मैंने कोशिश की है perf, लेकिन के उत्पादन perf statऔरperf report मुझे बहुत कम समझ में आता है। हालाँकि, मैं इससे केवल एक सुसंगत परिणाम प्राप्त कर सकता था:

-O2:

 602,312,864 stalled-cycles-frontend   #    0.00% frontend cycles idle
       3,318 cache-misses
 0.432703993 seconds time elapsed
 [...]
 81.23%  a.out  a.out              [.] work(int, int)
 18.50%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
100.00 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
       ¦   ? retq
[...]
       ¦            int z = add(x, y);
  1.93 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 79.79 ¦      add    %eax,%ebx

के लिए fno-align-*:

 604,072,552 stalled-cycles-frontend   #    0.00% frontend cycles idle
       9,508 cache-misses
 0.375681928 seconds time elapsed
 [...]
 82.58%  a.out  a.out              [.] work(int, int)
 16.83%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
 51.59 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
[...]
       ¦    __attribute__((noinline))
       ¦    static int work(int xval, int yval) {
       ¦        int sum(0);
       ¦        for (int i=0; i<LOOP_BOUND; ++i) {
       ¦            int x(xval+sum);
  8.20 ¦      lea    0x0(%r13,%rbx,1),%edi
       ¦            int y(yval+sum);
       ¦            int z = add(x, y);
 35.34 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 39.48 ¦      add    %eax,%ebx
       ¦    }

के लिए -fno-omit-frame-pointer:

 404,625,639 stalled-cycles-frontend   #    0.00% frontend cycles idle
      10,514 cache-misses
 0.375445137 seconds time elapsed
 [...]
 75.35%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]                                                                                     ¦
 24.46%  a.out  a.out              [.] work(int, int)
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
 18.67 ¦     push   %rbp
       ¦       return x + y;
 18.49 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   const int LOOP_BOUND = 200000000;
       ¦
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦     mov    %rsp,%rbp
       ¦       return x + y;
       ¦   }
 12.71 ¦     pop    %rbp
       ¦   ? retq
 [...]
       ¦            int z = add(x, y);
       ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 29.83 ¦      add    %eax,%ebx

ऐसा लगता है कि हम कॉल करने के लिए रुक रहे हैं add() धीमे मामले में ।

मैंने सब कुछ जांच लिया हैperf -e मेरी मशीन पर थूक से बाहर कर सकते हैं; न केवल ऊपर दिए गए आँकड़े।

एक ही निष्पादन योग्य के लिए, stalled-cycles-frontendनिष्पादन समय के साथ रैखिक सहसंबंध दिखाता है; मैंने कुछ और नहीं देखा जो इतनी स्पष्ट रूप से सहसंबंधित होगा। ( stalled-cycles-frontendविभिन्न निष्पादनों के लिए तुलना करने से मुझे कोई मतलब नहीं है।)

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


36
अंधा अनुमान: क्या यह कैश मिस हो सकता है?

@ H2CO3 यह मेरा पहला विचार था, लेकिन ओपी के प्रश्न को गहराई से पढ़ने और समझने के बिना टिप्पणी पोस्ट करने के लिए पर्याप्त प्रोत्साहित नहीं किया गया था।
atν

2
@ g-makulik यही कारण है कि मैंने चेतावनी दी है कि यह एक "अंधा अनुमान" ;-) "TL; DR" बुरे प्रश्नों के लिए आरक्षित है। : पी

3
बस एक दिलचस्प डेटा बिंदु: मुझे लगता है कि -O3 या -OIFT के बारे में 1.5x जितनी तेजी से -OOS है, जब मैं ओएस एक्स पर क्लैंग के साथ इसे संकलित करता हूं (मैंने जीसीसी के साथ पुन: प्रस्तुत करने की कोशिश नहीं की है।)
रोब नेपियर

2
यह एक ही कोड है। .L3 के पते पर करीब से नज़र डालें, तो गलत शाखा लक्ष्य महंगे हैं।
हंस पसंत

जवाबों:


505

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

यहां time ./test 0 0कई प्रोसेसर (उपयोगकर्ता द्वारा रिपोर्ट किए गए समय) के परिणाम हैं:

Processor (System-on-Chip)             Compiler   Time (-O2)  Time (-Os)  Fastest
AMD Opteron 8350                       gcc-4.8.1    0.704s      0.896s      -O2
AMD FX-6300                            gcc-4.8.1    0.392s      0.340s      -Os
AMD E2-1800                            gcc-4.7.2    0.740s      0.832s      -O2
Intel Xeon E5405                       gcc-4.8.1    0.603s      0.804s      -O2
Intel Xeon E5-2603                     gcc-4.4.7    1.121s      1.122s       -
Intel Core i3-3217U                    gcc-4.6.4    0.709s      0.709s       -
Intel Core i3-3217U                    gcc-4.7.3    0.708s      0.822s      -O2
Intel Core i3-3217U                    gcc-4.8.1    0.708s      0.944s      -O2
Intel Core i7-4770K                    gcc-4.8.1    0.296s      0.288s      -Os
Intel Atom 330                         gcc-4.8.1    2.003s      2.007s      -O2
ARM 1176JZF-S (Broadcom BCM2835)       gcc-4.6.3    3.470s      3.480s      -O2
ARM Cortex-A8 (TI OMAP DM3730)         gcc-4.6.3    2.727s      2.727s       -
ARM Cortex-A9 (TI OMAP 4460)           gcc-4.6.3    1.648s      1.648s       -
ARM Cortex-A9 (Samsung Exynos 4412)    gcc-4.6.3    1.250s      1.250s       -
ARM Cortex-A15 (Samsung Exynos 5250)   gcc-4.7.2    0.700s      0.700s       -
Qualcomm Snapdragon APQ8060A           gcc-4.8       1.53s       1.52s      -Os

कुछ मामलों में आप gccअपने विशेष प्रोसेसर (विकल्पों का उपयोग -mtune=nativeकर -march=native) या के लिए अनुकूलन करने के लिए कहकर नुकसानदेह अनुकूलन के प्रभाव को कम कर सकते हैं :

Processor            Compiler   Time (-O2 -mtune=native) Time (-Os -mtune=native)
AMD FX-6300          gcc-4.8.1         0.340s                   0.340s
AMD E2-1800          gcc-4.7.2         0.740s                   0.832s
Intel Xeon E5405     gcc-4.8.1         0.603s                   0.803s
Intel Core i7-4770K  gcc-4.8.1         0.296s                   0.288s

अपडेट: आइवी ब्रिज आधारित कोर पर के तीन संस्करण i3 gcc( 4.6.4, 4.7.3, और4.8.1 काफी अलग प्रदर्शन के साथ) का उत्पादन बाइनरी, लेकिन विधानसभा कोड केवल सूक्ष्म भिन्नताएं होती हैं। अब तक, मेरे पास इस तथ्य का कोई स्पष्टीकरण नहीं है।

विधानसभा से gcc-4.6.4 -Os(0.709 सेकंड में निष्पादित):

00000000004004d2 <_ZL3addRKiS0_.isra.0>:
  4004d2:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004d5:       c3                      ret

00000000004004d6 <_ZL4workii>:
  4004d6:       41 55                   push   r13
  4004d8:       41 89 fd                mov    r13d,edi
  4004db:       41 54                   push   r12
  4004dd:       41 89 f4                mov    r12d,esi
  4004e0:       55                      push   rbp
  4004e1:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  4004e6:       53                      push   rbx
  4004e7:       31 db                   xor    ebx,ebx
  4004e9:       41 8d 34 1c             lea    esi,[r12+rbx*1]
  4004ed:       41 8d 7c 1d 00          lea    edi,[r13+rbx*1+0x0]
  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>
  4004fd:       89 d8                   mov    eax,ebx
  4004ff:       5b                      pop    rbx
  400500:       5d                      pop    rbp
  400501:       41 5c                   pop    r12
  400503:       41 5d                   pop    r13
  400505:       c3                      ret

विधानसभा से gcc-4.7.3 -Os(0.822 सेकंड में निष्पादित):

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

00000000004004fe <_ZL4workii>:
  4004fe:       41 55                   push   r13
  400500:       41 89 f5                mov    r13d,esi
  400503:       41 54                   push   r12
  400505:       41 89 fc                mov    r12d,edi
  400508:       55                      push   rbp
  400509:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  40050e:       53                      push   rbx
  40050f:       31 db                   xor    ebx,ebx
  400511:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400516:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>
  40051f:       01 c3                   add    ebx,eax
  400521:       ff cd                   dec    ebp
  400523:       75 ec                   jne    400511 <_ZL4workii+0x13>
  400525:       89 d8                   mov    eax,ebx
  400527:       5b                      pop    rbx
  400528:       5d                      pop    rbp
  400529:       41 5c                   pop    r12
  40052b:       41 5d                   pop    r13
  40052d:       c3                      ret

विधानसभा से gcc-4.8.1 -Os(0.994 सेकंड में निष्पादित):

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3                      ret

0000000000400501 <_ZL4workii>:
  400501:       41 55                   push   r13
  400503:       41 89 f5                mov    r13d,esi
  400506:       41 54                   push   r12
  400508:       41 89 fc                mov    r12d,edi
  40050b:       55                      push   rbp
  40050c:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  400511:       53                      push   rbx
  400512:       31 db                   xor    ebx,ebx
  400514:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400519:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051d:       e8 db ff ff ff          call   4004fd <_ZL3addRKiS0_.isra.0>
  400522:       01 c3                   add    ebx,eax
  400524:       ff cd                   dec    ebp
  400526:       75 ec                   jne    400514 <_ZL4workii+0x13>
  400528:       89 d8                   mov    eax,ebx
  40052a:       5b                      pop    rbx
  40052b:       5d                      pop    rbp
  40052c:       41 5c                   pop    r12
  40052e:       41 5d                   pop    r13
  400530:       c3                      ret

186
बस यह स्पष्ट करने के लिए: क्या आप वास्तव में 12 अलग-अलग प्लेटफार्मों पर ओपी के कोड के प्रदर्शन को मापते हैं? (+1 के लिए मात्र यह सोचा था कि आप ऐसा करेंगे)
अनातोलीग

194
@anatolyg हाँ, मैंने किया था! (और जल्द ही कुछ और जोड़ेंगे)
Marat Dukhan

43
वास्तव में। न केवल विभिन्न सीपीयू के बारे में, बल्कि वास्तव में इसे साबित करने के लिए एक और +1 । गति के विषय में प्रत्येक उत्तर में आप कुछ नहीं देख रहे हैं। क्या ये परीक्षण एक ही ओएस के साथ चलाए जाते हैं? (जैसा कि यह संभव हो सकता है इस परिणाम skews ...)
usr2564301

7
@ एएमएल एएमडी-एफएक्स 6300 पर -O2 -fno-align-functions -fno-align-loopsसमय गिरता है 0.340s, इसलिए इसे संरेखण द्वारा समझाया जा सकता है। हालांकि, इष्टतम संरेखण प्रोसेसर-निर्भर है: कुछ प्रोसेसर संरेखित छोरों और कार्यों को पसंद करते हैं।
मराठ दुखन

13
@ जोंगवेयर यह नहीं देखता कि ओएस परिणामों को कैसे प्रभावित करेगा; लूप कभी भी सिस्टम कॉल नहीं करता है।
अली

186

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


संक्षिप्त जवाब:

क्या यह पेडिंग है जो इस मामले में दोषी है? क्यों और कैसे?

यह सब संरेखित करने के लिए फोड़ा। प्रदर्शन पर संरेखण का एक महत्वपूर्ण प्रभाव हो सकता है, यही कारण है कि हमारे पास है-falign-* पहले स्थान पर झंडे हैं।

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

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

शोर यह बहुत अधिक बनाता है समय सूक्ष्म अनुकूलन को असंभव बनाता है।

जब मैं C या C ++ स्रोत कोड पर माइक्रो-ऑप्टिमाइज़ेशन (स्टैक अलाइनमेंट के लिए असंबंधित) करता हूं, तो मैं यह कैसे सुनिश्चित कर सकता हूं कि ऐसे आकस्मिक भाग्यशाली / अशुभ संरेखण हस्तक्षेप नहीं कर रहे हैं?

बस सही संरेखण करने के लिए जीसीसी बताकर:

g++ -O2 -falign-functions=16 -falign-loops=16


लंबा जवाब:

कोड धीमा चलेगा यदि:

  • बीच में एक XXबाइट सीमा कटौती add()( XXमशीन पर निर्भर होने के नाते)।

  • अगर कॉल को बाइट की बाउंड्री add()पर कूदना है XXऔर लक्ष्य संरेखित नहीं है।

  • यदि add()संरेखित नहीं किया गया है।

  • यदि लूप संरेखित नहीं है।

पहले 2 कोड और परिणामों पर खूबसूरती से दिखाई दे रहे हैं जिन्हें मराट दुखन ने पोस्ट किया है । इस मामले में, gcc-4.8.1 -Os(0.994 सेकंड में निष्पादित):

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3   

एक 256 बाइट सीमा के add()ठीक बीच में कट जाती है और न add()तो लूप संरेखित होता है। आश्चर्य, आश्चर्य, यह सबसे धीमा मामला है!

मामले में gcc-4.7.3 -Os(0.822 सेकंड में निष्पादित), 256 बाइट सीमा केवल एक ठंडे खंड में कटौती करती है (लेकिन न तो लूप, और न ही add()कट जाता है):

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

[...]

  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>

कुछ भी संरेखित नहीं है, और add()256 बाइट सीमा पर कूदने के लिए कॉल करना होगा । यह कोड दूसरा सबसे धीमा है।

मामले में gcc-4.6.4 -Os(0.709 सेकंड में निष्पादित), हालांकि कुछ भी संरेखित नहीं है, कॉल को add()256 बाइट की सीमा से अधिक नहीं कूदना पड़ता है और लक्ष्य 32 बाइट दूर है:

  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>

यह तीनों में सबसे तेज है। क्यों उनकी मशीन पर 256 बाइट की सीमा स्थानिक है, मैं इसका पता लगाने के लिए इसे छोड़ दूंगा। मेरे पास ऐसा कोई प्रोसेसर नहीं है।

अब, मेरी मशीन पर मुझे यह 256 बाइट सीमा प्रभाव नहीं मिलता है। केवल फ़ंक्शन और लूप संरेखण मेरी मशीन पर किक करता है। अगर मैं पास हो जाता हूं g++ -O2 -falign-functions=16 -falign-loops=16तो सब कुछ सामान्य हो जाता है: मुझे हमेशा सबसे तेज मामला मिलता है और समय -fno-omit-frame-pointerअब ध्वज के प्रति संवेदनशील नहीं है। मैं g++ -O2 -falign-functions=32 -falign-loops=3216 में से किसी भी गुणक को पास कर सकता हूं, या तो कोड संवेदनशील नहीं है।

मैंने पहली बार 2009 में देखा कि gcc (कम से कम मेरे प्रोजेक्ट्स और मेरी मशीनों पर) में गति के बजाय (-O2 या -O3) आकार के लिए ऑप्टिमाइज़ करने की प्रवृत्ति होती है, अगर मैं आकार (-O2) का अनुकूलन करता हूं और मुझे आश्चर्य हो रहा है कब से क्यों

एक संभावित व्याख्या यह है कि मेरे पास हॉटस्पॉट थे जो संरेखण के प्रति संवेदनशील थे, इस उदाहरण में एक की तरह। झंडे के साथ खिलवाड़ करके ( -Osइसके बजाय गुजरते हुए -O2), उन हॉटस्पॉट्स को एक भाग्यशाली तरीके से दुर्घटना से संरेखित किया गया और कोड तेज हो गया। इसका आकार के लिए अनुकूलन से कोई लेना-देना नहीं था: ये सरासर दुर्घटना से थे कि हॉटस्पॉट बेहतर तरीके से संरेखित हो गए। अब से, मैं अपनी परियोजनाओं पर संरेखण के प्रभावों की जांच करूंगा।

अरे, और एक बात। ऐसे हॉटस्पॉट्स कैसे उत्पन्न हो सकते हैं, जैसे उदाहरण में दिखाया गया है? इस तरह के एक छोटे समारोह की inlining कैसे add()असफल हो सकता है?

इस पर विचार करो:

// add.cpp
int add(const int& x, const int& y) {
    return x + y;
}

और एक अलग फाइल में:

// main.cpp
int add(const int& x, const int& y);

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

और के रूप में संकलित g++ -O2 add.cpp main.cpp:।

      इनलाइन नहीं होगा add()!

यह सब, यह ओपी में एक की तरह अनायास ही हॉटस्पॉट बनाने के लिए आसान है। बेशक यह आंशिक रूप से मेरी गलती है: जीसीसी एक उत्कृष्ट संकलक है। अगर उपरोक्त को संकलित करें: g++ -O2 -flto add.cpp main.cppयानी, यदि मैं लिंक टाइम ऑप्टिमाइज़ेशन करता हूं, तो कोड 0.19 में चलता है!

(ओपी में कृत्रिम रूप से निष्क्रिय किया जाता है, इसलिए, ओपी में कोड 2x धीमा था)।


19
वाह ... यह निश्चित रूप से परे चला जाता है कि मैं आमतौर पर बेंचमार्किंग विसंगतियों के बारे में क्या करता हूं।
मिस्टिक

@ क्या मुझे लगता है कि समझ में आता है कि कंपाइलर इनलाइन कुछ ऐसा कैसे देख सकता है? शायद इसीलिए हम inlineहेडर में + फंक्शन की परिभाषा का उपयोग करते हैं । निश्चित नहीं है कि gcc में परिपक्व lto कैसा है। कम से कम मिंगवा में इसके साथ मेरा अनुभव एक हिट या मिस है।
महानवमी

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

1
के लिए विशेष रूप से करना होगा -flto। यह बहुत ही क्रांतिकारी है अगर आपने इसे पहले कभी प्रयोग नहीं किया है, अनुभव से बोल रहा है :)
underscore_d

2
यह एक शानदार वीडियो है जो यह बताता है कि संरेखण प्रदर्शन को कैसे प्रभावित कर सकता है और इसके लिए प्रोफ़ाइल कैसे बना सकता है: youtube.com/watch?time_continue=1&v=r-TLSBdHe1A
Zhro

73

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

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

मुझे लगता है कि आप एक ही अवलोकन पर एक अलग कोण का सामना कर रहे हैं।

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


33

मुझे लगता है कि आपने जैसा किया वैसा ही परिणाम प्राप्त कर सकते हैं:

मैंने -O2 के लिए असेंबली को पकड़ा और अपने सभी मतभेदों को .p2align लाइनों को छोड़कर असेंबली-ओनर्स में मर्ज कर दिया।

… का उपयोग करके -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1। मैं इन विकल्पों के साथ सब कुछ संकलित कर रहा हूं, जो कि -O215 साल के लिए मापने के लिए मुझे परेशान करने वाले सादे से अधिक तेज थे ।

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

अगर मैं सही ढंग से अनुमान लगाऊं, तो ये स्टैक अलाइनमेंट के लिए पैडिंग हैं।

नहीं, इसका स्टैक से कोई लेना-देना नहीं है, जो NOP डिफ़ॉल्ट रूप से उत्पन्न होते हैं और जो विकल्प -falign - * = 1 रोकथाम कोड संरेखण के लिए हैं।

क्यों के अनुसार GCC पैड NOP के साथ कार्य करता है? यह इस उम्मीद में किया जाता है कि कोड तेजी से चलेगा, लेकिन जाहिर है कि यह अनुकूलन मेरे मामले में बैकफायर पर है।

क्या यह पेडिंग है जो इस मामले में दोषी है? क्यों और कैसे?

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


मजेदार बात यह है: -O2 -fno-omit-frame-pointerबस के रूप में अच्छा है -Os। कृपया अद्यतन प्रश्न की जाँच करें।
अली 12

11

यदि आपका प्रोग्राम CODE L1 कैश से घिरा है, तो आकार के लिए अनुकूलन अचानक भुगतान करना शुरू कर देता है।

जब मैंने आखिरी बार जाँच की, तो कंपाइलर इतना स्मार्ट नहीं है कि वह सभी मामलों में इसका पता लगा सके।

आपके मामले में, -O3 संभवतः दो कैश लाइनों के लिए पर्याप्त कोड बनाता है, लेकिन -O एक कैश लाइन में फिट बैठता है।


1
आप कितना संरेखित करना चाहते हैं उन संरेखित करें = पैरामीटर कैश लाइनों के आकार से संबंधित हैं?
जोशुआ

मुझे अब कोई परवाह नहीं है: यह मेरी मशीन पर दिखाई नहीं दे रहा है। और -falign-*=16झंडे पास करके , सब कुछ वापस सामान्य हो जाता है, सब कुछ लगातार व्यवहार करता है। जहां तक ​​मेरा सवाल है, यह सवाल हल हो गया है।
अली

7

मैं इस क्षेत्र का कोई विशेषज्ञ नहीं हूं, लेकिन मुझे याद है कि आधुनिक प्रोसेसर काफी संवेदनशील होते हैं जब यह शाखा भविष्यवाणी की बात आती है । शाखाओं की भविष्यवाणी करने के लिए उपयोग किए जाने वाले एल्गोरिदम हैं (या कम से कम उन दिनों में जब मैंने कोड कोड लिखा था) कोड के कई गुणों के आधार पर, एक लक्ष्य की दूरी और दिशा पर।

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

उस ने कहा, मुझे नहीं पता कि इसे कैसे सत्यापित किया जाए और मैं आपको यह बताना चाहता हूं कि यह कुछ ऐसा हो सकता है जिसे आप देखना चाहते हैं।


धन्यवाद। मैंने इसके साथ खेला: मैं केवल स्वैप करके add()और work()यदि गति प्राप्त करता हूं-O2 यह पारित हो जाता है। अन्य सभी मामलों में कोड स्वैप करके काफी धीमा हो जाता है। सप्ताह के अंत के दौरान, मैंने शाखा की भविष्यवाणी / गलत-भविष्यवाणी के आंकड़ों का भी विश्लेषण किया perfऔर मुझे ऐसा कुछ भी नज़र नहीं आया जो इस अजीब व्यवहार को समझा सके। एकमात्र सुसंगत परिणाम यह है कि धीमे मामले में लूप में कॉल करने के तुरंत बाद लाइन perfमें 100.0 add()और बड़े मूल्य पर रिपोर्ट add()करता है। ऐसा लग रहा है कि हम add()धीमी गति के मामले में किसी कारण से नहीं बल्कि तेजी से रन बना रहे हैं।
अली

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