GCC एक * a * a * a * a (a (* a * a) * (a * a * a) ऑप्टिमाइज़ क्यों नहीं करता है?


2120

मैं एक वैज्ञानिक अनुप्रयोग पर कुछ संख्यात्मक अनुकूलन कर रहा हूं। एक बात जो मैंने देखी वह यह है कि जीसीसी कॉल pow(a,2)को इसमें संकलित करके अनुकूलित करेगा a*a, लेकिन कॉल pow(a,6)अनुकूलित नहीं है और वास्तव में लाइब्रेरी फ़ंक्शन को कॉल करेगा pow, जो प्रदर्शन को धीमा कर देता है। (इसके विपरीत, इंटेल C ++ कंपाइलर , निष्पादन योग्य icc, लाइब्रेरी कॉल को समाप्त कर देगा pow(a,6)।)

मैं इस बारे में उत्सुक हूं कि जब मैंने जीसीसी 4.5.1 और विकल्प " " का उपयोग करके प्रतिस्थापित pow(a,6)किया , तो यह 5 निर्देशों का उपयोग करता है :a*a*a*a*a*a-O3 -lm -funroll-loops -msse4mulsd

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13

जबकि अगर मैं लिखता हूं (a*a*a)*(a*a*a), तो यह उत्पादन होगा

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13

जो 3. से कई गुणा निर्देश कम करता iccहै। समान व्यवहार है।

कंपाइलर इस ऑप्टिमाइज़ेशन ट्रिक को क्यों नहीं पहचानते?


13
"Pow (a, 6) को पहचानने" का क्या अर्थ है?
वरुण मदीठ

659
उम ... आप जानते हैं कि एक एक एक एक एक एक और (एक एक एक) * (एक एक * क) चल बिन्दु संख्या के साथ ही, आप नहीं कर रहे हैं नहीं है? आपको -funsafe-math या -ffast-math या उसके लिए कुछ का उपयोग करना होगा।
डेमोन

106
मेरा सुझाव है कि आप डेविड गोल्डबर्ग द्वारा फ्लोटिंग पॉइंट अरिथमेटिक के बारे में "क्या हर कंप्यूटर साइंटिस्ट को पता होना चाहिए" डाउनलोड करें : download.oracle.com/docs/cd/E19957-01/806-3568/… जिसके बाद आपको अधिक पूर्ण समझ होगी टार पिट है कि आप में चला गया है!
फिल आर्मस्ट्रांग

189
एक पूरी तरह से उचित सवाल। 20 साल पहले मैंने एक ही सामान्य प्रश्न पूछा, और उस एकल अड़चन को कुचलकर, मोंटे कार्लो सिमुलेशन के निष्पादन समय को 21 घंटे से घटाकर 7 घंटे कर दिया। इनर लूप के कोड को इस प्रक्रिया में 13 ट्रिलियन बार निष्पादित किया गया था, लेकिन इसने सिमुलेशन को एक ओवर-नाइट विंडो में मिला दिया। (नीचे उत्तर देखें)

23
शायद (a*a)*(a*a)*(a*a)मिश्रण में भी फेंक दें। गुणा की समान संख्या, लेकिन शायद अधिक सटीक।
रोक क्रज

जवाबों:


2738

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

परिणामस्वरूप, अधिकांश कंपाइलर फ़्लोटिंग पॉइंट गणनाओं को पुन: व्यवस्थित करने के बारे में बहुत रूढ़िवादी हैं जब तक कि वे यह सुनिश्चित नहीं कर सकते कि उत्तर समान रहेगा, या जब तक आप उन्हें नहीं बताएंगे कि आप संख्यात्मक सटीकता के बारे में परवाह नहीं करते हैं। उदाहरण के लिए: gcc का -fassociative-mathविकल्प जो gcc को फ्लोटिंग पॉइंट ऑपरेशंस को पुन: व्यवस्थित करने की अनुमति देता है, या यहाँ तक कि वह -ffast-mathविकल्प जो गति के विरुद्ध सटीकता के और भी अधिक आक्रामक व्यापार की अनुमति देता है।


10
हाँ। -फ़ास्ट-गणित के साथ यह ऐसा अनुकूलन कर रहा है। अच्छा विचार! लेकिन चूंकि हमारे कोड की गति की तुलना में अधिक सटीकता की चिंता है, इसलिए इसे पारित नहीं करना बेहतर हो सकता है।
एक्सिस

19
IIRC C99 कंपाइलर को ऐसे "असुरक्षित" FP ऑप्टिमाइज़ेशन करने की अनुमति देता है, लेकिन GCC (x87 के अलावा किसी भी चीज़ पर) IEEE 754 को फॉलो करने के लिए एक उचित प्रयास करता है - यह "त्रुटि सीमा" नहीं है; केवल एक सही उत्तर है
टीसी

14
कार्यान्वयन विवरण powन तो यहां हैं और न ही हैं; यह उत्तर भी संदर्भ नहीं देता है pow
स्टीफन कैनन

14
@ आई डी आर: आईसीसी को फिर से जुड़ने की अनुमति देने में चूक। यदि आप मानक-अनुरूप व्यवहार प्राप्त करना चाहते हैं, तो आपको -fp-model preciseआईसीसी के साथ सेट करने की आवश्यकता है । clangऔर gccसख्त अनुरूपता पुनर्मूल्यांकन के लिए डिफ़ॉल्ट।
स्टीफन कैनन

49
@xis, यह वास्तव में गलत नहीं -fassociative-mathहोगा; यह सिर्फ इतना है a*a*a*a*a*aऔर (a*a*a)*(a*a*a)अलग हैं। यह सटीकता के बारे में नहीं है; यह मानकों के अनुरूपता और कड़ाई से दोहराने योग्य परिणामों के बारे में है, उदाहरण के लिए किसी भी संकलक पर समान परिणाम। फ़्लोटिंग पॉइंट नंबर पहले से सटीक नहीं हैं। इसका संकलन करना शायद ही अनुचित है -fassociative-math
पॉल ड्रेपर

652

Lambdageek सही ढंग से बताते हैं क्योंकि संबद्धता चल बिन्दु संख्या के लिए नहीं रखता है, की "अनुकूलन" किa*a*a*a*a*aकरने के लिए(a*a*a)*(a*a*a)बदल सकते हैं। यही कारण है कि यह C99 (जब तक कि विशेष रूप से उपयोगकर्ता द्वारा संकलित ध्वज या प्रज्ञा के माध्यम से अनुमति नहीं है) द्वारा अस्वीकृत हो जाता है। आम तौर पर, धारणा यह है कि प्रोग्रामर ने लिखा है कि उसने एक कारण के लिए क्या किया, और संकलक को उसका सम्मान करना चाहिए। यदि आप चाहते हैं(a*a*a)*(a*a*a), तो वह लिखें।

यह लिखने के लिए दर्द हो सकता है, हालांकि; जब आप उपयोग करते हैं तो कंपाइलर सिर्फ [आप जिसे मानते हैं] सही काम नहीं कर सकता है pow(a,6)? क्योंकि ऐसा करना गलत होगा । एक अच्छा गणित पुस्तकालय के साथ एक मंच पर, या pow(a,6)तो की तुलना में काफी अधिक सटीक है । बस कुछ डेटा प्रदान करने के लिए, मैंने अपने मैक प्रो पर एक छोटा सा प्रयोग किया, जिसमें सभी एकल-सटीक फ्लोटिंग संख्याओं के लिए ^ 6 का मूल्यांकन करने में सबसे खराब त्रुटि को मापा गया [1,2):a*a*a*a*a*a(a*a*a)*(a*a*a)

worst relative error using    powf(a, 6.f): 5.96e-08
worst relative error using (a*a*a)*(a*a*a): 2.94e-07
worst relative error using     a*a*a*a*a*a: 2.58e-07

का उपयोग करते हुए powएक गुणा पेड़ के बजाय एक से बंधे त्रुटि कम कर देता है 4 के कारक । कंपाइलर्स को (और आमतौर पर नहीं) "अनुकूलन" करना चाहिए जो त्रुटि को बढ़ाते हैं जब तक कि उपयोगकर्ता द्वारा ऐसा करने के लिए लाइसेंस प्राप्त न हो (जैसे कि -ffast-math)।

ध्यान दें कि जीसीसी __builtin_powi(x,n)एक विकल्प के रूप में प्रदान करता है pow( ), जो एक इनलाइन गुणा वृक्ष उत्पन्न करना चाहिए। यदि आप प्रदर्शन के लिए सटीकता का व्यापार करना चाहते हैं, तो इसका उपयोग करें, लेकिन तेज़-गणित को सक्षम नहीं करना चाहते हैं।


29
यह भी ध्यान दें कि विज़ुअल C ++ पॉव () का एक 'एन्हांस्ड' संस्करण प्रदान करता है। के _set_SSE2_enable(<flag>)साथ कॉल करके flag=1, यदि संभव हो तो यह SSE2 का उपयोग करेगा। यह सटीकता को थोड़ा कम करता है, लेकिन गति (कुछ मामलों में) में सुधार करता है। MSDN: _set_SSE2_enable () और pow ()
TkTech

18
@ टेक: किसी भी कम की गई सटीकता Microsoft के कार्यान्वयन के कारण होती है, न कि उपयोग किए गए रजिस्टरों के आकार के कारण। यदि लाइब्रेरी लेखक इतना प्रेरित है, तो केवल 32-बिट रजिस्टरों का उपयोग करके सही ढंग से राउंड देना संभव है pow। SSE- आधारित powकार्यान्वयन ऐसे हैं जो अधिकांश x87-आधारित कार्यान्वयनों की तुलना में अधिक सटीक हैं , और ऐसे कार्यान्वयन भी हैं जो गति के लिए कुछ सटीकता से व्यापार करते हैं।
स्टीफन कैनन

9
@ टेक: बेशक, मैं सिर्फ यह स्पष्ट करना चाहता था कि सटीकता में कमी पुस्तकालय लेखकों द्वारा किए गए विकल्पों के कारण है, एसएसई के उपयोग के लिए आंतरिक नहीं है।
स्टीफन कैनन

7
मुझे यह जानने में दिलचस्पी है कि आपने रिश्तेदार त्रुटियों की गणना के लिए यहां "स्वर्ण मानक" के रूप में क्या उपयोग किया है - मुझे आमतौर पर उम्मीद होगी a*a*a*a*a*aकि यह होगा , लेकिन यह स्पष्ट रूप से ऐसा नहीं है! :)
j_random_hacker

