जब सीमा 959 है, लेकिन 960 नहीं है तो एक साधारण लूप को क्यों अनुकूलित किया गया है?


131

इस सरल लूप पर विचार करें:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 959; i++)
    p += 1;
  return p;
}

यदि आप gcc 7 (स्नैपशॉट) या क्लैंग (ट्रंक) के साथ संकलन करते हैं, तो आपको -march=core-avx2 -Ofastकुछ समान मिलता है।

.LCPI0_0:
        .long   1148190720              # float 960
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        ret

दूसरे शब्दों में, यह केवल बिना पाश के 960 का उत्तर निर्धारित करता है।

हालाँकि अगर आप कोड को इसमें बदलते हैं:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 960; i++)
    p += 1;
  return p;
}

उत्पादित विधानसभा वास्तव में लूप योग करता है? उदाहरण के लिए क्लैंग देता है:

.LCPI0_0:
        .long   1065353216              # float 1
.LCPI0_1:
        .long   1086324736              # float 6
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        vxorps  ymm1, ymm1, ymm1
        mov     eax, 960
        vbroadcastss    ymm2, dword ptr [rip + .LCPI0_1]
        vxorps  ymm3, ymm3, ymm3
        vxorps  ymm4, ymm4, ymm4
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        vaddps  ymm0, ymm0, ymm2
        vaddps  ymm1, ymm1, ymm2
        vaddps  ymm3, ymm3, ymm2
        vaddps  ymm4, ymm4, ymm2
        add     eax, -192
        jne     .LBB0_1
        vaddps  ymm0, ymm1, ymm0
        vaddps  ymm0, ymm3, ymm0
        vaddps  ymm0, ymm4, ymm0
        vextractf128    xmm1, ymm0, 1
        vaddps  ymm0, ymm0, ymm1
        vpermilpd       xmm1, xmm0, 1   # xmm1 = xmm0[1,0]
        vaddps  ymm0, ymm0, ymm1
        vhaddps ymm0, ymm0, ymm0
        vzeroupper
        ret

यह क्यों है और यह क्लैंग और जीसीसी के लिए बिल्कुल समान क्यों है?


एक ही पाश के लिए सीमा अगर आप को बदलने के floatसाथ double479. है यह फिर जीसीसी और बजना के लिए ही है।

अपडेट १

यह पता चला है कि जीसी 7 (स्नैपशॉट) और क्लैंग (ट्रंक) बहुत अलग तरह से व्यवहार करते हैं। क्लैंग 960 से कम सभी सीमाओं के लिए छोरों का अनुकूलन करता है जहां तक ​​मैं बता सकता हूं। दूसरी ओर gcc सटीक मान के लिए संवेदनशील है और इसकी ऊपरी सीमा नहीं है। उदाहरण के लिए यह लूप को ऑप्टिमाइज़ नहीं करता है जब सीमा 200 (साथ ही कई अन्य मान) होती है, लेकिन यह तब होता है जब सीमा 202 और 20002 (साथ ही कई अन्य मान) होती है।


3
सुल्तान का शायद मतलब यह है कि 1) संकलक लूप को अनियंत्रित करता है और 2) एक बार अनियंत्रित होने के बाद यह देखता है कि योग संचालन को एक में बांटा जा सकता है। यदि लूप को अनियंत्रित नहीं किया जाता है, तो संचालन को समूहीकृत नहीं किया जा सकता है।
जीन फ़्राँस्वा Fabre

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

3
@ हंसपैंट यह भी 959 से छोटे किसी भी नंबर के लिए अनुकूलित है।
एलियनोरा

6
यह आमतौर पर एक पागल राशि को नियंत्रित करने के बजाय प्रेरण चर उन्मूलन के साथ नहीं किया जाएगा? 959 के एक कारक द्वारा अनियंत्रित होना पागल है।
हेरोल्ड

4
@ लिलोनोरा मैंने उस कंपिलर एक्सप्लोरर के साथ खेला और निम्नलिखित लगता है (केवल gcc स्नैपशॉट के बारे में बात करते हुए): यदि लूप काउंट 4 से अधिक है और कम से कम 72 है, तो लूप अनियंत्रित नहीं है (या यों कहें, अनियंत्रित है a) 4 का कारक); अन्यथा, पूरे लूप को एक स्थिरांक द्वारा बदल दिया जाता है - भले ही लूप काउंट 2000000001 हो। मेरा संदेह: समयपूर्व अनुकूलन (जैसा कि, एक समयपूर्व "हे, 4 का एक बहु, यह अनियंत्रित करने के लिए अच्छा है" जो आगे के अनुकूलन को अवरुद्ध करता है) अधिक पूरी तरह से "इस लूप के साथ वैसे भी क्या है?")
हेगन वॉन एटिजन

जवाबों:


88

टी एल; डॉ

