क्या रस्ट में क्वेक का तेज़ इनस्वार्ट () फ़ंक्शन लिखना संभव है?


101

यह सिर्फ मेरी अपनी जिज्ञासा को संतुष्ट करने के लिए है।

क्या इसका कार्यान्वयन है:

float InvSqrt (float x)
{
   float xhalf = 0.5f*x;
   int i = *(int*)&x;
   i = 0x5f3759df - (i>>1);
   x = *(float*)&i;
   x = x*(1.5f - xhalf*x*x);
   return x;
}

जंग में? यदि यह मौजूद है, तो कोड पोस्ट करें।

मैंने इसे आजमाया और असफल रहा। मुझे नहीं पता कि पूर्णांक प्रारूप का उपयोग करके फ्लोट संख्या को कैसे एनकोड करना है। यहाँ मेरा प्रयास है:

fn main() {
    println!("Hello, world!");
    println!("sqrt1: {}, ",sqrt2(100f64));
}

fn sqrt1(x: f64) -> f64 {
    x.sqrt()
}

fn sqrt2(x: f64) -> f64 {
    let mut x = x;
    let xhalf = 0.5*x;
    let mut i = x as i64;
    println!("sqrt1: {}, ", i);

    i = 0x5f375a86 as i64 - (i>>1);

    x = i as f64;
    x = x*(1.5f64 - xhalf*x*x);
    1.0/x
}

संदर्भ:
1. क्वेक 3 के फास्ट इनवस्कर्ट की उत्पत्ति () - पेज 1
2. क्वेक के फास्ट इनवर्स स्क्वायर रूट को समझना
3. फास्ट इन्वर्टर वर्ग ROOT.pdf
4. स्रोत कोड: q_math.c # L552-L572


C # संस्करण: क्या
फ्लाइक

4
जैसा कि मैं इसे समझता हूं, यह कोड सख्त अलियासिंग नियम का उल्लंघन करने के कारण सी में यूबी है । इस प्रकार की चकाचौंध को करने का मानक-धन्य तरीका a के साथ है union
ट्रेंटक्ले

4
@trentcl: मुझे नहीं लगता कि कोई unionकाम करता है। memcpyनिश्चित रूप से काम करता है, हालांकि यह क्रिया है।
मैथ्यू एम।

14
@MatthieuM। यूनियनों के साथ टाइपिंग की गति पूरी तरह से मान्य सी है , लेकिन वैध सी ++ नहीं है।
मोइरा

4
मुझे लगता है कि यह सवाल शुद्ध-जिज्ञासा के नजरिए से ठीक है, लेकिन कृपया समझें कि समय बदल गया है। X86 पर, rsqrtssऔर rsqrtpsनिर्देश, 1999 में पेंटियम III के साथ पेश किए गए, इस कोड की तुलना में अधिक तेज़ और सटीक हैं। ARM NEON vrsqrteजो समान है और जो कुछ भी गणना III के लिए इस्तेमाल की जाती है वह शायद इन दिनों GPU पर शायद वैसे भी किया जाएगा।
बेन्ज

जवाबों:


87

मुझे नहीं पता कि पूर्णांक प्रारूप का उपयोग करके फ्लोट संख्या को कैसे एनकोड करना है।

इसके लिए एक फ़ंक्शन है: f32::to_bitsजो एक रिटर्न देता है u32। अन्य दिशा के लिए भी फ़ंक्शन है: f32::from_bitsजो एक u32तर्क के रूप में लेता है। इन कार्यों को प्राथमिकता दी जाती है mem::transmuteक्योंकि बाद वाला unsafeऔर उपयोग करने के लिए मुश्किल है।

उस के साथ, यहाँ का कार्यान्वयन है InvSqrt:

fn inv_sqrt(x: f32) -> f32 {
    let i = x.to_bits();
    let i = 0x5f3759df - (i >> 1);
    let y = f32::from_bits(i);

    y * (1.5 - 0.5 * x * y * y)
}

( खेल का मैदान )


यह फ़ंक्शन x86-64 पर निम्न असेंबली के लिए संकलित करता है:

.LCPI0_0:
        .long   3204448256        ; f32 -0.5
.LCPI0_1:
        .long   1069547520        ; f32  1.5
example::inv_sqrt:
        movd    eax, xmm0
        shr     eax                   ; i << 1
        mov     ecx, 1597463007       ; 0x5f3759df
        sub     ecx, eax              ; 0x5f3759df - ...
        movd    xmm1, ecx
        mulss   xmm0, dword ptr [rip + .LCPI0_0]    ; x *= 0.5
        mulss   xmm0, xmm1                          ; x *= y
        mulss   xmm0, xmm1                          ; x *= y
        addss   xmm0, dword ptr [rip + .LCPI0_1]    ; x += 1.5
        mulss   xmm0, xmm1                          ; x *= y
        ret