8
@j_random_hacker: के बाद से मैं एक स्वर्ण मानक के लिए एकल परिशुद्धता परिणाम, डबल परिशुद्धता suffices की तुलना की गई थी - एक से त्रुटि एक एक एक एक एक डबल गणना * बेहद है एकल परिशुद्धता संगणना में से किसी की त्रुटि से छोटा है।
स्टीफन कैनन

168

एक और इसी तरह के मामले: सबसे compilers नहीं होगा का अनुकूलन a + b + c + dकरने के लिए (a + b) + (c + d)के रूप में दिया (यानी के रूप में और मूल्यांकन यह (यह एक अनुकूलन के बाद से दूसरी अभिव्यक्ति बेहतर pipelined किया जा सकता है) (((a + b) + c) + d))। यह भी कोने के मामलों के कारण है:

float a = 1e35, b = 1e-5, c = -1e35, d = 1e-5;
printf("%e %e\n", a + b + c + d, (a + b) + (c + d));

यह आउटपुट 1.000000e-05 0.000000e+00


10
यह बिल्कुल वैसा नहीं है। गुणन / विभाजनों के क्रम को बदल दें (0 से विभाजन को छोड़कर) राशि / घटाव के परिवर्तन के क्रम से अधिक सुरक्षित है। मेरी विनम्र राय में, संकलक को mults./divs को जोड़ने का प्रयास करना चाहिए। क्योंकि ऐसा करने से ऑपरेशन की कुल संख्या कम हो जाती है और प्रदर्शन लाभ के साथ सटीक लाभ भी होता है।
कॉफिडेवलपर्स

