मैंने पहली बार 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
, न कि केवल ऊपर दिए गए। कैश मिस बहुत शोर कर रहे हैं और निष्पादन समय के साथ कोई संबंध नहीं है।