c++ - जीसीसी को यह मानने के लिए कैसे मजबूर किया जाए कि एक फ्लोटिंग-पॉइंट अभिव्यक्ति गैर-नकारात्मक है?




gcc assembly (3)

ऐसे मामले हैं जहां आप जानते हैं कि एक निश्चित फ्लोटिंग-पॉइंट अभिव्यक्ति हमेशा गैर-नकारात्मक होगी। उदाहरण के लिए, एक वेक्टर की लंबाई की गणना करते समय, एक sqrt(a[0]*a[0] + ... + a[N-1]*a[N-1]) (NB: मुझे जानकारी है std::hypot , यह सवाल के लिए प्रासंगिक नहीं है), और वर्गमूल के तहत अभिव्यक्ति स्पष्ट रूप से गैर-नकारात्मक है। हालाँकि, GCC sqrt(x*x) लिए निम्न असेंबली को outputs करता है:

        mulss   xmm0, xmm0
        pxor    xmm1, xmm1
        ucomiss xmm1, xmm0
        ja      .L10
        sqrtss  xmm0, xmm0
        ret
.L10:
        jmp     sqrtf

यही है, यह x*x के परिणाम की तुलना शून्य से करता है, और यदि परिणाम गैर-नकारात्मक है, तो यह sqrtss निर्देश करता है, अन्यथा यह sqrtf कहता है।

तो, मेरा सवाल यह है: मैं यह मानकर जीसीसी को कैसे बाध्य कर सकता हूं कि x*x हमेशा गैर-नकारात्मक है ताकि यह इनलाइन असेंबली लिखे बिना तुलना और sqrtf कॉल को sqrtf ?

मैं इस बात पर जोर देना चाहता हूं कि मैं एक स्थानीय समाधान में दिलचस्पी रखता हूं, और -ffast-math , -fno-math-errno -ffinite-math-only , या -ffinite-math-only जैसी चीजें नहीं कर रहा -ffast-math (हालांकि ये वास्तव में मुद्दे को हल करते हैं, ks1b2 के लिए धन्यवाद हैरोल्ड, और टिप्पणियों में एरिक पोस्टपिसिल)।

Furthemore, "GCC को x*x गैर-ऋणात्मक मानने के लिए बाध्य करें" की व्याख्या assert(x*x >= 0.f) रूप में की जानी चाहिए, इसलिए यह x*x NaN होने के मामले को भी बाहर करता है।

मैं संकलक-विशिष्ट, प्लेटफ़ॉर्म-विशिष्ट, सीपीयू-विशिष्ट, आदि समाधानों के साथ ठीक हूं।


आप assert(x*x >= 0.f) C में निम्नानुसार रनटाइम चेक के बजाय संकलन-समय वादे के रूप में assert(x*x >= 0.f) लिख सकते हैं:

#include <cmath>

float test1 (float x)
{
    float tmp = x*x;
    if (!(tmp >= 0.0f)) 
        __builtin_unreachable();    
    return std::sqrt(tmp);
}

