2011-12-26 13 views
7

निम्नलिखित नूनिट परीक्षण एक एकल थ्रेड चलाने के बीच प्रदर्शन को दोहरी कोर मशीन पर चलने वाले 2 धागे चलाने के बीच तुलना करता है। विशेष रूप से, यह एक वीएमवेयर डुअल कोर वर्चुअल विंडोज 7 मशीन है जो क्वाड कोर लिनक्स एसएलडीडी होस्ट पर चल रही है, जिसमें डेल इंस्पेरन 503 है।दोहरे कोर प्रदर्शन एकल कोर से भी बदतर है?

प्रत्येक थ्रेड बस 2 काउंटर, एडकॉन्टर और रीड काउंटर को लूप और बढ़ाता है। यह परीक्षण एक क्यूई कार्यान्वयन का मूल परीक्षण था जिसे बहु-कोर मशीन पर खराब प्रदर्शन करने के लिए खोजा गया था। तो समस्या को छोटे प्रतिलिपिबद्ध कोड में कम करने में, आपके पास कोई कतार केवल बढ़ती चर और सदमे और निराशा के लिए है, यह 2 धागे के साथ बहुत धीमी है।

पहला परीक्षण चलाते समय, टास्क मैनेजर कोर के 1% को अन्य कोर के साथ लगभग 100% व्यस्त दिखाता है। सिंगल थ्रेड टेस्ट के लिए टेस्ट आउटपुट यहां दिया गया है:

readCounter 360687000 
readCounter2 0 
total readCounter 360687000 
addCounter 360687000 
addCounter2 0 

आप 360 मिलियन से अधिक वेतन वृद्धि देखते हैं!

अगला दोहरी धागा परीक्षण परीक्षण के पूरे 5 सेकंड की अवधि के लिए दोनों कोरों पर 100% व्यस्त दिखाता है। लेकिन यह उत्पादन पता चलता है केवल:

readCounter 88687000 
readCounter2 134606500 
totoal readCounter 223293500 
addCounter 88687000 
addCounter2 67303250 
addFailure0 

केवल 223 मिलियन पढ़ने वेतन वृद्धि है कि। भगवान की सृष्टि क्या है उन 2 सीपीयू कम काम करने के लिए उन 5 सेकंड के लिए कर रहे हैं?

कोई संभावित सुराग? और क्या आप यह देखने के लिए अपनी मशीन पर परीक्षण चला सकते हैं कि आपको अलग-अलग परिणाम मिलते हैं या नहीं? एक विचार यह है कि शायद वीएमवेयर डुअल कोर प्रदर्शन वह नहीं है जो आप उम्मीद करेंगे।

using System; 
using System.Threading; 
using NUnit.Framework; 

namespace TickZoom.Utilities.TickZoom.Utilities 
{ 
    [TestFixture] 
    public class ActiveMultiQueueTest 
    { 
     private volatile bool stopThread = false; 
     private Exception threadException; 
     private long addCounter; 
     private long readCounter; 
     private long addCounter2; 
     private long readCounter2; 
     private long addFailureCounter; 

     [SetUp] 
     public void Setup() 
     { 
      stopThread = false; 
      addCounter = 0; 
      readCounter = 0; 
      addCounter2 = 0; 
      readCounter2 = 0; 
     } 


     [Test] 
     public void TestSingleCoreSpeed() 
     { 
      var speedThread = new Thread(SpeedTestLoop); 
      speedThread.Name = "1st Core Speed Test"; 
      speedThread.Start(); 
      Thread.Sleep(5000); 
      stopThread = true; 
      speedThread.Join(); 
      if (threadException != null) 
      { 
       throw new Exception("Thread failed: ", threadException); 
      } 
      Console.Out.WriteLine("readCounter " + readCounter); 
      Console.Out.WriteLine("readCounter2 " + readCounter2); 
      Console.Out.WriteLine("total readCounter " + (readCounter + readCounter2)); 
      Console.Out.WriteLine("addCounter " + addCounter); 
      Console.Out.WriteLine("addCounter2 " + addCounter2); 
     } 