4
@DarioOO: यह कोई सुरक्षित नहीं है। गुणा और भाग, घातांक के जोड़ और घटाव के समान होते हैं, और आदेश को बदलने से आसानी से अस्थायी लोग घातांक की संभावित सीमा से अधिक हो सकते हैं। (बिल्कुल वैसा ही नहीं, क्योंकि प्रतिपादक को सटीकता का नुकसान नहीं होता है ... लेकिन प्रतिनिधित्व अभी भी काफी सीमित है, और पुन: व्यवस्थित करने से अप्राप्य मूल्य हो सकते हैं)
बेन वायगेट

8
मुझे लगता है कि आप कुछ पथरी की पृष्ठभूमि को याद कर रहे हैं। 2 संख्याओं को गुणा और विभाजित करना त्रुटि की समान मात्रा का परिचय देता है। घटाते / घटाते समय 2 संख्याएँ विशेष रूप से तब बड़ी त्रुटि का परिचय दे सकती हैं जब 2 संख्याएँ अलग-अलग परिमाणों का क्रम होती हैं, इसलिए यह उप-जोड़ की तुलना में फिर से व्यवस्थित mul / divide सुरक्षित है क्योंकि यह अंतिम त्रुटि में मामूली परिवर्तन का परिचय देता है।
कॉफिडेवलपर्स

8
@ डायरो: जोखिम अलग है mul / div के साथ: पुन: व्यवस्थित करना या तो अंतिम परिणाम में एक नगण्य परिवर्तन करता है, या प्रतिपादक कुछ बिंदु पर ओवरफ्लो करता है (जहां यह पहले नहीं होता) और परिणाम बड़े पैमाने पर भिन्न होता है (संभावित रूप से / या) 0)।
पीटर कॉर्डेस

@GameDeveloper अप्रत्याशित तरीके से सटीक लाभ प्राप्त करना बेहद समस्याग्रस्त है।
जिज्ञासु

80

फोरट्रान (वैज्ञानिक कंप्यूटिंग के लिए डिज़ाइन किया गया) में एक अंतर्निहित पावर ऑपरेटर है, और जहां तक ​​मुझे पता है कि फोरट्रान कंपाइलर्स आमतौर पर पूर्णांक शक्तियों को एक समान फैशन में बढ़ाने का अनुकूलन करेंगे जो आप वर्णन करते हैं। C / C ++ दुर्भाग्य से पावर ऑपरेटर नहीं है, केवल लाइब्रेरी फ़ंक्शन pow()। यह स्मार्ट कंपाइलरों को powविशेष रूप से इलाज करने और विशेष मामलों के लिए तेजी से गणना करने से नहीं रोकता है , लेकिन ऐसा लगता है कि वे इसे आमतौर पर कम करते हैं ...

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

template<unsigned N> struct power_impl;

