2012-01-25 47 views
17

में एक डबल फाड़ना अनुकरण करें मैं 32-बिट मशीन पर चल रहा हूं और मैं यह पुष्टि करने में सक्षम हूं कि लंबे कोड निम्न कोड स्निपेट का उपयोग करके फाड़ सकते हैं जो बहुत जल्दी हिट करता है।सी #

 static void TestTearingLong() 
     { 
      System.Threading.Thread A = new System.Threading.Thread(ThreadA); 
      A.Start(); 

      System.Threading.Thread B = new System.Threading.Thread(ThreadB); 
      B.Start(); 
     } 

     static ulong s_x; 

     static void ThreadA() 
     { 
      int i = 0; 
      while (true) 
      { 
       s_x = (i & 1) == 0 ? 0x0L : 0xaaaabbbbccccddddL; 
       i++; 
      } 
     } 

     static void ThreadB() 
     { 
      while (true) 
      { 
       ulong x = s_x; 
       Debug.Assert(x == 0x0L || x == 0xaaaabbbbccccddddL); 
      } 
     } 

लेकिन जब मैं युगल के साथ कुछ ऐसा करने की कोशिश करता हूं, तो मैं किसी भी फाड़ने में सक्षम नहीं हूं। क्या किसी को पता है क्यों? जहां तक ​​मैं कल्पना से बता सकता हूं, केवल एक फ्लोट के लिए असाइनमेंट परमाणु है। एक डबल के लिए असाइनमेंट को फाड़ने का खतरा होना चाहिए।

static double s_x; 

    static void TestTearingDouble() 
    { 
     System.Threading.Thread A = new System.Threading.Thread(ThreadA); 
     A.Start(); 

     System.Threading.Thread B = new System.Threading.Thread(ThreadB); 
     B.Start(); 
    } 

    static void ThreadA() 
    { 
     long i = 0; 

     while (true) 
     { 
      s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; 
      i++; 

      if (i % 10000000 == 0) 
      { 
       Console.Out.WriteLine("i = " + i); 
      } 
     } 
    } 

    static void ThreadB() 
    { 
     while (true) 
     { 
      double x = s_x; 

      System.Diagnostics.Debug.Assert(x == 0.0 || x == double.MaxValue); 
     } 
    } 
+4

बेवकूफ सवाल - क्या फाड़ रहा है? – Oded

+0

इनट्स पर संचालन कई थ्रेडों के उपयोग के संबंध में परमाणु होने की गारंटी है। इतने लंबे समय तक नहीं। फाड़ना दो अंतरिम मूल्यों (बुरा) का मिश्रण हो रहा है। वह सोच रहा है कि युगल में ऐसा क्यों नहीं देखा जाता है, क्योंकि युगल भी परमाणु परिचालन की गारंटी नहीं देता है। – hatchet

+13

@ ओडेड: 32 बिट मशीनों पर, केवल 32 बिट्स एक समय में लिखे जाते हैं। यदि आप 32 बिट मशीन पर 64 बिट मान लिख रहे हैं, और उसी समय एक ही पते पर दो अलग-अलग धागे पर लिख रहे हैं, तो आपके पास वास्तव में * चार * लिखते हैं, * * * नहीं, क्योंकि लिखते हैं 32 बिट्स एक वक़्त। इसलिए धागे दौड़ने के लिए यह संभव है, और जब धूम्रपान चर को साफ़ करता है तो एक थ्रेड द्वारा लिखे गए शीर्ष 32 बिट्स और दूसरे द्वारा लिखे गए 32 बिट्स होते हैं। तो आप 0xDEADBEEF00000000 को एक थ्रेड और 0x00000000BAADF00D पर दूसरे पर लिख सकते हैं, और मेमोरी में 0x0000000000000000 के साथ समाप्त हो सकते हैं। –

उत्तर

10
static double s_x; 

यह जब आप एक डबल का उपयोग प्रभाव का प्रदर्शन करने में बहुत कठिन है। सीपीयू डबल, क्रमशः FLD और FSTP को लोड और स्टोर करने के लिए समर्पित निर्देशों का उपयोग करता है। लंबे के साथ यह बहुत आसान है क्योंकि 32-बिट मोड में 64-बिट पूर्णांक लोड/स्टोर करने वाला कोई भी निर्देश नहीं है। इसे देखने के लिए आपको चर के पते को गलत तरीके से हस्ताक्षर करने की आवश्यकता है ताकि यह सीपीयू कैश लाइन सीमा को झुकाए।

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

ऐसा करने का सबसे अच्छा तरीका जानबूझकर एक सूचक का उपयोग कर डबल को संरेखित करना है।कार्यक्रम कक्षा के सामने असुरक्षित रखो और यह कुछ ऐसा दिखाई बनाने:

static double* s_x; 

    static void Main(string[] args) { 
     var mem = Marshal.AllocCoTaskMem(100); 
     s_x = (double*)((long)(mem) + 28); 
     TestTearingDouble(); 
    } 
ThreadA: 
      *s_x = ((i & 1) == 0) ? 0.0 : double.MaxValue; 
ThreadB: 
      double x = *s_x; 