मुझे कोई संदर्भ असेंबली नहीं मिली है (यदि आपके पास है, तो कृपया मुझे बताएं!), लेकिन यह मुझे काफी अच्छा लगता है। मुझे यकीन नहीं है कि फ्लोट को eaxकेवल शिफ्ट और पूर्णांक घटाव करने के लिए क्यों स्थानांतरित किया गया था । हो सकता है कि SSE रजिस्टर उन ऑपरेशनों का समर्थन नहीं करता हो?

मूल रूप से एक ही विधानसभा-O3 के लिए सी कोड के साथ 9.0 9.0 संकलित । तो यह एक अच्छा संकेत है।


यह इंगित करने योग्य है कि यदि आप वास्तव में इसे व्यवहार में उपयोग करना चाहते हैं: तो कृपया नहीं। जैसा कि बेन्गर ने टिप्पणियों में बताया , आधुनिक x86 सीपीयू में इस फ़ंक्शन के लिए एक विशेष निर्देश है जो इस हैक की तुलना में तेज और सटीक है। दुर्भाग्य से, 1.0 / x.sqrt() उस निर्देश का अनुकूलन नहीं लगता है । तो अगर आपको वास्तव में गति की आवश्यकता है, तो _mm_rsqrt_psआंतरिक का उपयोग करना संभवतः जाने का रास्ता है। हालाँकि, इसके लिए फिर से unsafeकोड की आवश्यकता होती है । मैं इस जवाब में अधिक विस्तार में नहीं जाऊंगा, क्योंकि प्रोग्रामर के अल्पसंख्यक को वास्तव में इसकी आवश्यकता होगी।


4
इंटेल इंट्रिंसिक्स गाइड के अनुसार कोई पूर्णांक शिफ्ट ऑपरेशन नहीं है जो केवल 32-बिट को 128-बिट रजिस्टर एनालॉग के addssया उसके पास स्थानांतरित करता है mulss। लेकिन अगर xmm0 के अन्य 96 बिट्स को अनदेखा किया जा सकता है, तो कोई भी psrldनिर्देश का उपयोग कर सकता है । वही पूर्णांक घटाव के लिए जाता है।
fsasm

मैं जंग के बारे में कुछ नहीं करने के लिए अगले जानने के लिए मानता हूँ, लेकिन "असुरक्षित" नहीं है मूल रूप से fast_inv_sqrt की एक मुख्य संपत्ति? इसके साथ डेटाटाइप्स और इस तरह के कुल अनादर हैं।
ग्लोवेय

12
@Gloweye यह "असुरक्षित" का एक अलग प्रकार है, हालांकि हम इसके बारे में बात करते हैं। एक तेज़ सन्निकटन जो मीठे स्थान से बहुत दूर एक बुरा मूल्य प्राप्त करता है, बनाम अपरिभाषित व्यवहार के साथ तेज़ और ढीली खेलना।
Deduplicator 16

8
@Gloweye: गणितीय रूप से, इसका अंतिम भाग fast_inv_sqrtन्यूटन-राफसन पुनरावृत्ति कदम है, जिसका एक बेहतर सन्निकटन खोजने के लिए inv_sqrt। उस हिस्से के बारे में कुछ भी असुरक्षित नहीं है। प्रवंचना पहले भाग में है, जो एक अच्छा अनुमान लगाती है। यह काम करता है क्योंकि यह फ्लोट के प्रतिपादक भाग पर 2 से एक पूर्णांक विभाजित करता है, और वास्तव मेंsqrt(pow(0.5,x))=pow(0.5,x/2)
MSalters