template<unsigned N> struct power_impl {
    template<typename T>
    static T calc(const T &x) {
        if (N%2 == 0)
            return power_impl<N/2>::calc(x*x);
        else if (N%3 == 0)
            return power_impl<N/3>::calc(x*x*x);
        return power_impl<N-1>::calc(x)*x;
    }
};

template<> struct power_impl<0> {
    template<typename T>
    static T calc(const T &) { return 1; }
};

template<unsigned N, typename T>
inline T power(const T &x) {
    return power_impl<N>::calc(x);
}

जिज्ञासु के लिए स्पष्टीकरण: यह शक्तियों की गणना करने का इष्टतम तरीका नहीं ढूंढता है, लेकिन चूंकि इष्टतम समाधान एनपी-पूर्ण समस्या है और यह केवल वैसे भी छोटी शक्तियों के लिए करने योग्य है (जैसा कि उपयोग करने का विरोध किया गया है pow), उपद्रव का कोई कारण नहीं है। विस्तार के साथ।

तो बस के रूप में उपयोग करें power<6>(a)

इससे शक्तियों को टाइप करना आसान हो जाता है ( aParens के साथ 6 s को स्पेल करने की कोई आवश्यकता नहीं है ), और आपको इस प्रकार के अनुकूलन की अनुमति -ffast-mathदेता है, यदि आपके पास कुछ सटीक निर्भरता है जैसे कि क्षतिपूर्ति योग (उदाहरण जहां संचालन का क्रम आवश्यक है) ।

आप शायद यह भी भूल सकते हैं कि यह C ++ है और इसे C प्रोग्राम में उपयोग करें (यदि यह C ++ कंपाइलर के साथ संकलित है)।

आशा है कि यह उपयोगी हो सकता है।

संपादित करें:

मुझे अपने कंपाइलर से यही मिलता है:

के लिए a*a*a*a*a*a,

    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0

के लिए (a*a*a)*(a*a*a),

    movapd  %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm1, %xmm0
    mulsd   %xmm0, %xmm0

के लिए power<6>(a),

    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm0, %xmm1

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

7
आधुनिक प्रोसेसर पर, गति विलंबता द्वारा सीमित है। उदाहरण के लिए, पांच चक्रों के बाद गुणा का परिणाम उपलब्ध हो सकता है। उस स्थिति में, कुछ शक्ति बनाने का सबसे तेज़ तरीका खोजना अधिक मुश्किल हो सकता है।
gnasher729

3
आप बिजली के पेड़ को खोजने की कोशिश कर सकते हैं जो सापेक्ष गोलाई त्रुटि के लिए सबसे ऊपरी ऊपरी सीमा देता है, या सबसे कम औसत सापेक्ष गोलाई त्रुटि।
gnasher729

1
बूस्ट के पास इसके लिए समर्थन भी है, उदाहरण के लिए बढ़ावा देना :: गणित :: pow <6> (n); मुझे लगता है कि यह सामान्य कारकों को भी हटाकर गुणा की संख्या को कम करने की कोशिश करता है।
Gast128

ध्यान दें कि अंतिम एक (** 2) के बराबर है ** 3
minmaxavg

62

जीसीसी वास्तव में अनुकूलन करता है a*a*a*a*a*aकरने के लिए (a*a*a)*(a*a*a)जब एक एक पूर्णांक है। मैंने इस कमांड के साथ कोशिश की:

$ echo 'int f(int x) { return x*x*x*x*x*x; }' | gcc -o - -O2 -S -masm=intel -x c -

बहुत सारे जीसीसी झंडे हैं लेकिन फैंसी कुछ भी नहीं। उनका मतलब है: स्टड से पढ़ें; O2 अनुकूलन स्तर का उपयोग करें; बाइनरी के बजाय आउटपुट असेंबली भाषा लिस्टिंग; लिस्टिंग में इंटेल असेंबली भाषा सिंटैक्स का उपयोग करना चाहिए; इनपुट सी भाषा में है (आमतौर पर भाषा इनपुट फ़ाइल एक्सटेंशन से निकाली गई है, लेकिन स्टड से पढ़ते समय कोई फ़ाइल एक्सटेंशन नहीं है); और stdout को लिखें।

यहाँ आउटपुट का महत्वपूर्ण हिस्सा है। मैंने इसे कुछ टिप्पणियों के साथ एनोटेट किया है जो यह दर्शाता है कि विधानसभा भाषा में क्या हो रहा है:

; x is in edi to begin with.  eax will be used as a temporary register.
mov  eax, edi  ; temp = x
imul eax, edi  ; temp = x * temp
imul eax, edi  ; temp = x * temp
imul eax, eax  ; temp = temp * temp

मैं लिनक्स टकसाल 16 पेट्रा, एक उबंटू व्युत्पन्न पर जीसीसी प्रणाली का उपयोग कर रहा हूं। यहाँ gcc संस्करण है:

$ gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1

जैसा कि अन्य पोस्टरों ने उल्लेख किया है, यह विकल्प फ्लोटिंग पॉइंट में संभव नहीं है, क्योंकि फ्लोटिंग पॉइंट अंकगणितीय साहचर्य नहीं है।