यह अभी भी एक अच्छा मिसलिग्न्मेंट (hehe) की गारंटी नहीं है वहाँ वास्तव में नियंत्रित करने के लिए जहां AllocCoTaskMem() संरेखित होगा कोई रास्ता नहीं है के बाद से सीपीयू कैश लाइन की शुरुआत के सापेक्ष आवंटन। और यह आपके सीपीयू कोर में कैश एसोसिएटिविटी पर निर्भर करता है (मेरा कोर i5 है)। आपको ऑफसेट के साथ टिंकर करना होगा, मुझे प्रयोग द्वारा मूल्य 28 मिला है। मान 4 से विभाजित होना चाहिए, लेकिन 8 तक नहीं, वास्तव में जीसी ढेर व्यवहार को अनुकरण करने के लिए। जब तक आप कैश लाइन को स्ट्रैडल करने के लिए डबल न हों और जोर से ट्रिगर न करें तब तक मान को 8 में जोड़ते रहें।

इसे कम कृत्रिम बनाने के लिए आपको एक ऐसा प्रोग्राम लिखना होगा जो कक्षा के क्षेत्र में डबल स्टोर करता है और कचरा कलेक्टर को स्मृति में चारों ओर स्थानांतरित करने के लिए मिलता है, इसलिए इसे गलत तरीके से गलत किया जाता है। किंडा को एक नमूना कार्यक्रम के साथ आने के लिए मुश्किल है कि यह सुनिश्चित करता है कि ऐसा होता है।

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

+0

मुझे समझ में नहीं आता कि कैश लाइन सीमा पार करने से फाड़ने का कारण बन सकता है। मैंने सोचा कि यह केवल एक रजिस्टर के आकार की तुलना में अधिक जगह लेने के मूल्य के कारण हुआ था। क्या आप कृपया इस पर थोड़ा और विस्तार कर सकते हैं? – Tudor

+0

@ ट्यूडर - यह एक पूरी तरह से अलग प्रभाव है, रजिस्टर आकार से जुड़ा नहीं है। अंतिम पैराग्राफ पर फ़ोकस करें, ध्यान दें कि कैसे सीपीयू कैश सिंक्रनाइज़ेशन में अद्यतन की इकाई के रूप में कैश लाइन होती है। एक ग़लत गठबंधन जो एक रेखा को झुकाव करता है * दो * अपडेट की आवश्यकता होती है, जिस तरह से दो रजिस्टर लिखने की आवश्यकता होती है। जो कोड को अनुमति देने के लिए पर्याप्त समय लेता है जो फाड़ने का निरीक्षण करने के लिए किसी अन्य कोर पर चलता है। –

11

जितना अजीब लगता है, यह आपके सीपीयू पर निर्भर करता है। जबकि युगल की गारंटी नहीं है फाड़ने के लिए नहीं, वे कई मौजूदा प्रोसेसर पर नहीं होंगे। यदि आप इस स्थिति में फाड़ना चाहते हैं तो एएमडी सेमप्रॉन आज़माएं।

संपादित करें: कुछ साल पहले कठिन तरीके से सीख लिया गया था।

+0

क्या यह फ़्लोटिंग पॉइंट रजिस्टरों के आकार के साथ ऐसा करने के लिए है? – leppie

+0

टीबीएच मेरे पास थोड़ा सा विचार नहीं है, इसे कभी नहीं देखा। मेरा एक डिमन (सभी भाषाओं के नि: शुल्क पास्कल) ने बेकार ढंग से बेकार परिणामों का उत्पादन शुरू किया और कई (शायद 100) में से एक मशीन, सभी एक ही छवि से स्थापित की गई। यह पता चला कि यह एक वैश्विक डबल था जिसे अपडेट किया गया था मुख्य धागा और एक जीटीके निर्मित माध्यमिक धागा। तब एफपीके में कोई लॉकिंग प्राइमेटिव नहीं ... (अपूर्ण, अपूर्ण) –

+0

हाँ, अगर मुझे सीपीयू पर एमएमएक्स या एसएसई एक्सटेंशन के साथ कुछ करना है तो मुझे शक नहीं होगा। – antiduh

0

कुछ खुदाई कर रहा, मैंने पाया कुछ रोचक x86 आर्किटेक्चर पर चल बिन्दु आपरेशनों के विषय में लिखा है:

Wikipedia के अनुसार, 86 फ्लोटिंग प्वाइंट यूनिट संग्रहीत 80-बिट रजिस्टरों में फ्लोटिंग प्वाइंट मान:

[...] बाद के 86 प्रोसेसर तो चिप पर इस x87 कार्यक्षमता जो x87 निर्देश एक वास्तविक अभिन्न x86 निर्देश सेट का हिस्सा बनाया एकीकृत। प्रत्येक x87 रजिस्टर, जिसे एसटी (0) के माध्यम से एसटी (7) के माध्यम से जाना जाता है, 80 बिट चौड़ा है और आईईईई फ्लोटिंग-पॉइंट मानक डबल विस्तारित सटीक प्रारूप में स्टोर्स नंबर है।

इसके अलावा इस अन्य तो सवाल यह संबंधित है: Some floating point precision and numeric limits question

यही कारण है कि, हालांकि युगल 64-बिट रहे हैं, वे atomically पर संचालित कर रहे हैं समझा सकता है।

0

इस विषय और कोड नमूना के लायक के लिए यहां क्या पाया जा सकता है।

http://msdn.microsoft.com/en-us/magazine/cc817398.aspx

+0

वह लेख केवल लंबे समय तक नहीं बल्कि डबल के बारे में बात करता है। – Tudor

+0

सहमत हुए। असल में, मुझे लगता है कि प्रश्न में मैंने जो नमूना कोड पोस्ट किया है वह उस पोस्ट से है (डबल सामान को छोड़कर)। (मैं इसे एक टेस्ट प्रोजेक्ट में था और थोड़ी देर के लिए इसके बारे में भूल गया था)। –