क्या यह फ्लोटिंग-पॉइंट ऑप्टिमाइज़ेशन की अनुमति है?


90

मैंने यह जांचने की कोशिश की floatकि बड़े पूर्णांक संख्याओं का सही प्रतिनिधित्व करने की क्षमता कहाँ खोती है। इसलिए मैंने यह छोटा सा स्निपेट लिखा:

int main() {
    for (int i=0; ; i++) {
        if ((float)i!=i) {
            return i;
        }
    }
}

यह कोड क्लैंग को छोड़कर सभी कंपाइलरों के साथ काम करता है। क्लैंग एक साधारण अनंत लूप उत्पन्न करता है। गॉडबोल्ट

क्या यह अनुमति है? यदि हाँ, तो क्या यह एक QoI मुद्दा है?


@geza मुझे परिणामी संख्या सुनने में दिलचस्पी होगी!
नाडा

5
gccयदि आप -Ofastइसके बजाय संकलन करते हैं, तो वही अनंत लूप ऑप्टिमाइज़ेशन करता है , इसलिए यह एक ऑप्टिमाइज़ gccअसुरक्षित है, लेकिन यह ऐसा कर सकता है।
१२३४५ ईस्वी १२'१

3
जी ++ भी एक अनंत लूप उत्पन्न करता है, लेकिन यह इसके अंदर से काम को दूर नहीं करता है। आप इसे खुद ucomiss xmm0,xmm0से तुलना करने के लिए देख सकते हैं (float)i। यह आपका पहला सुराग था कि आपका सी ++ स्रोत का मतलब यह नहीं है कि आपने क्या सोचा था। क्या आप दावा कर रहे हैं कि आपको यह लूप प्रिंट करने / वापस करने के लिए मिला है 16777216? क्या संकलक / संस्करण / विकल्प के साथ था? क्योंकि यह एक कंपाइलर बग होगा। gcc आपके कोड को jnpलूप ब्रांच ( godbolt.org/z/XJYWeu ) के रूप में सही रूप से अनुकूलित करता है : जब तक ऑपरेशन्स!= NaN नहीं होते तब तक लूपिंग रखें ।
पीटर कॉर्ड्स

4
विशेष रूप से, यह -ffast-mathविकल्प है जो अंतर्निहित रूप से सक्षम है -Ofastजो कि जीसीसी को असुरक्षित फ्लोटिंग-पॉइंट ऑप्टिमाइज़ेशन लागू करने की अनुमति देता है और इस तरह क्लैंग के समान कोड उत्पन्न करता है। MSVC बिल्कुल उसी तरह से व्यवहार करता है: इसके बिना /fp:fast, यह कोड का एक गुच्छा उत्पन्न करता है जिसके परिणामस्वरूप एक अनंत लूप होता है; के साथ /fp:fast, यह एक jmpनिर्देश का उत्सर्जन करता है । मैं मान रहा हूं कि स्पष्ट रूप से असुरक्षित एफपी ऑप्टिमाइज़ेशन को चालू किए बिना, ये संकलक आईईईई 754 आवश्यकताओं पर एनएएन मानों के बारे में लटका हुआ है। बल्कि दिलचस्प है कि क्लैंग वास्तव में नहीं है। इसका स्थैतिक विश्लेषक बेहतर है। @ 12345ieee
कोड़ी ग्रे

1
@geza: यदि कोड ने वही किया जो आप चाहते थे, तब जब गणितीय मान से (float) iभिन्न के गणितीय मान की जाँच करें i, तो परिणाम ( returnविवरण में दिया गया मान ) 16,777,217 होगा, न कि 16,777,216।
एरिक पोस्टपिसिल

जवाबों:


49

जैसा कि @Angew ने बताया , !=ऑपरेटर को दोनों तरफ एक ही प्रकार की आवश्यकता होती है। (float)i != iआरएचएस को बढ़ावा देने के परिणाम भी तैरते हैं, इसलिए हमारे पास है (float)i != (float)i