12
यह पूर्णांक गुणन के लिए कानूनी है क्योंकि दो का पूरक अतिप्रवाह अपरिभाषित व्यवहार है। अगर वहाँ एक अतिप्रवाह होने जा रहा है, तो यह कहीं न कहीं होगा, संचालन की परवाह किए बिना। इसलिए, कोई अतिप्रवाह के साथ अभिव्यक्तियाँ एक ही मूल्यांकन करती हैं, अभिव्यक्ति कि अतिप्रवाह अपरिभाषित व्यवहार है इसलिए कंपाइलर के लिए यह ठीक है कि वह बिंदु जिस पर अतिप्रवाह होता है। gcc के साथ unsigned intभी ऐसा करता है।
पीटर कॉर्ड्स

51

क्योंकि एक 32-बिट फ्लोटिंग-पॉइंट नंबर - जैसे 1.024 - 1.024 नहीं है। एक कंप्यूटर में, 1.024 एक अंतराल है: (1.024-ई) से (1.024 + ई), जहां "ई" एक त्रुटि का प्रतिनिधित्व करता है। कुछ लोग इसे महसूस करने में विफल होते हैं और यह भी मानते हैं कि उन संख्याओं से जुड़ी त्रुटियों के बिना * ए * में मनमानी-सटीक संख्याओं के गुणन के लिए एक खड़ा है। कुछ लोगों को इस बात का अहसास होने में असफल होने का कारण यह है कि शायद प्राथमिक विद्यालयों में वे गणित की गणना कर रहे हैं: केवल त्रुटियों के बिना आदर्श संख्याओं के साथ काम करना, और यह विश्वास करना कि गुणन करते समय "ई" की उपेक्षा करना ठीक है। वे "फ्लोट ए = 1.2", "ए * ए" और इसी तरह के सी कोड में "ई" निहित नहीं देखते हैं।

अधिकांश प्रोग्रामर को पहचानना चाहिए (और उस पर अमल करने में सक्षम) इस विचार को कि C अभिव्यक्ति * a * a * a * a * वास्तव में आदर्श संख्याओं के साथ काम नहीं कर रही है, GCC संकलक फिर "a *" का अनुकूलन करने के लिए स्वतंत्र होगा * a * a * a "in say" t = (a * a); t * t * t "जिसके लिए कई गुणा संख्या की आवश्यकता होती है। लेकिन दुर्भाग्य से, जीसीसी संकलक को यह नहीं पता है कि प्रोग्राम लिखने वाला प्रोग्रामर सोचता है कि "ए" एक संख्या है या बिना किसी त्रुटि के। और इसलिए जीसीसी केवल वही करेगा जो स्रोत कोड जैसा दिखता है - क्योंकि यही जीसीसी अपनी "नग्न आंखों" के साथ देखता है।

... एक बार आप जानते हैं कि प्रोग्रामर की तरह आप कर रहे हैं, तो आप "-ffast-गणित" स्विच का उपयोग कर सकते जीसीसी बताने के लिए कि "अरे, जीसीसी, मुझे पता है मैं क्या कर रहा हूँ!"। यह GCC को एक * a * a * a * a को पाठ के एक अलग टुकड़े में बदलने की अनुमति देगा - यह एक a * a * a * a * a - a से अलग दिखता है, लेकिन फिर भी त्रुटि के अंतराल के भीतर एक संख्या की गणना करता है एक * एक * एक * एक * एक * एक। यह ठीक है, क्योंकि आप पहले से ही जानते हैं कि आप अंतराल के साथ काम कर रहे हैं, आदर्श संख्या नहीं।


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

3
@ डोनल फेलो: आईईईई मानक के लिए आवश्यक है कि फ्लोटिंग-पॉइंट गणना परिणाम उत्पन्न करे जो सबसे सटीक रूप से मेल खाता है यदि स्रोत ऑपरेंड सटीक मान थे, लेकिन इसका मतलब यह नहीं है कि वे वास्तव में सटीक मूल्यों का प्रतिनिधित्व करते हैं। यह कई मामलों में 0.1 एफ के रूप में संबंध में अधिक उपयोगी है (1,677,722 +/- 0.5) / 16,777,216, जिसे उस अनिश्चितता से निहित दशमलव अंकों की संख्या के साथ प्रदर्शित किया जाना चाहिए, इसे सटीक मात्रा (1,677,722 +/) के रूप में माना जा सकता है 0.5) / 16,777,216 (जिसे 24 दशमलव अंकों को प्रदर्शित किया जाना चाहिए)।
सुपरकैट

23
@supercat: आईईईई-754 मुद्दा यह है कि फ्लोटिंग प्वाइंट डेटा पर बहुत स्पष्ट है करना सही मूल्यों का प्रतिनिधित्व; खंड 3.2 - 3.4 प्रासंगिक खंड हैं। आप निश्चित रूप से, अन्यथा उनकी व्याख्या करना चुन सकते हैं, जैसा कि आप int x = 3अर्थ के रूप में व्याख्या करना चुन सकते हैं जो कि x3 +/- 0.5 है।
स्टीफन कैनन