डिफ़ॉल्ट रूप से, वर्तमान स्नैपशॉट जीसीसी 7 असंगत व्यवहार करता है, जबकि पिछले संस्करणों में डिफ़ॉल्ट सीमा होती है PARAM_MAX_COMPLETELY_PEEL_TIMES, जो 16 है। इसे कमांड-लाइन से ओवरराइड किया जा सकता है।

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

जीसीसी संस्करण <= 6.3.0

जीसीसी के लिए प्रासंगिक अनुकूलन विकल्प है -fpeel-loops, जो अप्रत्यक्ष रूप से ध्वज के साथ सक्षम है -Ofast(जोर मेरा है):

पील्स लूप्स जिसके लिए पर्याप्त जानकारी है कि वे ज्यादा रोल नहीं करते हैं (प्रोफ़ाइल प्रतिक्रिया या स्थिर विश्लेषण से )। यह पूर्ण लूप छीलने (यानी छोटे निरंतर पुनरावृत्तियों के साथ छोरों को हटाने) को भी चालू करता है ।

के साथ सक्षम -O3और / या -fprofile-use

अधिक विवरण जोड़कर प्राप्त किया जा सकता है -fdump-tree-cunroll:

$ head test.c.151t.cunroll 

;; Function f (f, funcdef_no=0, decl_uid=1919, cgraph_uid=0, symbol_order=0)

Not peeling: upper bound is known so can unroll completely

संदेश से है /gcc/tree-ssa-loop-ivcanon.c:

if (maxiter >= 0 && maxiter <= npeel)
    {
      if (dump_file)
        fprintf (dump_file, "Not peeling: upper bound is known so can "
         "unroll completely\n");
      return false;
    }

इसलिए try_peel_loopफंक्शन रिटर्न false

अधिक क्रिया उत्पादन के साथ पहुँचा जा सकता है -fdump-tree-cunroll-details:

Loop 1 iterates 959 times.
Loop 1 iterates at most 959 times.
Not unrolling loop 1 (--param max-completely-peeled-times limit reached).
Not peeling: upper bound is known so can unroll completely

यह संभव है कि सीमा max-completely-peeled-insns=nऔर पाराम के साथ मरहम लगाकर max-completely-peel-times=n:

max-completely-peeled-insns

एक पूरी तरह से छील लूप के शिलालेख की अधिकतम संख्या।

max-completely-peel-times

पूर्ण छीलने के लिए उपयुक्त होने के लिए एक लूप की पुनरावृत्तियों की अधिकतम संख्या।

इंसन्स के बारे में अधिक जानने के लिए, आप जीसीसी इंटर्नल मैनुअल का उल्लेख कर सकते हैं ।

उदाहरण के लिए, यदि आप निम्नलिखित विकल्पों का संकलन करते हैं:

-march=core-avx2 -Ofast --param max-completely-peeled-insns=1000 --param max-completely-peel-times=1000

फिर कोड में बदल जाता है:

f:
        vmovss  xmm0, DWORD PTR .LC0[rip]
        ret
.LC0:
        .long   1148207104

बजना

मुझे यकीन नहीं है कि क्लैंग वास्तव में क्या करता है और इसकी सीमाओं को कैसे मोड़ना है, लेकिन जैसा कि मैंने देखा, आप इसे लूप को अनियंत्रित प्रगति के साथ चिह्नित करके अंतिम मूल्य का मूल्यांकन करने के लिए मजबूर कर सकते हैं , और यह इसे पूरी तरह से हटा देगा:

#pragma unroll
for (int i = 0; i < 960; i++)
    p++;

में परिणाम:

.LCPI0_0:
        .long   1148207104              # float 961
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        ret

इस बहुत अच्छे उत्तर के लिए धन्यवाद। जैसा कि दूसरों ने बताया है, जीसीसी सटीक सीमा आकार के प्रति संवेदनशील प्रतीत होता है। उदाहरण के लिए यह 912 godbolt.org/g/EQJHvT के लिए लूप को खत्म करने में विफल रहता है । उस मामले में fdump-tree-cunroll-details क्या कहता है?
एलीनोरा

वास्तव में 200 भी इस समस्या है। यह सब gcc 7 के एक स्नैपशॉट में है जो गॉडबॉल्ट प्रदान करता है। godbolt.org/g/Vg3SVs यह बिल्कुल भी लागू नहीं होता है।
एलियनोरा

13
आप छीलने के यांत्रिकी की व्याख्या करते हैं, लेकिन यह नहीं कि 960 की प्रासंगिकता क्या है या एक सीमा भी क्यों है
MM

1
@MM: छीलने का व्यवहार जीसीसी 6.3.0 और नवीनतम स्नैपचैट के बीच पूरी तरह से अलग है। पूर्व के मामले में, मुझे दृढ़ता से संदेह है, कि हार्ड-कोडित सीमा को PARAM_MAX_COMPLETELY_PEEL_TIMESपरम द्वारा लागू किया जाता है , जिसे /gcc/params.def:321मूल्य 16 के साथ परिभाषित किया गया है।
ग्रेज़ोगोरज़ स्ज़ेपकोव्स्की

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

