2011-02-11 7 views
9

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

क्या कोई मुझे यह समझा सकता है?

मैंने इसका परीक्षण करने के लिए एक छोटा सा नमूना तैयार किया है। यह एक कंसोल कार्यक्रम है। यह प्रत्येक प्रोसेसर के लिए एक धागा बनाता है और इसकी गति को एक साधारण परीक्षण (एक "नया ऑब्जेक्ट()") के साथ मापता है। नहीं, "नया ऑब्जेक्ट()" दूर नहीं है (अगर आप मुझ पर भरोसा नहीं करते हैं तो कोशिश करें)। मुख्य धागा प्रत्येक धागे की गति दिखाता है। CTRL-C दबाकर, प्रोग्राम 50 "सोने" धागे पैदा करता है। धीमी गति केवल 50 धागे से शुरू होती है। लगभग 250 के साथ यह कार्य प्रबंधक पर बहुत दिखाई देता है कि सीपीयू 100% उपयोग नहीं किया जाता है (मेरे 82% पर)।

मैं "सो" धागा लॉकिंग की तीन तरीकों की कोशिश की है: Thread.CurrentThread.Suspend() (बुरा, बुरा, मुझे पता है :-)), एक पहले से ही बंद कर दिया वस्तु पर एक ताला और एक Thread.Sleep (समय समाप्त .Infinite)। यह ऐसा ही है। यदि मैं नई ऑब्जेक्ट() के साथ पंक्ति पर टिप्पणी करता हूं, और मैं इसे Math.qrt (या कुछ भी नहीं) के साथ प्रतिस्थापित करता हूं तो समस्या मौजूद नहीं है। गति धागे की संख्या के साथ बदल नहीं है। क्या कोई और इसे देख सकता है? क्या कोई जानता है कि बोतल की गर्दन कहां है?

आह ... आपको इसे विजुअल स्टूडियो से लॉन्च किए बिना रिलीज मोड में परीक्षण करना चाहिए। मैं एक दोहरी प्रोसेसर (कोई एचटी) पर XP SP3 का उपयोग कर रहा हूं। कुछ या तो की जरूरत है - मैं (अलग ढांचा runtimes परीक्षण करने के लिए)

namespace TestSpeed 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Threading; 

    class Program 
    { 
     private const long ticksInSec = 10000000; 
     private const long ticksInMs = ticksInSec/1000; 
     private const int threadsTime = 50; 
     private const int stackSizeBytes = 256 * 1024; 
     private const int waitTimeMs = 1000; 

     private static List<int> collects = new List<int>(); 
     private static int[] objsCreated; 

     static void Main(string[] args) 
     { 
      objsCreated = new int[Environment.ProcessorCount]; 
      Monitor.Enter(objsCreated); 

      for (int i = 0; i < objsCreated.Length; i++) 
      { 
       new Thread(Worker).Start(i); 
      } 

      int[] oldCount = new int[objsCreated.Length]; 

      DateTime last = DateTime.UtcNow; 

      Console.Clear(); 

      int numThreads = 0; 
      Console.WriteLine("Press Ctrl-C to generate {0} sleeping threads, Ctrl-Break to end.", threadsTime); 

      Console.CancelKeyPress += (sender, e) => 
      { 
       if (e.SpecialKey != ConsoleSpecialKey.ControlC) 
       { 
        return; 
       } 

       for (int i = 0; i < threadsTime; i++) 
       { 
        new Thread(() => 
        { 
         /* The same for all the three "ways" to lock forever a thread */ 
         //Thread.CurrentThread.Suspend(); 
         //Thread.Sleep(Timeout.Infinite); 
         lock (objsCreated) { } 
        }, stackSizeBytes).Start(); 

        Interlocked.Increment(ref numThreads); 
       } 

       e.Cancel = true; 
      }; 

      while (true) 
      { 
       Thread.Sleep(waitTimeMs); 

       Console.SetCursorPosition(0, 1); 

       DateTime now = DateTime.UtcNow; 

       long ticks = (now - last).Ticks; 

       Console.WriteLine("Slept for {0}ms", ticks/ticksInMs); 

       Thread.MemoryBarrier(); 

       for (int i = 0; i < objsCreated.Length; i++) 
       { 
        int count = objsCreated[i]; 
        Console.WriteLine("{0} [{1} Threads]: {2}/sec ", i, numThreads, ((long)(count - oldCount[i])) * ticksInSec/ticks); 
        oldCount[i] = count; 
       } 

       Console.WriteLine(); 

       CheckCollects(); 

       last = now; 
      } 
     } 

     private static void Worker(object obj) 
     { 
      int ix = (int)obj; 

      while (true) 
      { 
       /* First and second are slowed by threads, third, fourth, fifth and "nothing" aren't*/ 

       new Object(); 
       //if (new Object().Equals(null)) return; 
       //Math.Sqrt(objsCreated[ix]); 
       //if (Math.Sqrt(objsCreated[ix]) < 0) return; 
       //Interlocked.Add(ref objsCreated[ix], 0); 

       Interlocked.Increment(ref objsCreated[ix]); 
      } 
     } 

     private static void CheckCollects() 
     { 
      int newMax = GC.MaxGeneration; 

      while (newMax > collects.Count) 
      { 
       collects.Add(0); 
      } 

      for (int i = 0; i < collects.Count; i++) 
      { 
       int newCol = GC.CollectionCount(i); 

       if (newCol != collects[i]) 
       { 
        collects[i] = newCol; 
        Console.WriteLine("Collect gen {0}: {1}", i, newCol); 
       } 
      } 
     } 
    } 
} 
+3