1
@ एफ़एफ़एस्म: यह सही है; movdEAX और वापस वर्तमान संकलक द्वारा एक चूक अनुकूलन है। (और हाँ, बुला सम्मेलनों पारित / वापसी अदिश floatएक XMM के निम्न तत्व में और उच्च बिट्स कचरा होने की अनुमति लेकिन टिप्पणी करता है, तो यह है कि। था शून्य बढ़ाया, यह आसानी से इस तरह से रहने कर सकते हैं: सही स्थानांतरण गैर परिचय नहीं है शून्य तत्व और न ही से घटाव _mm_set_epi32(0,0,0,0x5f3759df), यानी एक movdलोड नहीं है। आपको movdqa xmm1,xmm0पहले reg को कॉपी करने की आवश्यकता होगी psrld। FP निर्देश से बायपास विलंबता और पूर्णांक के लिए अग्रेषण mulssविलंबता द्वारा छिपा हुआ है ।
पीटर कॉर्डेस

37

यह unionजंग में कम ज्ञात के साथ लागू किया जाता है:

union FI {
    f: f32,
    i: i32,
}

fn inv_sqrt(x: f32) -> f32 {
    let mut u = FI { f: x };
    unsafe {
        u.i = 0x5f3759df - (u.i >> 1);
        u.f * (1.5 - 0.5 * x * u.f * u.f)
    }
}

क्या criterionx86-64 लिनक्स बॉक्स पर टोकरा का उपयोग करते हुए कुछ माइक्रो बेंचमार्क थे । आश्चर्यजनक रूप से रस्ट का अपना sqrt().recip()सबसे तेज़ है। लेकिन निश्चित रूप से, किसी भी सूक्ष्म बेंचमार्क परिणाम को नमक के दाने के साथ लिया जाना चाहिए।

inv sqrt with transmute time:   [1.6605 ns 1.6638 ns 1.6679 ns]
inv sqrt with union     time:   [1.6543 ns 1.6583 ns 1.6633 ns]
inv sqrt with to and from bits
                        time:   [1.7659 ns 1.7677 ns 1.7697 ns]
inv sqrt with powf      time:   [7.1037 ns 7.1125 ns 7.1223 ns]
inv sqrt with sqrt then recip
                        time:   [1.5466 ns 1.5488 ns 1.5513 ns]

22
मैं कम से कम आश्चर्य में नहीं हूं sqrt().inv()सबसे तेज है। दोनों sqrt और inv इन दिनों सिंगल इंस्ट्रक्शंस हैं, और बहुत तेजी से चलते हैं। कयामत उन दिनों में लिखी गई थी जब यह मान लेना सुरक्षित नहीं था कि हार्डवेयर फ़्लोटिंग पॉइंट बिल्कुल भी नहीं था, और sqrt जैसे ट्रान्सेंडैंटल फ़ंक्शंस निश्चित रूप से सॉफ़्टवेयर होते। बेंचमार्क के लिए +1।
मार्टिन बोनर

4
जो मुझे आश्चर्यचकित करता है, transmuteवह स्पष्ट रूप से अलग है to_और from_bits- मैं अपेक्षा करता हूं कि अनुकूलन से पहले ही वे निर्देश-समकक्ष होंगे।
ट्रेंटक्ले

2
@MartinBonner (इसके अलावा, ऐसा नहीं है कि यह मायने रखता है, लेकिन sqrt एक पारलौकिक कार्य नहीं है ।)
benrg

4
@ मर्टिनबोनर: विभाजन का समर्थन करने वाला कोई भी हार्डवेयर FPU सामान्य रूप से sqrt का भी समर्थन करेगा। आईईईई "बुनियादी" संचालन (+ - * / sqrt) एक सही ढंग से गोल परिणाम का उत्पादन करने के लिए आवश्यक हैं; यही कारण है कि SSE उन सभी कार्यों को प्रदान करता है, लेकिन ऍक्स्प, पाप या जो कुछ भी नहीं है। वास्तव में, विभाजित और sqrt आमतौर पर एक ही निष्पादन इकाई पर चलते हैं, एक समान तरीके से डिज़ाइन किया गया है। देखें HW div / sqrt इकाई विवरण । वैसे भी, वे अभी भी गुणा की तुलना में तेजी से नहीं हैं, खासकर विलंबता में।
पीटर कॉर्डेस

1
वैसे भी, स्काईलेक के पास पिछले यूरेश की तुलना में div / sqrt के लिए बेहतर पाइपलाइनिंग है। Agner कोहरे की मेज से कुछ अर्क के लिए फ़्लोटिंग पॉइंट डिवीज़न बनाम फ़्लोटिंग पॉइंट गुणा देखें । यदि आप एक लूप में बहुत अधिक अन्य काम नहीं कर रहे हैं, तो sqrt + div एक अड़चन है, तो आप HW फास्ट पारस्परिक sqrt (भूकंप हैक के बजाय) + न्यूटन पुनरावृत्ति का उपयोग करना चाह सकते हैं। विशेष रूप से FMA के साथ जो थ्रूपुट के लिए अच्छा है, यदि विलंबता नहीं। परिशुद्धता के आधार पर SSE / AVX के साथ फास्ट वेक्टरकृत rsqrt और पारस्परिक
पीटर कॉर्डेस

10

आप std::mem::transmuteआवश्यक रूपांतरण करने के लिए उपयोग कर सकते हैं :

fn inv_sqrt(x: f32) -> f32 {
    let xhalf = 0.5f32 * x;
    let mut i: i32 = unsafe { std::mem::transmute(x) };
    i = 0x5f3759df - (i >> 1);
    let mut res: f32 = unsafe { std::mem::transmute(i) };
    res = res * (1.5f32 - xhalf * res * res);
    res
}

आप यहां एक जीवंत उदाहरण की तलाश कर सकते हैं: यहां


4
वहाँ असुरक्षित साथ कुछ भी नहीं गलत है, लेकिन वहाँ स्पष्ट असुरक्षित ब्लॉक के बिना यह करने के लिए एक तरह से है, तो मैं का उपयोग कर इस उत्तर के पुनर्लेखन के लिए सुझाव देंगे f32::to_bitsऔर f32::from_bits। यह प्रसारण के विपरीत स्पष्ट रूप से इरादे को वहन करता है, जिसे ज्यादातर लोग शायद "जादू" के रूप में देखते हैं।
साहसाहेह

5
@Sahsahae मैंने अभी आपके द्वारा उल्लिखित दो कार्यों का उपयोग करके एक उत्तर पोस्ट किया है :) और मैं मानता हूं, unsafeयहां से बचना चाहिए, क्योंकि यह आवश्यक नहीं है।
लुकास कलबर्टोड्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.