7
@ सुपरकैट: मैं पूरी तरह से सहमत हूं, लेकिन इसका मतलब यह नहीं है कि Distanceइसके संख्यात्मक मूल्य के बराबर नहीं है; इसका अर्थ है कि संख्यात्मक मान केवल कुछ भौतिक मात्रा के मॉडल होने का अनुमान है।
स्टीफन कैनन

10
संख्यात्मक विश्लेषण के लिए, आपका मस्तिष्क आपको धन्यवाद देगा यदि आप फ़्लोटिंग पॉइंट नंबरों की व्याख्या अंतराल के रूप में नहीं करते हैं, लेकिन सटीक मानों के रूप में (जो कि वास्तव में वे मान नहीं हैं जो आप चाहते थे)। उदाहरण के लिए, यदि x कहीं 0.1 से कम त्रुटि के साथ 4.5 का दौर है, और आप गणना (x + 1) - x करते हैं, तो "अंतराल" व्याख्या आपको 0.8 से 1.2 के अंतराल के साथ छोड़ देती है, जबकि "सटीक मान" व्याख्या बताती है आपका परिणाम अधिकतम 2 ^ (- 50) की त्रुटि के साथ 1 होगा, दोहरी सटीकता में।
gnasher729

34

किसी भी पोस्टर ने अभी तक अस्थायी अभिव्यक्तियों के संकुचन का उल्लेख नहीं किया है (आईएसओ सी मानक, 6.5 पी 8 और 7.12.2)। यदि FP_CONTRACTpragma पर सेट किया गया है ON, तो संकलक को इस तरह की अभिव्यक्ति के संबंध में अनुमति हैa*a*a*a*a*a को एक एकल ऑपरेशन , जैसे कि एक एकल गोलाई के साथ मूल्यांकन किया गया हो। उदाहरण के लिए, एक कंपाइलर इसे एक आंतरिक पावर फ़ंक्शन द्वारा प्रतिस्थापित कर सकता है जो कि तेज और अधिक सटीक दोनों है। यह विशेष रूप से दिलचस्प है क्योंकि प्रोग्रामर द्वारा सीधे स्रोत कोड में व्यवहार को आंशिक रूप से नियंत्रित किया जाता है, जबकि अंत उपयोगकर्ता द्वारा प्रदान किए गए कंपाइलर विकल्प कभी-कभी गलत तरीके से उपयोग किए जा सकते हैं।

FP_CONTRACTप्राग्मा की डिफ़ॉल्ट स्थिति कार्यान्वयन-परिभाषित है, ताकि एक कंपाइलर को डिफ़ॉल्ट रूप से इस तरह के अनुकूलन करने की अनुमति हो। इस प्रकार पोर्टेबल कोड जिसे IEEE 754 नियमों का कड़ाई से पालन करने की आवश्यकता है, उसे स्पष्ट रूप से निर्धारित करना चाहिएOFF

यदि कोई कंपाइलर इस प्रज्ञा का समर्थन नहीं करता है, तो डेवलपर को इसे सेट करने के लिए चुने जाने की स्थिति में इस तरह के किसी भी अनुकूलन से बचकर रूढ़िवादी होना चाहिए OFF

जीसीसी इस प्रगति का समर्थन नहीं करता है, लेकिन डिफ़ॉल्ट विकल्पों के साथ, यह मानता है कि यह होना चाहिए ON; इस प्रकार एक हार्डवेयर FMA के साथ लक्ष्य के लिए, यदि कोई a*b+cfma (a, b, c) में परिवर्तन को रोकना चाहता है, तो किसी को एक विकल्प प्रदान करने की आवश्यकता होती है, जैसे -ffp-contract=off(स्पष्ट रूप से प्रचार सेट करना OFF) या -std=c99(GCC को कुछ के अनुरूप बताना ) C मानक संस्करण, यहाँ C99, इस प्रकार उपरोक्त पैराग्राफ का पालन करें)। अतीत में, बाद वाला विकल्प परिवर्तन को नहीं रोक रहा था, जिसका अर्थ है कि जीसीसी इस बिंदु पर अनुरूप नहीं था: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37845


3
लंबे समय से लोकप्रिय प्रश्न कभी-कभी उनकी उम्र को दर्शाते हैं। यह सवाल 2011 में पूछा गया था और इसका जवाब दिया गया था, जब जीसीसी को तत्कालीन सी 99 मानक का बिल्कुल सम्मान नहीं करने के लिए बहाना किया जा सकता था। बेशक अब यह 2014 है, इसलिए जीसीसी ... अहम।
पास्कल क्यूक

क्या आपको एक हाल ही में स्वीकार किए गए उत्तर के बिना तुलनात्मक रूप से हाल के अस्थायी-बिंदु वाले सवालों का जवाब नहीं देना चाहिए? कफ stackoverflow.com/questions/23703408 खांसी
पास्कल कूक