यदि आप प्रदर्शन के बारे में चिंतित हैं, तो आपके पास (cpucount) थ्रेड से अधिक नहीं होना चाहिए। (Cpucount + 2) और (cpucount * 2) के बीच अंगूठे के अच्छे नियम हैं (और आपके सिस्टम पर, दोनों 4 तक आते हैं)। सोने के बजाय कुछ धागे व्यस्त रखने के लिए एसिंक्रोनस I/O संचालन की कतारों का उपयोग करें। लॉक के लिए दंडित होने पर केवल एक थ्रेड का इंतजार करना चाहिए। –

+0

मैं एक "धीमी गति" coroutines कर रहा हूँ। धागे के बीच "स्विच टाइम" अप्रासंगिक है, इसलिए मैं धागे का उपयोग कर सकता हूं (मेरे पास "स्विच"/सेकेंड है, इसलिए यदि यह पुराने धागे और नए थ्रेड के बीच स्विच करने के लिए कुछ एमएस खो देता है तो मेरे पास नहीं है कोई परेशानी)। प्रोसेसर के बराबर चलने वाले कई धागे हमेशा होते हैं, लेकिन यदि सोते धागे सबकुछ धीमा करते हैं, तो मुझे एक समस्या है। नहीं, मैं एमएस की एसिंक लाइब्रेरी का उपयोग नहीं कर सकता, क्योंकि यह "नकली" है। यह आपके कार्यक्रम को "पुनः लिखता है"। मुझे कुछ पूर्ववर्ती पुस्तकालयों का उपयोग करना है। – xanatos

+0

क्या आपने स्पष्ट रूप से धागे बनाने के बजाय टीपीएल का उपयोग करने पर विचार किया है? इस तरह ढांचा काम करने के लिए देशी धागे की सबसे उचित संख्या तय कर सकता है। –

उत्तर

5

मेरे अनुमान कि समस्या यह है कि कचरा संग्रहण धागे के बीच सहयोग की एक निश्चित राशि की आवश्यकता है है .NET 3.5 और 4.0 के साथ यह परीक्षण किया है जांच करने के लिए है कि वे सभी को निलंबित कर दिया रहे हैं, या खुद को निरस्त करने और ऐसा करने के लिए, आदि प्रतीक्षा करने के लिए उन्हें पूछना (और भले ही वे हैं निलंबित, यह उन्हें जगाने के लिए नहीं बताने के लिए है!)

यह बेशक, "दुनिया को रोकें" कचरा कलेक्टर का वर्णन करता है। मेरा मानना ​​है कि कम से कम दो या तीन अलग-अलग जीसी कार्यान्वयन हैं जो समांतरता के विवरणों में भिन्न हैं ... लेकिन मुझे संदेह है कि उनमें से सभी कुछ सहयोग करने के लिए धागे प्राप्त करने के मामले में काम करने के लिए काम कर रहे हैं।

+0

