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




performance gcc (4)

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

time ./test 0 0 के परिणाम यहां दिए गए हैं 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.7.3 , 4.7.3 , और 4.8.1 ) के तीन संस्करणों में बाइनरी का उत्पादन काफी अलग प्रदर्शन के साथ होता है, लेकिन असेंबली कोड में केवल सूक्ष्म भिन्नताएं होती हैं। अब तक, मुझे इस तथ्य का कोई स्पष्टीकरण नहीं है।

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

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 से विधानसभा ( gcc-4.7.3 -Os सेकेंड में निष्पादित):

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 से विधानसभा ( gcc-4.8.1 -Os 9 4 सेकेंड में निष्पादित):

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

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

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

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 , -Os , इस प्रोग्राम को निष्पादित करने के लिए 0.38 एस लेता है, और 0.44 एस अगर इसे -O2 या -O3 साथ संकलित किया जाता है। इन बार लगातार प्राप्त होते हैं और व्यावहारिक रूप से कोई शोर नहीं (जीसीसी 4.7.2, x86_64 जीएनयू / लिनक्स, इंटेल कोर i5-3320M)।

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

-Os और -Os साथ -Os असेंबली यहां दी गई है।

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

अगर मुझे सही लगता है, तो ये स्टैक संरेखण के लिए पैडिंग हैं। एनसीसी के साथ जीसीसी पैड क्यों काम करता है? यह उम्मीद में किया जाता है कि कोड तेजी से चल जाएगा, लेकिन स्पष्ट रूप से यह अनुकूलन मेरे मामले में पीछे हट गया।

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

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

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

अद्यतन करें:

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

-ओएस सभी-ओ 2 अनुकूलन सक्षम करता है [लेकिन] -ओएस निम्नलिखित अनुकूलन झंडे को अक्षम करता है:

  -falign-functions  -falign-jumps  -falign-loops <br/>
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition <br/>
  -fprefetch-loop-arrays <br/>

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

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

अद्यतन 2:

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

  • -O2 -fno-omit-frame-pointer 0.37 एस

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

  • -S -O2 फिर मैन्युअल रूप से work() 0.37s के बाद add() की असेंबली को ले जा रहा है

  • -ओ 2 0.44 एस

ऐसा लगता है कि कॉल साइट से 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 लिए stalled-cycles-frontend तुलना करना मुझे समझ में नहीं आता है।)

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


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

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

... -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1 । मैं इन विकल्पों के साथ सबकुछ संकलित कर रहा हूं, जो सादा- -O2 से तेज़ थे, हर बार जब मैं 15 साल तक मापने के लिए परेशान था।

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

अगर मुझे सही लगता है, तो ये स्टैक संरेखण के लिए पैडिंग हैं।

नहीं, इसमें स्टैक के साथ कुछ भी नहीं है, डिफ़ॉल्ट रूप से जेनरेट किए गए एनओपी और विकल्प- फाइनिग - * = 1 रोक कोड संरेखण के लिए हैं।

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

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

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


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

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

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


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

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

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

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






assembly