मुझे पता है ... परेशान है कि gcc C99 फ़्लोटिंग-पॉइंट प्रैग्मस को लागू नहीं करता है।
डेविड मोननियाक्स

1
@DavidMonniaux pragmas परिभाषा के अनुसार लागू करने के लिए वैकल्पिक हैं।
टिम सेग्यूनी

2
@TimSeguine लेकिन अगर कोई प्रोग्मा लागू नहीं किया जाता है, तो उसके डिफ़ॉल्ट मूल्य को लागू करने के लिए सबसे अधिक प्रतिबंधात्मक होना चाहिए। मुझे लगता है कि डेविड के बारे में क्या सोच रहा था। GCC के साथ, यह अब FP_CONTRACT के लिए नियत है यदि कोई ISO C मोड का उपयोग करता है : यह अभी भी प्राग को लागू नहीं करता है, लेकिन एक ISO C मोड में, अब यह मान लेता है कि प्रैग्मा बंद है।
vinc17

28

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


3
@ वर्णगो नहीं, यह अभी भी निर्धारक है। शब्द के किसी भी अर्थ में कोई यादृच्छिकता नहीं जोड़ी जाती है।
ऐलिस

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

5
@ सारे शब्द को छोड़कर आपकी व्याख्या में भी वह क्या कहता है, यह अभी भी गलत है; IEEE 754 का संपूर्ण बिंदु, प्लेटफार्मों भर में अधिकांश (यदि सभी नहीं) संचालन के लिए समान विशेषताओं को प्रदान करने के लिए है। अब, उन्होंने प्लेटफार्मों या संकलक संस्करणों का कोई उल्लेख नहीं किया है, जो एक वैध चिंता का विषय होगा यदि आप चाहते हैं कि प्रत्येक दूरस्थ सर्वर / क्लाइंट पर हर एक ऑपरेशन समान हो .... लेकिन यह उनके बयान से स्पष्ट नहीं है। एक बेहतर शब्द "मज़बूती से समान" या कुछ और हो सकता है।
एलिस

8
@ आप शब्दार्थों पर बहस करके हर किसी का समय बर्बाद कर रहे हैं। उसका अर्थ स्पष्ट था।
लानारू

11
@Lanaru मानकों के पूरे बिंदु शब्दार्थ है; उनका अर्थ स्पष्ट रूप से स्पष्ट नहीं था।
ऐलिस

28

"पॉव" जैसे पुस्तकालय कार्यों को आमतौर पर न्यूनतम संभावित त्रुटि (सामान्य स्थिति में) प्राप्त करने के लिए सावधानीपूर्वक तैयार किया जाता है। यह आमतौर पर स्प्लीन के साथ अनुमानित कार्यों को प्राप्त करता है (पास्कल की टिप्पणी के अनुसार सबसे आम कार्यान्वयन रेमेज़ एल्गोरिथ्म का उपयोग करता हुआ प्रतीत होता है )

मूल रूप से निम्नलिखित ऑपरेशन:

pow(x,y);

लगभग किसी भी एकल गुणन या विभाजन में त्रुटि के रूप में लगभग उसी परिमाण की एक अंतर्निहित त्रुटि है ।

जबकि निम्नलिखित ऑपरेशन:

float a=someValue;
float b=a*a*a*a*a*a;

एक अंतर्निहित त्रुटि है जो एकल गुणन या विभाजन की त्रुटि से 5 गुना से अधिक है (क्योंकि आप 5 गुणा जोड़ रहे हैं)।

संकलक को वास्तव में उस तरह के अनुकूलन के प्रति सावधानी बरतनी चाहिए जो वह कर रहा है:

  1. के अनुकूलन करता है, तो pow(a,6)करने के लिए a*a*a*a*a*aयह कर सकते हैं प्रदर्शन में सुधार है, लेकिन तेजी से चल बिन्दु संख्या के लिए सटीकता को कम।
  2. यदि इसका अनुकूलन a*a*a*a*a*a करना pow(a,6)वास्तव में सटीकता को कम कर सकता है क्योंकि "ए" कुछ विशेष मूल्य था जो त्रुटि के बिना गुणन की अनुमति देता है (2 या कुछ छोटे पूर्णांक संख्या की शक्ति)
  3. यदि अनुकूलन pow(a,6)करना (a*a*a)*(a*a*a)या (a*a)*(a*a)*(a*a)अभी भी powकार्य की तुलना में सटीकता का नुकसान हो सकता है ।

सामान्य तौर पर आप जानते हैं कि मनमाने ढंग से फ्लोटिंग पॉइंट वैल्यू के लिए "पॉव" में किसी भी फंक्शन की तुलना में बेहतर सटीकता होती है, जिसे आप अंततः लिख सकते हैं, लेकिन कुछ विशेष मामलों में कई गुणा में बेहतर सटीकता और प्रदर्शन हो सकता है, यह डेवलपर के ऊपर है कि वह क्या अधिक उपयुक्त है। अंततः कोड को टिप्पणी करना ताकि कोई और उस कोड को "अनुकूलित" न करे।

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


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