मैंने "सर्वर" जीसी की कोशिश की है। यह प्रत्येक प्रोसेसर के लिए एक जीसी और एक ढेर आवंटित करता है। ऐप बेहतर स्केल करता है। 100 धागे के साथ यह वस्तुओं के आवंटन पर "केवल" 10% गति खो देता है। – xanatos

+0

जितना अधिक परीक्षण मैं करता हूं, मुझे विश्वास है कि यह जीसी है। जीसी को "बेंचमार्क" करना और वस्तुओं के निर्माण के लिए समय से अपना समय अलग करना बहुत मुश्किल है, लेकिन अंत में यह मेरे पीओवी से कुछ भी नहीं बदलता है: कई धागे = धीमे "नई" वस्तुएं (कम से कम नई वस्तुएं जीसी संग्रह का कारण बनती हैं)। सर्वर जीसी = अच्छा जब कई धागे। मैं ऑब्जेक्ट पूलिंग का प्रयास कर सकता हूं लेकिन मुझे लगता है कि यह जटिलता में वृद्धि करेगा ... मैं देखूंगा। धन्यवाद! – xanatos

10

प्रारंभ Taskmgr.exe, प्रक्रिया टैब प्रारंभ करें। देखें + कॉलम का चयन करें, "पृष्ठ दोष डेल्टा" पर निशान लगाएं। आप सैकड़ों मेगाबाइट आवंटित करने के प्रभाव को देखेंगे, बस आपके द्वारा बनाए गए इन सभी धागे के ढेर को स्टोर करने के लिए। हर बार जब आपकी प्रक्रिया के लिए संख्या ब्लाइप्स होती है, तो आपका प्रोग्राम डिस्क से डेटा में रैम में ऑपरेटिंग सिस्टम पेजिंग के लिए इंतजार कर रहा है।

TANSTAAFL, मुफ्त लंच जैसी कोई चीज़ नहीं है।

+0

1 एमबी उपयोगकर्ता मोड स्टैक स्पेस प्लस एक और 1 एमबी देशी मोड स्टैक स्पेस, सृजन पर प्रत्येक थ्रेड के लिए डिफ़ॉल्ट आकार। –

+2

@ क्रिस, कोई देशी मोड स्टैक नहीं है, एक स्टैक दोनों परोसता है। हालांकि, बनाए गए प्रत्येक धागे में 24 केबी कर्नेल मोड स्टैक भी है। –

+0

@ हंस, स्पष्टीकरण के लिए धन्यवाद, मेरा मतलब मूल के स्थान पर कर्नेल था, लेकिन मैंने सोचा कि यह आकार भी 1 एमबी था। –

1

जो आप यहां देख रहे हैं वह कार्रवाई में जीसी है। जब आप अपनी प्रक्रिया में डीबगर संलग्न करते हैं तो आप देखेंगे कि फॉर्म

Unknown exception - code e0434f4e (first chance) 

फेंक दिए गए हैं। जीसी द्वारा निलंबित धागे को फिर से शुरू करने के कारण यह अपवाद हैं। जैसा कि आप जानते हैं कि यह आपकी प्रक्रिया के अंदर सस्पेंड/रेज़्यूमे थ्रेड को कॉल करने के लिए दृढ़ता से निराश है। यह प्रबंधित दुनिया में और भी सच है। एकमात्र प्राधिकरण जो इसे सुरक्षित रूप से कर सकता है वह जीसी है। आप SuspendThread पर एक ब्रेकपाइंट सेट जब आप

0118f010 5f3674da 00000000 00000000 83e36f53 KERNEL32!SuspendThread 
0118f064 5f28c51d 00000000 83e36e63 00000000 mscorwks!Thread::SysSuspendForGC+0x2b0 (FPO: [Non-Fpo]) 
0118f154 5f28a83d 00000001 00000000 00000000 mscorwks!WKS::GCHeap::SuspendEE+0x194 (FPO: [Non-Fpo]) 
0118f17c 5f28c78c 00000000 00000000 0000000c mscorwks!WKS::GCHeap::GarbageCollectGeneration+0x136 (FPO: [Non-Fpo]) 
0118f208 5f28a0d3 002a43b0 0000000c 00000000 mscorwks!WKS::gc_heap::try_allocate_more_space+0x15a (FPO: [Non-Fpo]) 
0118f21c 5f28a16e 002a43b0 0000000c 00000000 mscorwks!WKS::gc_heap::allocate_more_space+0x11 (FPO: [Non-Fpo]) 
0118f23c 5f202341 002a43b0 0000000c 00000000 mscorwks!WKS::GCHeap::Alloc+0x3b (FPO: [Non-Fpo]) 
0118f258 5f209721 0000000c 00000000 00000000 mscorwks!Alloc+0x60 (FPO: [Non-Fpo]) 
0118f298 5f2097e6 5e2d078c 83e36c0b 00000000 mscorwks!FastAllocateObject+0x38 (FPO: [Non-Fpo]) 