19

सुलतान की टिप्पणी पढ़ने के बाद, मुझे लगता है कि:

  1. संकलक पूरी तरह से लूप को नियंत्रित करता है यदि लूप काउंटर स्थिर है (और बहुत अधिक नहीं है)

  2. एक बार जब यह अनियंत्रित हो जाता है, तो संकलक देखता है कि योग संचालन को एक में बांटा जा सकता है।

यदि लूप किसी कारण से अनियंत्रित नहीं होता है (यहां: यह बहुत अधिक स्टेटमेंट जेनरेट करेगा 1000 ), तो ऑपरेशंस को समूहीकृत नहीं किया जा सकता।

संकलक यह देख सकता है कि 1000 स्टेटमेंट्स का एकल एकल जोड़ के बराबर होना, लेकिन ऊपर वर्णित चरण 1 और 2 दो अलग-अलग अनुकूलन हैं, इसलिए यह अनियंत्रित होने का "जोखिम" नहीं ले सकता है, न कि यह जानने के लिए कि ऑपरेशन को समूहीकृत किया जा सकता है (उदाहरण: एक फ़ंक्शन कॉल को समूहीकृत नहीं किया जा सकता)।

नोट: यह एक कोने का मामला है: जो एक ही चीज़ को फिर से जोड़ने के लिए एक लूप का उपयोग करता है? उस मामले में, संकलक संभव अनियंत्रित / अनुकूलन पर भरोसा न करें; सीधे एक निर्देश में उचित संचालन लिखें।


1
तो क्या आप उस not too highहिस्से पर ध्यान केंद्रित कर सकते हैं ? मेरा मतलब है कि मामले में जोखिम क्यों नहीं है 100? मैंने कुछ अनुमान लगाया है ... ऊपर मेरी टिप्पणी में..इसका कारण हो सकता है?
user2736738

मुझे लगता है कि संकलक को फ्लोटिंग पॉइंट अशुद्धि के बारे में पता नहीं है कि यह ट्रिगर हो सकता है। मुझे लगता है कि यह सिर्फ एक अनुदेश आकार की सीमा है। आपके पास max-unrolled-insnsके साथmax-unrolled-times
जीन फ़्राँस्वा Fabre

आह यह मेरे विचार या अनुमान की तरह था ... काश एक और स्पष्ट तर्क मिल सके।
user2736738

5
दिलचस्प बात यह है कि अगर आप बदलना floatएक करने के लिए int, जीसीसी संकलक करने के लिए अपने प्रेरण चर अनुकूलन के कारण पुनरावृत्ति संख्या की परवाह किए बिना पाश शक्ति को कम करने, सक्षम है ( -fivopts)। लेकिन उन लोगों के लिए काम करने के लिए प्रतीत नहीं होता है float
तेवियन बार्न्स

1
@CortAmmon राइट, और मुझे याद है कि कुछ लोग जो हैरान थे और परेशान थे कि जीसीसी एमपीएफआर का उपयोग बहुत बड़ी संख्याओं की ठीक-ठीक गणना करने के लिए करता है, जो बराबर फ्लोटिंग पॉइंट ऑपरेशंस की तुलना में अलग-अलग परिणाम देता है, जिसमें त्रुटि और सटीक नुकसान होता है। यह दिखाने के लिए जाता है कि कई लोग फ्लोटिंग पॉइंट की गणना गलत तरीके से करते हैं।
ज़ेन लिंक्स

12

बहुत अच्छा सवाल!

आपको लगता है कि पुनरावृत्तियों की संख्या पर कोई सीमा नहीं लगी है या कोड को सरल बनाते समय कंपाइलर इनलाइन को आज़माता है। जैसा कि ग्रेज़गोरज़ ज़ेप्टकोव्स्की द्वारा प्रलेखित किया गया है, इन सीमाओं को व्यावहारिक या कमांड लाइन विकल्पों के साथ मोड़ने के लिए विशिष्ट विशिष्ट तरीके हैं।

आप अलग-अलग संकलक और विकल्प उत्पन्न कोड को कैसे प्रभावित करते हैं, इसकी तुलना करने के लिए आप गॉडबोल्ट के कंपाइलर एक्सप्लोरर के साथ भी खेल सकते हैं : gcc 6.2और icc 17अभी भी 960 के लिए कोड को इनलाइन करते हैं, जबकि clang 3.9(डिफ़ॉल्ट गॉडबोल्ट कॉन्फ़िगरेशन के साथ नहीं है, यह वास्तव में 73 पर इनलाइन करना बंद कर देता है)।


मैंने इस प्रश्न को संपादित कर दिया है कि यह मेरे द्वारा उपयोग किए जा रहे gcc और clang के संस्करणों को स्पष्ट कर सकता है। Godbolt.org/g/FfwWjL देखें । मैं का उपयोग कर रहा हूँ -उदाहरण के लिए उदाहरण।
एलीनोरा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.