(संबंधित: क्या अनुकूलन __builtin_unreachable की सुविधा देता है? आप भी लपेट सकते हैं if(!x)__builtin_unreachable() और इसे promise() या कुछ और कहते हैं।

लेकिन जीसीसी यह नहीं जानता कि कैसे उस वादे का फायदा उठाया जाए जो tmp गैर-NaN और गैर-नकारात्मक है। हम अभी भी ( Godbolt ) समान डिब्बाबंद अनुक्रम प्राप्त करते हैं जो x>=0 लिए जाँच करता है और अन्यथा sqrtf को sqrtf से सेट करने के लिए कहता है। संभवतः एक तुलना-और-शाखा में विस्तार अन्य अनुकूलन पास होने के बाद होता है, इसलिए यह कंपाइलर को अधिक जानने में मदद नहीं करता है।

यह तर्क में एक चूक-अनुकूलन है, जो कि जब -fmath-errno जब -fmath-errno जब -fmath-errno तब -fmath-errno (डिफ़ॉल्ट रूप से दुर्भाग्य से) सक्षम होता है, तब -fmath-errno करता है।

इसके बजाय आप क्या चाहते हैं -fno-math-errno , जो विश्व स्तर पर सुरक्षित है

यह 100% सुरक्षित है यदि आप गणित के कार्यों पर भरोसा नहीं करते हैं जो कभी भी errno सेटिंग नहीं errno । कोई भी यह नहीं चाहता है, कि NaN प्रचार और / या चिपचिपा झंडे जो कि रिकॉर्ड किए गए FP अपवादों को मुखौटा बनाते हैं। जैसे fenv / C ++ 11 fenv एक्सेस #pragma STDC FENV_ACCESS ON माध्यम से और फिर fetestexcept() जैसे कार्य करता है। feclearexcept में उदाहरण देखें जो शून्य से विभाजन का पता लगाने के लिए इसका उपयोग करता है।

एफपी पर्यावरण थ्रेड संदर्भ का हिस्सा है, जबकि errno वैश्विक है।

इस अप्रचलित मिसफिट के लिए समर्थन मुक्त नहीं है; आपको इसे तब तक बंद कर देना चाहिए जब तक कि आपके पास पुराना कोड न हो जो इसे इस्तेमाल करने के लिए लिखा गया था। इसे नए कोड में उपयोग न करें: fenv उपयोग fenv-fmath-errno लिए आदर्श रूप से समर्थन जितना संभव हो उतना सस्ता होगा, लेकिन किसी NaN इनपुट को नियंत्रित करने के लिए वास्तव में __builtin_unreachable() या अन्य चीजों का उपयोग करने की दुर्लभता ने अनुकूलन को लागू करने के लिए इसे विकसित करने के समय के लायक नहीं बनाया। फिर भी, यदि आप चाहते थे तो आप एक छूट-अनुकूलन बग की रिपोर्ट कर सकते हैं।

वास्तविक दुनिया के FPU हार्डवेयर में वास्तव में ये चिपचिपे झंडे होते हैं, जो तब तक साफ रहते हैं, जैसे x86 की mxcsr स्थिति / SSE / AVX गणित के लिए नियंत्रण रजिस्टर, या अन्य ISAs में हार्डवेयर FPUs। हार्डवेयर पर जहां एफपीयू अपवादों का पता लगा सकता है, एक गुणवत्ता सी ++ कार्यान्वयन fetestexcept() जैसे सामान का समर्थन करेगा। और यदि नहीं, तो गणित- ग़लती शायद काम भी नहीं करता है।

गणित के लिए errno एक पुरानी अप्रचलित डिजाइन थी जिसे C / C ++ अभी भी डिफ़ॉल्ट रूप से अटका हुआ है, और अब इसे व्यापक रूप से एक बुरा विचार माना जाता है। यह कुशलतापूर्वक इनलाइन गणित कार्यों के लिए संकलक के लिए कठिन बनाता है। या हो सकता है कि हम इसके साथ उतने फंसे नहीं, जितना कि मैंने सोचा था: इरोम को ईडीओएम के लिए सेट नहीं किया गया है, यहां तक ​​कि sqrt भी डोमेन बहस से बाहर नहीं है? बताते हैं कि गणित कार्यों में इरनो को स्थापित करना आईएसओ सी 11 में वैकल्पिक है, और एक कार्यान्वयन यह इंगित कर सकता है कि वे ऐसा करते हैं या नहीं। संभवतः C ++ में भी।

यह -ffast-math या -ffinite-math-only जैसे मूल्य-बदलते अनुकूलन के साथ -fno-math-errno एक बड़ी गलती है। आपको विश्व स्तर पर या कम से कम इस फ़ंक्शन वाली संपूर्ण फ़ाइल के लिए इसे सक्षम करने पर दृढ़ता से विचार करना चाहिए।

float test2 (float x)
{
    return std::sqrt(x*x);
}
# g++ -fno-math-errno -std=gnu++17 -O3
test2(float):   # and test1 is the same
        mulss   xmm0, xmm0
        sqrtss  xmm0, xmm0
        ret

यदि आप कभी भी किसी भी FP अपवादों को feenableexcept() साथ feenableexcept() नहीं कर रहे हैं तो आप -fno-trapping-math का भी उपयोग कर सकते हैं। (हालांकि इस अनुकूलन के लिए उस विकल्प की आवश्यकता नहीं है, यह केवल errno परेशान करने वाली बकवास है जो यहां एक समस्या है।)

-fno-trapping-math NaN या कुछ भी ग्रहण नहीं करता है, यह केवल मानता है कि अमान्य या Inexact जैसे FP अपवाद वास्तव में NaN या एक गोल परिणाम के बजाय एक सिग्नल हैंडलर को लागू नहीं करेंगे। -ftrapping-math देव मार्क ग्लिसे के अनुसार -ftrapping-math डिफ़ॉल्ट है लेकिन यह टूट गया है और "कभी काम नहीं किया" । (इस पर भी, जीसीसी कुछ अनुकूलन करता है जो अपवादों की संख्या को शून्य से गैर-शून्य या इसके विपरीत तक बढ़ा सकता है। और यह कुछ सुरक्षित अनुकूलन को अवरुद्ध करता है)। लेकिन दुर्भाग्य से, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54192 (डिफ़ॉल्ट रूप से इसे बंद करें) अभी भी खुला है।

यदि आपने वास्तव में कभी अपवादों को -ftrapping-math किया था, तो बेहतर हो सकता है कि -ftrapping-math , लेकिन फिर से यह बहुत दुर्लभ है कि आप कभी भी चाहते हैं कि कुछ गणित के संचालन के बाद झंडे की जाँच करने के बजाय, या NaN के लिए जाँच करें। और यह वास्तव में वैसे भी सटीक अपवाद शब्दार्थ को संरक्षित नहीं करता है।

एक मामले के लिए फ्लोट थ्रेशोल्ड ऑपरेशन के लिए SIMD देखें जहां -fno-trapping-math गलत तरीके से एक सुरक्षित अनुकूलन को अवरुद्ध करता है। (संभावित-फंसाने वाले ऑपरेशन को फेल करने के बाद भी, सी बिना शर्त के करता है, जीसी गैर-वेक्टरकृत असम बनाता है जो इसे सशर्त रूप से करता है! इसलिए न केवल यह वेक्टरकरण को अवरुद्ध करता है, यह अपवाद शब्दार्थ बनाम सी एब्सट्रैक्ट मशीन को बदलता है।)


बिना किसी वैश्विक विकल्प के, यहाँ एक (कम-ओवरहेड, लेकिन मुक्त नहीं) तरीका है जिसकी कोई शाखा नहीं है।

#include <immintrin.h>

float test(float x)
{
    return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set1_ps(x * x)));
}