देखेंगे कि जीसी इससे पहले कि वह एक पूर्ण संग्रह कर सकते हैं अपने सूत्र के सभी को निलंबित करने की कोशिश करता है। मेरी मशीन पर (32 बिट, विंडोज 7, .NET 3.5 एसपी 1) मंदी इतनी नाटकीय नहीं है। मुझे थ्रेड गिनती और सीपीयू (गैर) उपयोग के बीच एक रैखिक निर्भरता दिखाई देती है। ऐसा लगता है कि आप प्रत्येक जीसी के लिए बढ़ी हुई लागत देख रहे हैं क्योंकि जीसी को पूर्ण संग्रह करने से पहले अधिक धागे निलंबित करना पड़ता है। दिलचस्प बात यह है कि समय मुख्य रूप से Usermode में बिताया जाता है ताकि कर्नेल सीमित कारक न हो।

मैं नेट को कम थ्रेड का उपयोग करने या अप्रबंधित कोड का उपयोग करने के अलावा कैसे प्राप्त कर सकता हूं, इस तरह से नेट को देख सकता हूं। यह हो सकता है कि यदि आप स्वयं द्वारा सीएलआर होस्ट करते हैं और भौतिक धागे के बजाय फाइबर का उपयोग करते हैं तो जीसी बहुत बेहतर पैमाने पर स्केल करेगा। दुर्भाग्यवश यह सुविधा .NET 2.0 के रिलेज़ चक्र के दौरान cut out थी। चूंकि अब 6 साल बाद यह उम्मीद है कि इसे फिर से जोड़ा जाएगा।

आपके थ्रेड गिनती के अलावा जीसी भी आपके ऑब्जेक्ट ग्राफ़ की जटिलता से सीमित है। इस "Do You Know The Costs Of Garbage?" पर एक नज़र डालें।

+0

+1 हाँ मुझे पता चला था कि यह जीसी कि समस्या पैदा कर रहा था । उन्होंने कहा कि शायद धागे कि पहले से ही कुछ करने के लिए इंतजार कर रहे हैं को निलंबित करने की कोशिश करता है, तो यह हे (एन) है एन = हे (एम) मीटर के साथ के बजाय धागे की कुल संख्या के साथ = धागे चल की संख्या। दुर्भाग्य से मैं पहले से ही फाइबर चाल शोध किया था और मैं इसे बाहर :-(कट गया था पता था और बड़े Async सीटीपी कुछ समस्याओं जहां यह एक टास्क कि कुछ और का इंतजार किए बिना तुरंत समाप्त धीमा चलेगा था (वे के साथ इसे हल किया जाना चाहिए था नए Async सीटीपी लेकिन मैंने इस दौरान एक और परियोजना पर काम शुरू किया) – xanatos

+0

मुझे लगता है कि यह ओ (एम) क्यों नहीं हो सकता है कि अगर आप एक समय समाप्ति के साथ प्रतीक्षा करते हैं कि आप कुछ धागे जीसी के बीच में जाग सकते हैं। कि इसके अलावा आप जहाँ से जीसी मानना ​​है कि यह जबकि यह supend सभी धागे करता निलंबित कर दिया है एक धागा जगा सकता है। –

+0

वे अन्य तरीकों से इसे हल किया जा सकता था। विभिन्न प्रतीक्षा ओएस के लिए सीधे संवाद करने के लिए जरूरत नहीं है। उनका पुनः आरंभ हो सकता है किया गया जीसी द्वारा "मध्यस्थता" है। वे इसे इस तरह से करने के लिए चुना है, हम इसके साथ काम करना है। – xanatos

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