1
यह मुझे लगता है कि स्टीफन कैनन का उत्तर आपको कहना है। आप जोर देकर कहते हैं कि परिवाद को स्प्लीन के साथ लागू किया जाता है: वे आम तौर पर तर्क में कमी (कार्य के क्रियान्वयन के आधार पर) के साथ-साथ एकल बहुपद गुणांक का उपयोग करते हैं, जो रिमेज़ एल्गोरिथ्म के अधिक या कम परिष्कृत वेरिएंट द्वारा प्राप्त किया गया है। जंक्शन बिंदुओं पर चिकनाई को लिबास फ़ंक्शंस का पीछा करने के लिए एक उद्देश्य के रूप में नहीं माना जाता है (यदि वे सटीक रूप से समाप्त हो जाते हैं, तो वे स्वचालित रूप से काफी चिकनी हैं, चाहे डोमेन कितने टुकड़ों में विभाजित हो)।
पास्कल क्यूक

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

आपके इनपुट के लिए धन्यवाद, मैंने उत्तर को थोड़ा ठीक किया, कुछ नया अभी भी अंतिम 2 पंक्तियों में मौजूद है ^ ^
कॉफ़ीड्यूलेटर

27

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

मुझे अन्य उत्तरों से यह जानकर आश्चर्य हुआ कि यह अभिव्यक्ति वास्तव में उचित संकलक स्विच के साथ अनुकूलित की जा सकती है। या तो अनुकूलन तुच्छ है, या यह बहुत अधिक सामान्य अनुकूलन का एक किनारा मामला है, या संकलक लेखक बेहद गहन थे।

जैसा कि आपने यहाँ किया है, संकलक को संकेत प्रदान करने में कुछ भी गलत नहीं है। यह माइक्रो-ऑप्टिमाइज़ेशन प्रक्रिया का एक सामान्य और अपेक्षित हिस्सा है जो बयानों और अभिव्यक्तियों को फिर से व्यवस्थित करने के लिए देखते हैं कि वे क्या अंतर लाएंगे।

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


17
जैसा कि एक अन्य टिप्पणीकार ने कहा है, यह बेतुका होने की बात से असत्य है; यह अंतर लागत के आधे से 10% के बीच हो सकता है, और यदि एक तंग लूप में चलाया जाता है, जो कि अतिरिक्त परिशुद्धता की एक महत्वहीन राशि हो सकती है, को बर्बाद करने के लिए कई निर्देशों का अनुवाद करेगा। यह कहना कि आप मानक एफपी का उपयोग नहीं कर रहे हैं जब आप एक मोंटे कार्लो कर रहे हैं तो यह कहने की तरह है कि आपको हमेशा देश भर में हवाई जहाज का उपयोग करना चाहिए; यह कई बाह्यताओं की उपेक्षा करता है। अंत में, यह एक असामान्य अनुकूलन नहीं है; मृत कोड विश्लेषण और कोड में कमी / रिफ्लेक्टर बहुत आम है।
ऐलिस

21

इस प्रश्न के पहले से ही कुछ अच्छे उत्तर हैं, लेकिन पूर्णता के लिए मैं यह बताना चाहता था कि C मानक का लागू खंड 5.1.2.2.3 / 15 है (जो कि खंड 1.9 / 9 के समान है। सी ++ 11 मानक)। इस खंड में कहा गया है कि ऑपरेटरों को केवल तभी समूहित किया जा सकता है जब वे वास्तव में सहयोगी या कम्यूटेटिव हों।


12

gcc वास्तव में फ्लोटिंग-पॉइंट नंबरों के लिए भी इस अनुकूलन को कर सकता है। उदाहरण के लिए,

double foo(double a) {
  return a*a*a*a*a*a;
}

हो जाता है

foo(double):
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm1, %xmm0
    ret

के साथ -O -funsafe-math-optimizations। यह पुन: व्यवस्थित करना IEEE-754 का उल्लंघन करता है, हालाँकि, इसके लिए ध्वज की आवश्यकता होती है।

पीटर कॉर्डेस ने एक टिप्पणी में कहा कि पूर्णांक पर हस्ताक्षर किए गए, यह अनुकूलन कर सकता है, -funsafe-math-optimizationsक्योंकि यह बिना किसी अतिप्रवाह के होता है और यदि अतिप्रवाह होता है तो आपको अपरिभाषित व्यवहार मिलता है। तो आपको मिलता है

foo(long):
    movq    %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rax, %rax
    ret

बस के साथ -O। अहस्ताक्षरित पूर्णांकों के लिए, यह और भी आसान है क्योंकि वे 2 की शक्तियां काम करते हैं और इसलिए अतिप्रवाह की स्थिति में भी स्वतंत्र रूप से पुन: व्यवस्थित किया जा सकता है।


1
डबल, इंट और अहस्ताक्षरित के साथ गॉडबॉल्ट लिंक । gcc और clang दोनों तीनों को समान रूप से (साथ -ffast-math) अनुकूलित करते हैं
पीटर कॉर्ड्स

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