মরিচায় কি কোয়াকের দ্রুত ইনভাস্কর্ট () ফাংশনটি লেখা সম্ভব?


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. Quake3 এর ফাস্ট InvSqrt অরিজিন () - পৃষ্ঠা 1
2. বোঝাপড়া কোয়েক এর ফাস্ট বিপরীত বর্গ রুট
3. দ্রুত বিপরীত বর্গ ROOT.pdf
4. সোর্স কোড: q_math.c # L552-L572



4
আমি যেমন এটি বুঝতে পারি, কঠোর আলিয়াজিং বিধি লঙ্ঘনের কারণে এই কোডটি সি তে ইউবি । এই ধরণের শাস্তি দেওয়ার স্ট্যান্ডার্ড-আশীর্বাদযুক্ত উপায়টি একটি এর সাথে union
ট্রেন্টক্লা

4
@ ট্রেন্টক্লাব: আমার মনে unionহয় না যে এটি কাজ করে। memcpyস্পষ্টতই এটি কাজ করে, যদিও এটি ভার্বোস।
ম্যাথিউ এম।

14
@MatthieuM। ইউনিয়নগুলির সাথে টাইপ পেনিং পুরোপুরি বৈধ সি , তবে বৈধ সি ++ নয়।
ময়রার

4
আমি মনে করি এই প্রশ্নটি শুদ্ধ-কৌতূহল দৃষ্টিকোণ থেকে ঠিক আছে, তবে দয়া করে বুঝতে পারি যে সময়গুলি পরিবর্তিত হয়েছে। X86-এ, 1999 সালে পেন্টিয়াম III এর সাথে প্রবর্তিত, rsqrtssএবং rsqrtpsনির্দেশাবলী এই কোডের চেয়ে দ্রুত এবং আরও নির্ভুল। এআরএম নিওনেরও vrsqrteমিল রয়েছে। এবং ভূমিকম্প তৃতীয় যে কোনও গণনাগুলির জন্য এটি ব্যবহার করেছে সম্ভবত এই দিনগুলিতে জিপিইউতে সম্পন্ন হবে।
বেনার্গ

উত্তর:


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

আমি কোনও রেফারেন্স সমাবেশ খুঁজে পাই নি (যদি আপনার থাকে তবে দয়া করে আমাকে বলুন!) তবে এটি আমার কাছে মোটামুটি ভাল বলে মনে হচ্ছে seems আমি ঠিক নিশ্চিত নই যে কেন eaxশিফট এবং পূর্ণসংখ্যা বিয়োগ করতে ভাসাটি সরানো হয়েছিল । হতে পারে এসএসই রেজিস্টাররা এই অপারেশনগুলিকে সমর্থন করে না?

9.0 -O3ঝাঁকুনি দিয়ে সি কোডটি মূলত একই সমাবেশে সংকলন করে । সুতরাং এটি একটি ভাল চিহ্ন।


এটি উল্লেখ করার মতো যে আপনি যদি বাস্তবে এটি ব্যবহারে ব্যবহার করতে চান: দয়া করে এটি করবেন না। যেমনটি মন্তব্যগুলিতে উল্লেখ করা হয়েছে , আধুনিক x86 সিপিইউগুলির এই ফাংশনের জন্য একটি বিশেষ নির্দেশনা রয়েছে যা এই হ্যাকের চেয়ে দ্রুত এবং আরও সঠিক। দুর্ভাগ্যক্রমে, 1.0 / x.sqrt() এই নির্দেশকে অনুকূলিত করে না বলে মনে হচ্ছে । সুতরাং আপনার যদি সত্যিই গতির প্রয়োজন হয় তবে _mm_rsqrt_psঅন্তর্নিবেশগুলি সম্ভবত ব্যবহারের উপায়। এটি যাইহোক, আবার unsafeকোড প্রয়োজন । আমি এই উত্তরে খুব বেশি বিশদে যাব না, কারণ সংখ্যালঘু প্রোগ্রামারদের এটির প্রয়োজন হবে।


4
ইন্টেল Intrinsics গাইড অনুযায়ী কোন পূর্ণসংখ্যা শিফট অপারেশন যে কেবল 128-বিট রেজিস্টার এনালগ সর্বনিম্ন 32 বিট বদল নেই addssবা mulss। তবে যদি xmm0 এর অন্যান্য 96 টি বিট উপেক্ষা করা যায় তবে একটি psrldনির্দেশ ব্যবহার করতে পারে । একই পূর্ণসংখ্যার বিয়োগের জন্য যায়।
fsasm

আমি মরিচা সম্পর্কে কিছুই জানার পরেও স্বীকার করব, তবে "অনিরাপদ" মূলত ফাস্ট_আইএনভি_সিকিআরটির মূল সম্পত্তি নয়? এটি ডেটাটাইপগুলি এবং এই জাতীয় জন্য সম্পূর্ণ অসম্মানের সাথে।
গ্লোয়

12
@ গ্লোয়ে এটি ভিন্ন ধরণের "অনিরাপদ" যা আমরা সম্পর্কে কথা বলছি। একটি দ্রুত অনুমান যা মিষ্টি স্পট থেকে খুব দূরে একটি খারাপ মান পায়, বনাম কোনও কিছু নির্ধারিত আচরণের সাথে দ্রুত এবং আলগা খেলে বনাম।
হস্তান্তরকারী