( godbolt पर)

हमेशा की तरह, क्लैंग अपने फेरबदल के बारे में चतुर है। उस क्षेत्र में GCC और MSVC पिछड़ जाते हैं, और प्रसारण से बचने का प्रबंधन नहीं करते हैं। MSVC कुछ रहस्यमय कदम भी कर रहा है।

फ्लोट को __m128 में बदलने के अन्य तरीके हैं, उदाहरण के लिए _mm_set_ss । क्लेंग के लिए कोई फर्क नहीं पड़ता है, जीसीसी के लिए जो कोड को थोड़ा बड़ा और बदतर बनाता है (एक movss reg, reg सहित movss reg, reg जो इंटेल पर फेरबदल के रूप में गिना जाता है, इसलिए यह फेरबदल पर भी नहीं बचाता है)।


विकल्प -fno-math-errno को gcc में पास करें। यह आपके कोड को बिना कोड के बनाए या ISO / IEC 9899: 2011 (C11) के दायरे को छोड़कर समस्या को हल करता है।

जब कोई गणित लाइब्रेरी फ़ंक्शन विफल होता है तो यह विकल्प errno निर्धारित करने का प्रयास नहीं करता है:

       -fno-math-errno
           Do not set "errno" after calling math functions that are executed
           with a single instruction, e.g., "sqrt".  A program that relies on
           IEEE exceptions for math error handling may want to use this flag
           for speed while maintaining IEEE arithmetic compatibility.

           This option is not turned on by any -O option since it can result
           in incorrect output for programs that depend on an exact
           implementation of IEEE or ISO rules/specifications for math
           functions. It may, however, yield faster code for programs that do
           not require the guarantees of these specifications.

           The default is -fmath-errno.

           On Darwin systems, the math library never sets "errno".  There is
           therefore no reason for the compiler to consider the possibility
           that it might, and -fno-math-errno is the default.

यह देखते हुए कि आपको errno गणित की सेटिंग करने में कोई दिलचस्पी नहीं है, यह एक अच्छा समाधान लगता है।





micro-optimization