जी ++ भी एक अनंत लूप उत्पन्न करता है, लेकिन यह इसके अंदर से काम को अनुकूलित नहीं करता है। आप यह देख सकते हैं कि यह अंतर-फ़्लोट के साथ परिवर्तित cvtsi2ssहोता ucomiss xmm0,xmm0है और (float)iस्वयं से तुलना करता है। (यह आपका पहला सुराग था कि आपके C ++ स्रोत का मतलब यह नहीं है कि आपने क्या सोचा था कि यह @ Angew का उत्तर बताता है।)

x != xयह सच है जब यह "अनियंत्रित" xथा क्योंकि NaN था। ( INFINITYIEEE गणित में खुद के बराबर की तुलना करता है, लेकिन NaN NAN == NANगलत नहीं है, NAN != NANसच है)।

gcc7.4 और पुराने अपने कोड को jnpलूप शाखा ( https://godbolt.org/z/fyOhW1 ) के रूप में सही रूप से अनुकूलित करता है : जब तक ऑपरेशंस x != x NaN नहीं होते तब तक लूपिंग को चालू रखें । (gcc8 और बाद jeमें लूप से ब्रेक आउट की जाँच करता है, इस तथ्य के आधार पर अनुकूलन करने में विफल कि यह किसी भी गैर-NaN इनपुट के लिए हमेशा सही रहेगा)। x86 FP अनियंत्रित पर सेट पीएफ की तुलना करता है।


और BTW, इसका मतलब है कि क्लैंग का ऑप्टिमाइज़ेशन भी सुरक्षित है : इसे बस CSE (float)i != (implicit conversion to float)iके समान होना चाहिए, और यह साबित करना चाहिए कि i -> floatसंभव सीमा के लिए कभी भी NaN नहीं है int

(हालांकि यह देखते हुए कि यह लूप हस्ताक्षरित-अतिप्रवाह यूबी को हिट करेगा, यह शाब्दिक रूप से किसी भी तरह का कोई भी ud2निर्देश, जिसमें कोई अवैध निर्देश, या लूप बॉडी वास्तव में क्या था, की परवाह किए बिना एक खाली अनंत लूप को बाहर निकालने की अनुमति है ।) लेकिन हस्ताक्षरित-अतिप्रवाह यूबी की अनदेखी करना। , यह अनुकूलन अभी भी 100% कानूनी है।


GCC लूप बॉडी को ऑप्टिमाइज़ करने में भी नाकाम है, यहां तक ​​कि -fwrapvहस्ताक्षरित-पूर्णांक ओवरफ्लो को अच्छी तरह से परिभाषित करने के लिए भी (2 के पूरक रैपराउंड के रूप में)। https://godbolt.org/z/t9A8t_

यहां तक ​​कि सक्षम करने से -fno-trapping-mathभी मदद नहीं मिलती है। (GCC का डिफ़ॉल्ट दुर्भाग्य से सक्षम करने के लिए है,
-ftrapping-mathभले ही GCC का क्रियान्वयन टूटा / छोटा हो ।) int-> फ्लोट रूपांतरण के कारण FP अक्षम्य अपवाद हो सकता है (संख्याओं का बहुत बड़ा प्रतिनिधित्व किया जा सकता है), इसलिए अपवादों के साथ संभवत: यह अस्वीकार्य नहीं है। लूप बॉडी को ऑप्टिमाइज़ करें। (क्योंकि 16777217फ्लोट में कनवर्ट करने पर ऑब्जर्वेबल साइड-इफ़ेक्ट हो सकता है, अगर एक्सपेक्ट अपवाद अपवाद नहीं है।)

लेकिन इसके साथ -O3 -fwrapv -fno-trapping-math, यह 100% छूट वाला अनुकूलन है जो इसे खाली अनंत लूप में संकलित नहीं करता है। बिना #pragma STDC FENV_ACCESS ON, चिपचिपा झंडे की स्थिति जो कि एफपी अपवादों को रिकॉर्ड करती है, कोड का एक नमूदार साइड-इफेक्ट नहीं है। नहीं int-> floatरूपांतरण का परिणाम NaN हो x != xसकता है , इसलिए यह सच नहीं हो सकता।


ये संकलनकर्ता C ++ कार्यान्वयन के लिए सभी अनुकूलन कर रहे हैं जो IEEE 754 एकल-परिशुद्धता (बाइनरी float32 ) और 32-बिट का उपयोग करते हैं int

Bugfixed(int)(float)i != i पाश संकीर्ण 16-बिट के साथ सी ++ कार्यान्वयन पर यूबी होता intऔर / या व्यापक floatक्योंकि आप हिट हैं, पर हस्ताक्षर किए-पूर्णांक अतिप्रवाह यूबी पहले पूर्णांक है कि एक के रूप में वास्तव में प्रदर्शनीय नहीं था तक पहुँचने से पहले float

लेकिन कार्यान्वयन-परिभाषित विकल्पों के एक अलग सेट के तहत UB को x86-64 सिस्टम V ABI के साथ gcc या क्लैंग जैसे कार्यान्वयन के लिए संकलन करते समय कोई नकारात्मक परिणाम नहीं होता है।


BTW, आप सांख्यिकीय रूप से इस लूप के परिणाम की गणना कर सकते हैं FLT_RADIXऔर FLT_MANT_DIG, में परिभाषित किया गया है <climits>। या कम से कम आप सिद्धांत में कर सकते हैं, अगर floatवास्तव में एक आईईईई फ्लोट के मॉडल को फिट करता है बजाय किसी अन्य प्रकार के वास्तविक संख्या प्रतिनिधित्व जैसे कि पॉसिट / अनम।

मुझे यकीन नहीं है कि floatव्यवहार के बारे में आईएसओ सी ++ मानक नाखून कितना नीचे है और क्या एक प्रारूप जो निश्चित-चौड़ाई वाले घातांक और महत्व के क्षेत्रों पर आधारित नहीं था, मानकों के अनुरूप होगा।


टिप्पणियों में:

@geza मुझे परिणामी संख्या सुनने में दिलचस्पी होगी!

@ नदा: यह 16777216 है

क्या आप दावा कर रहे हैं कि आपको यह लूप प्रिंट करने / वापस करने के लिए मिला है 16777216?

अपडेट: चूंकि वह टिप्पणी हटा दी गई है, मुझे नहीं लगता। संभवतः ओपी floatपहले पूर्णांक से पहले उद्धृत कर रहा है जिसे 32-बिट के रूप में बिल्कुल प्रतिनिधित्व नहीं किया जा सकता है floathttps://en.wikipedia.org/wiki/Single-preaches_floating-point_format#Preults_limits_on_integer_values यानी वे इस छोटी गाड़ी के कोड के साथ सत्यापन की उम्मीद कर रहे थे।

Bugfixed संस्करण निश्चित रूप से प्रिंट होगा 16777217, पहला पूर्णांक जो कि इससे पहले मूल्य के बजाय, बिल्कुल प्रतिनिधित्व योग्य नहीं है।

(सभी उच्च फ़्लोट मान सटीक पूर्णांक होते हैं, लेकिन वे 2 से 4 के गुणक होते हैं, फिर महत्व चौड़ाई से अधिक घातांक मान के लिए 4, 8 आदि। कई उच्च पूर्णांक मानों का प्रतिनिधित्व किया जा सकता है, लेकिन अंतिम स्थान पर 1 इकाई। (महत्व का) 1 से अधिक है, इसलिए वे सन्निहित पूर्णांक नहीं हैं। सबसे बड़ा परिमित float2 ^ 128 से नीचे है, जो कि बहुत बड़ा है int64_t।)

यदि कोई कंपाइलर मूल लूप से बाहर निकलता है और प्रिंट करता है, तो वह कंपाइलर बग होगा।


3
@SombreroChicken: नहीं, मैंने पहले इलेक्ट्रॉनिक्स सीखा (कुछ पाठ्य पुस्तकों से मेरे पिताजी झूठ बोल रहे थे; वह एक भौतिकी के प्रोफेसर थे), फिर डिजिटल तर्क और उसके बाद सीपीयू / सॉफ्टवेयर में प्रवेश किया। : पी बहुत सुंदर मुझे हमेशा जमीन से चीजों को समझना पसंद आया है, या अगर मैं उच्च स्तर से शुरू करता हूं तो मुझे नीचे के स्तर के बारे में कम से कम कुछ सीखना पसंद है जो प्रभावित करता है कि मैं किस स्तर पर काम करता हूं / क्यों करता हूं के बारे में सोच। (जैसे एएसएम कैसे काम करता है और इसे कैसे अनुकूलित किया जाए यह सीपीयू डिज़ाइन की कमी / सीपीयू-आर्किटेक्चर सामान से प्रभावित होता है। जो भौतिकी + गणित से आता है।)
पीटर कॉर्ड्स

1
जीसीसी के साथ भी अनुकूलन करने में सक्षम नहीं हो सकता है frapw, लेकिन मुझे यकीन है कि जीसीसी 10 -ffinite-loopsको इस तरह की स्थितियों के लिए डिज़ाइन किया गया था।
MCCCS

64

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

(float)i != (float)i

यह कभी भी विफल नहीं होना चाहिए, और इसलिए कोड अंततः अतिप्रवाह करेगा i, जिससे आपका कार्यक्रम अपरिभाषित व्यवहार होगा। कोई भी व्यवहार इसलिए संभव है।

जो आप जांचना चाहते हैं उसे सही ढंग से जांचने के लिए, आपको परिणाम वापस करना चाहिए int:

if ((int)(float)i != i)

8
@ Džuris यह यूबी है। वहाँ है कोई भी निश्चित परिणाम। कंपाइलर को एहसास हो सकता है कि यह केवल यूबी में समाप्त हो सकता है और पूरी तरह से लूप को हटाने का निर्णय ले सकता है।
निधि मोनिका का मुकदमा

4
@opa तुम्हारा मतलब है static_cast<int>(static_cast<float>(i))? reinterpret_castवहाँ स्पष्ट UB है
Caleth

6
@ नीचार्टली: क्या आप कह रहे हैं कि (int)(float)i != iयूबी है? आप कैसे निष्कर्ष निकालेंगे? हां यह कार्यान्वयन परिभाषित गुणों पर निर्भर करता है (क्योंकि floatIEEE754 बाइनरी 32 होने की आवश्यकता नहीं है), लेकिन किसी भी दिए गए कार्यान्वयन पर यह अच्छी तरह से परिभाषित किया गया है जब तक कि हम floatसभी सकारात्मक intमूल्यों का प्रतिनिधित्व नहीं कर सकते हैं ताकि हम हस्ताक्षरित-पूर्णांक अतिप्रवाह यूबी प्राप्त करें। ( en.cppreference.com/w/cpp/types/climits परिभाषित करता है FLT_RADIXऔर इसे FLT_MANT_DIGनिर्धारित करता है )। सामान्य मुद्रण कार्यान्वयन-परिभाषित चीजों में, जैसे std::cout << sizeof(int)यूबी नहीं है ...
पीटर कॉर्ड्स

2
@ कैलेथ: reinterpret_cast<int>(float)वास्तव में यूबी नहीं है, यह सिर्फ एक सिंटैक्स त्रुटि / बीमार-गठन है। यह अच्छा होगा यदि उस वाक्यविन्यास को फ्लोट के टाइप-पैंटिंग के intविकल्प के रूप में अनुमति दी जाए memcpy(जो अच्छी तरह से परिभाषित है), लेकिन reinterpret_cast<>केवल सूचक प्रकारों पर काम करता है, मुझे लगता है।
पीटर कॉर्ड्स

2
@Peter सिर्फ NaN के लिए, x != xसच है। कोलिरु पर लाइव देखें । C में भी।
Deduplicator
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.