     [Test] 
     public void TestDualCoreSpeed() 
     { 
      var speedThread1 = new Thread(SpeedTestLoop); 
      speedThread1.Name = "Speed Test 1"; 
      var speedThread2 = new Thread(SpeedTestLoop2); 
      speedThread2.Name = "Speed Test 2"; 
      speedThread1.Start(); 
      speedThread2.Start(); 
      Thread.Sleep(5000); 
      stopThread = true; 
      speedThread1.Join(); 
      speedThread2.Join(); 
      if (threadException != null) 
      { 
       throw new Exception("Thread failed: ", threadException); 
      } 
      Console.Out.WriteLine("readCounter " + readCounter); 
      Console.Out.WriteLine("readCounter2 " + readCounter2); 
      Console.Out.WriteLine("totoal readCounter " + (readCounter + readCounter2)); 
      Console.Out.WriteLine("addCounter " + addCounter); 
      Console.Out.WriteLine("addCounter2 " + addCounter2); 
      Console.Out.WriteLine("addFailure" + addFailureCounter); 
     } 

     private void SpeedTestLoop() 
     { 
      try 
      { 
       while (!stopThread) 
       { 
        for (var i = 0; i < 500; i++) 
        { 
         ++addCounter; 
        } 
        for (var i = 0; i < 500; i++) 
        { 
         readCounter++; 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       threadException = ex; 
      } 
     } 

     private void SpeedTestLoop2() 
     { 
      try 
      { 
       while (!stopThread) 
       { 
        for (var i = 0; i < 500; i++) 
        { 
         ++addCounter2; 
         i++; 
        } 
        for (var i = 0; i < 500; i++) 
        { 
         readCounter2++; 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       threadException = ex; 
      } 
     } 


    } 
} 

संपादित करें: मैं ऊपर एक क्वाड-कोर लैपटॉप पर w/ओ VMware परीक्षण किया है और इसी तरह की अवक्रमित प्रदर्शन मिला है। इसलिए मैंने उपर्युक्त के समान एक और परीक्षण लिखा लेकिन प्रत्येक थ्रेड विधि को एक अलग वर्ग में रखा गया है। ऐसा करने का मेरा उद्देश्य 4 कोर का परीक्षण करना था।

वैसे परीक्षण ने उत्कृष्ट परिणामों को दिखाया जो लगभग 1, 2, 3, या 4 कोर के साथ लगभग रैखिक रूप से सुधार हुआ।

कुछ प्रयोगों के साथ अब दोनों मशीनों पर ऐसा लगता है कि उचित प्रदर्शन केवल तभी होता है जब मुख्य थ्रेड विधियां एक ही उदाहरण के बजाय अलग-अलग उदाहरणों पर हों।

दूसरे शब्दों में, यदि किसी विशेष वर्ग के एक ही उदाहरण पर एकाधिक धागे मुख्य प्रविष्टि विधि, तो आपके द्वारा जोड़े गए प्रत्येक थ्रेड के लिए बहु-कोर पर प्रदर्शन खराब होगा, जैसा कि आप मान सकते हैं।

यह लगभग प्रतीत होता है कि सीएलआर "सिंक्रनाइज़िंग" है, इसलिए उस समय केवल एक थ्रेड उस विधि पर चल सकता है। हालांकि, मेरा परीक्षण कहता है कि यह मामला नहीं है। तो यह अभी भी अस्पष्ट है कि क्या हो रहा है।

लेकिन मेरी खुद की समस्या को थ्रेड को अपने प्रारंभिक बिंदु के रूप में चलाने के तरीकों के अलग-अलग उदाहरण बनाकर हल किया जाना प्रतीत होता है।

निष्ठा से, वेन

संपादित करें:

यहाँ एक अद्यतन इकाई परीक्षण है कि एक वर्ग का एक ही उदाहरण पर 1, 2, 3, 4 & उन सभी के साथ धागे का परीक्षण करती है। चर के साथ सरणी का उपयोग थ्रेड लूप में कम से कम 10 तत्वों के अलावा उपयोग करता है। और प्रदर्शन अभी भी प्रत्येक थ्रेड के लिए महत्वपूर्ण रूप से घटता है।

using System; 
using System.Threading; 
using NUnit.Framework; 

namespace TickZoom.Utilities.TickZoom.Utilities 
{ 
    [TestFixture] 
    public class MultiCoreSameClassTest 
    { 
     private ThreadTester threadTester; 
     public class ThreadTester 
     { 
      private Thread[] speedThread = new Thread[400]; 
      private long[] addCounter = new long[400]; 
      private long[] readCounter = new long[400]; 
      private bool[] stopThread = new bool[400]; 
      internal Exception threadException; 
      private int count; 

      public ThreadTester(int count) 
      { 
       for(var i=0; i<speedThread.Length; i+=10) 
       { 
        speedThread[i] = new Thread(SpeedTestLoop); 
       } 
       this.count = count; 
      } 

      public void Run() 
      { 
       for (var i = 0; i < count*10; i+=10) 
       { 
        speedThread[i].Start(i); 
       } 
      } 

      public void Stop() 
      { 
       for (var i = 0; i < stopThread.Length; i+=10) 
       { 
        stopThread[i] = true; 
       } 
       for (var i = 0; i < count * 10; i += 10) 
       { 
        speedThread[i].Join(); 
       } 
       if (threadException != null) 
       { 
        throw new Exception("Thread failed: ", threadException); 
       } 
      } 

      public void Output() 
      { 
       var readSum = 0L; 
       var addSum = 0L; 
       for (var i = 0; i < count; i++) 
       { 
        readSum += readCounter[i]; 
        addSum += addCounter[i]; 
       } 
       Console.Out.WriteLine("Thread readCounter " + readSum + ", addCounter " + addSum); 
      } 

      private void SpeedTestLoop(object indexarg) 
      { 
       var index = (int) indexarg; 
       try 
       { 
        while (!stopThread[index*10]) 
        { 
         for (var i = 0; i < 500; i++) 
         { 
          ++addCounter[index*10]; 
         } 
         for (var i = 0; i < 500; i++) 
         { 
          ++readCounter[index*10]; 
         } 
        } 
       } 
       catch (Exception ex) 
       { 
        threadException = ex; 
       } 
      } 
     } 

     [SetUp] 
     public void Setup() 
     { 
     } 


     [Test] 
     public void SingleCoreTest() 
     { 
      TestCores(1); 
     } 

     [Test] 
     public void DualCoreTest() 
     { 
      TestCores(2); 
     } 

     [Test] 
     public void TriCoreTest() 
     { 
      TestCores(3); 
     } 

     [Test] 
     public void QuadCoreTest() 
     { 
      TestCores(4); 
     } 

     public void TestCores(int numCores) 
     { 
      threadTester = new ThreadTester(numCores); 
      threadTester.Run(); 
      Thread.Sleep(5000); 
      threadTester.Stop(); 
      threadTester.Output(); 
     } 
    } 
} 
+0

क्या आप इसे डिबगर संलग्न किए बिना रिलीज़ मोड में चला रहे हैं? –

+0

नोट: आपके कोड में कोई थ्रेड सिंक्रनाइज़ेशन ऑपरेशन नहीं है (ताले या इंटरलॉक या कुछ और)। यदि आप इसे पसंद करते हैं तो आप सभी मानों के लिए रैंडम का उपयोग भी कर सकते हैं क्योंकि बिना सिंक्रनाइज़ेशन के मल्टीथ्रेड कोड को सही तरीके से चलाने का कोई तरीका नहीं है। –

+0

जिम, मैं इसे दोनों तरीकों से आजमाया गया हूं। लेकिन चर्चा की गई संख्या डीबग मोड के दौरान होती है। एलेक्सेल..मैं ताले और interleaved की जरूरत के बारे में पता है। बेशक। लेकिन यह सिर्फ प्रयोगात्मक कोड है। प्रयोग ताले के साथ शुरू हुआ लेकिन प्रदर्शन भयानक था - अधिक धागे/कोर के साथ बदतर। इसलिए मैंने ताले को यह देखने के लिए हटा दिया कि यह तेज है या नहीं। नहीं ... अभी भी बुरा है। तो मैं अलग करने की कोशिश कर रहा हूं क्यों 4 कोर एक ही कोड चल रहे हैं w/o किसी भी ताले तो कुत्ते धीमा ??? तुम जानते हो क्यों? – Wayne

उत्तर

5

यह केवल 223 मिलियन पढ़ने की वृद्धि है। भगवान की सृष्टि क्या है उन 2 सीपीयू कम काम करने के लिए उन 5 सेकंड के लिए कर रहे हैं?

आप शायद कैश विवाद में चला रहे हैं -, जब एक ही सीपीयू अपने पूर्णांक incrementing है, यह अपने आप ही एल 1 कैश में ऐसा कर सकते हैं, लेकिन जैसे ही दो के रूप में सीपीयू एक ही मूल्य से अधिक "लड़ाई" शुरू जब भी प्रत्येक व्यक्ति इसे एक्सेस करता है तो कैश लाइन को अपने कैश के बीच आगे और पीछे कॉपी करना पड़ता है। कैश के बीच डेटा कॉपी करने में अतिरिक्त समय तेजी से जोड़ता है, खासकर जब आप जो ऑपरेशन कर रहे हैं (एक पूर्णांक में वृद्धि) इतना छोटा है।

+0

दिलचस्प कोण। कृपया मेरे संपादनों को अधिक प्रयोगों के परिणामों से देखें। क्या आपको लगता है कि सीएलआर में एल 1 कैश विवाद होता है जब थ्रेड का प्रवेश बिंदु अलग-अलग उदाहरण बनाम कक्षा के समान उदाहरणों पर होता है? यदि ऐसा है, तो आप इसे स्पष्टीकरण पर हो सकता है। – Wayne

+0

संदर्भ के लिए, इस "मेमोरी स्वैपिंग" समस्या को "थ्रैशिंग" कहा जाता है - http://en.wikipedia.org/wiki/Thrashing_(computer_science) –

+0

कुछ और परीक्षण बताते हैं कि वृद्धि के लिए इंटरलाक्ड का उपयोग करने के अतिरिक्त, फिर लगातार प्रदर्शन : क्वाड कोर पर 2 धागे का उपयोग करने के लिए केवल 1 से घटता है, फिर यह तीसरे धागे पर यूपी जाता है और फिर चौथा धागा जोड़ने के साथ घटता है। यह स्पष्ट है कि "बहु-सीपीयू" के साथ कुछ करना है जो मुझे याद आ रही है। कुछ प्रकार की "बातचीत" चल रही है कि क्या यह एल 1 कैश कुछ और है। – Wayne

0

कुछ बातें:

  1. आप शायद प्रत्येक सेटअप में कम से कम 10 बार परीक्षण करने और जहाँ तक मुझे पता के रूप में औसत
  2. लेना चाहिए, Thread.Sleep सटीक नहीं है - यह पर निर्भर करता है ओएस आपके धागे को कैसे स्विच करता है
  3. Thread.join तत्काल नहीं है। फिर, यह कैसे ओएस अपने धागे

एक बेहतर परीक्षण करने के लिए एक कंप्यूटेशनल रूप से संवेदनशील आपरेशन चलाने के लिए किया जाएगा जिस तरह से (जैसे कि, योग एक से एक लाख तक) दो विन्यास और उन्हें समय पर स्विच पर निर्भर करता है। उदाहरण के लिए:

  1. समय कितनी देर तक यह एक लाख
  2. समय कितना समय एक-दूसरे

पर 1000000 करने के लिए एक धागा और एक 500,001 पर 500000 को योग करने के लिए ले जाता है एक से योग करने के लिए ले जाता है आप सही थे जब आपने सोचा था कि दो धागे एक धागे से तेज काम करेंगे। लेकिन आपका एकमात्र थ्रेड नहीं चल रहा है - ओएस में धागे हैं, आपके ब्राउज़र में धागे हैं, और इसी तरह। ध्यान रखें कि आपका समय सटीक नहीं होगा और यहां तक ​​कि उतार-चढ़ाव भी हो सकता है।

आखिरकार, other reasons (स्लाइड 24 देखें) क्यों धागे धीमे काम करते हैं।

+0

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

+0

उस मामले में, मैं duskwuff से सहमत हूं। यह अधिक संभावना है कि थ्रैशिंग तब होती है जब धागे एक ही उदाहरण पर होते हैं। इसे आज़माएं: readCounter और readCounter2 में जोड़ने के बजाय, एक सरणी में जोड़ें उदा। एआर [0] और एआर [5]। देखें कि जब आप दोनों के बीच अंतर बदलते हैं तो क्या होता है। –

+0

तो उन्हें अलग कैश लाइनों में मजबूर करने के लिए एक सरणी का उपयोग करना? दिलचस्प। मैं उपरोक्त एक ही टेस्ट कोड का उपयोग करूंगा और ठीक उसी कोशिश करूँगा। कृपया मुझे कुछ मिनट दें। कोशिश करने के लिए इंतजार नहीं कर सकता। – Wayne

संबंधित मुद्दे