8
@ গ্লোয়ে: গাণিতিকভাবে, এর শেষ অংশটি fast_inv_sqrtএকটি আরও ভাল অনুমানের সন্ধানের জন্য নিউটন- রাফসন পুনরুক্তি পদক্ষেপ inv_sqrt। এই অংশটি সম্পর্কে নিরাপদ কিছু নেই। কৌশলটি প্রথম অংশে রয়েছে, এটি একটি ভাল অনুমানের সন্ধান করে। এটি কাজ করে কারণ এটি sqrt(pow(0.5,x))=pow(0.5,x/2)
ভাসুর

1
@fsasm: এটি সঠিক; movdEAX এ এবং পিছনে বর্তমান সংকলকগুলির দ্বারা মিস করা অপ্টিমাইজেশন। (এবং হ্যাঁ, কলিং নিয়মাবলী পাস / রিটার্ন স্কালে floatএকটি XMM কম উপাদানের ক্ষেত্রে এবং উচ্চ বিট আবর্জনা হতে করার অনুমতি দেয় কিন্তু নোট যদি এটা যে। ছিল শূন্য বাড়ানো, এটি সহজে যে ভাবে যুক্ত থাকতে পারবেন: ডান নড়ন অ- পরিচয় করিয়ে না শূন্য উপাদান এবং তন্ন তন্ন করে থেকে বিয়োগ _mm_set_epi32(0,0,0,0x5f3759df), অর্থাত্ একটি movdলোড তুমি একটা প্রয়োজন হবে। movdqa xmm1,xmm0সামনে REG কপি করতে psrldবাইপাস থেকে লেটেন্সি FP নির্দেশ ফরওয়ার্ডিং পূর্ণসংখ্যা এবং তদ্বিপরীত দ্বারা লুকানো হয়। mulssলেটেন্সি।
পিটার Cordes

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)
    }
}

criterionএকটি x86-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()হয় না দ্রুত। স্কয়ার্ট এবং ইনভ উভয়ই এই দিনগুলিতে একক নির্দেশনা এবং খুব দ্রুত চলে go ডুম সেই দিনগুলিতে লেখা হয়েছিল যখন ধারণা করা নিরাপদ ছিল না যে সেখানে হার্ডওয়্যার ভাসমান বিন্দু ছিল না এবং স্কয়ার্টের মতো ট্রান্সসেন্টাল ফাংশন অবশ্যই সফ্টওয়্যার হত । মানদণ্ডের জন্য +1।
মার্টিন বোনার

4
আমাকে যে অবাক করে transmuteতোলে তা আপাতদৃষ্টিতে পৃথক to_এবং from_bits- আমি আশা করতাম যে এটি অপ্টিমাইজেশনের আগেও নির্দেশ-সমতুল্য হবে।
ট্রেন্টক্লা

2
@ মার্টিনবোনার (এছাড়াও, এটি গুরুত্বপূর্ণ যে নয়, তবে
স্কয়ার্ট

4
@ মার্টিনবোনার: ডিভিশনকে সমর্থন করে এমন কোনও হার্ডওয়্যার এফপিইউ সাধারণত স্কয়ারটি সমর্থন করবে। আইইইই "বেসিক" অপারেশনগুলি (+ - * / স্কয়ার্ট) সঠিকভাবে গোলাকার ফলাফল তৈরি করতে প্রয়োজনীয়; এজন্য এসএসই সেই সমস্ত ক্রিয়াকলাপ সরবরাহ করে তবে মেয়াদ, পাপ বা যা কিছু নয়। প্রকৃতপক্ষে, বিভাজন এবং sqrt সাধারণত একই নির্বাহী ইউনিটে চালিত হয়, একইভাবে নকশাকৃত। দেখুন এইচ ডব্লিউ DIV আছে / বর্গমূল ইউনিট বিবরণ । যাইহোক, তারা এখনও গুনের সাথে তুলনা করে দ্রুত নয়, বিশেষত বিলম্বের ক্ষেত্রে।
পিটার কর্ডস

1
যাইহোক, স্কাইলেকে পূর্ববর্তী uarches এর চেয়ে ডিভ / স্কয়ার্টের জন্য উল্লেখযোগ্যভাবে ভাল পাইপলাইনিং রয়েছে। দেখুন বিন্দু বিভাগ ভাসমান ফ্লোটিং পয়েন্ট গুণ বনাম Agner কুয়াশা টেবিল থেকে কিছু নির্যাস জন্য। আপনি যদি লুপটিতে অন্য কোনও কাজ না করে থাকেন তবে স্কয়ার্ট + ডিভ একটি বাধা হ'ল, আপনি এইচডব্লিউ দ্রুত পুনরুদ্ধার স্কয়ারটি (ভূমিকম্পের হ্যাকের পরিবর্তে) + একটি নিউটনের পুনরাবৃত্তি ব্যবহার করতে পারেন। বিশেষত এফএমএর সাথে এটি থ্রুপুটের পক্ষে ভাল, যদি না বিলম্ব হয়। যথাযথতার উপর নির্ভর করে এসএসই / এভিএক্সের সাথে দ্রুত ভেক্টরাইজড আরএসকিআরটি এবং পারস্পরিক ক্রিয়াকলাপ
পিটার

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
@ সাহসাহে আমি আপনার উল্লিখিত দুটি ফাংশন ব্যবহার করে একটি উত্তর পোস্ট করেছি :) এবং আমি সম্মত হচ্ছি, unsafeএখানে এড়ানো উচিত, কারণ এটি প্রয়োজনীয় নয়।
লুকাস কালবার্তোড
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.