संचालकों को विधि कॉल की तुलना में इतना धीमा क्यों है? (पुराने जेआईटी पर संरचनाएं धीमी हैं)


84

परिचय: मैं C # में उच्च-प्रदर्शन कोड लिखता हूं। हां, मुझे पता है कि C ++ मुझे बेहतर अनुकूलन देगा, लेकिन मैं अभी भी C # का उपयोग करना चाहता हूं। मैं उस पसंद पर बहस नहीं करना चाहता। बल्कि, मैं उन लोगों से सुनना चाहूंगा, जो मेरी तरह, .NET फ्रेमवर्क पर उच्च-प्रदर्शन कोड लिखने की कोशिश कर रहे हैं।

प्रशन:

  • समतुल्य विधि कॉल की तुलना में कोड क्यों धीमा है?
  • जिस कोड में दो डबल्स होते हैं, उसके समतुल्य विधि से तेजी से नीचे कोड में दो डबल्स पास करने की विधि क्यों है? (ए: पुराने JITs खराब तरीके से संरचना का अनुकूलन करते हैं)
  • क्या नेट जेआईटी कंपाइलर को सरल तरीके से संरचना के सदस्यों के रूप में कुशलतापूर्वक व्यवहार करने का एक तरीका है? (ए: नई जेआईटी प्राप्त करें)

मुझे क्या लगता है मुझे पता है: मूल .NET JIT कम्पाइलर एक संरचना को शामिल करने वाली किसी भी चीज़ को इनलाइन नहीं करेगा। विचित्र दी गई संरचना का उपयोग केवल वहीं किया जाना चाहिए जहां आपको छोटे मूल्य प्रकारों की आवश्यकता होती है जिन्हें बिल्ट-इन की तरह अनुकूलित किया जाना चाहिए, लेकिन यह सच है। सौभाग्य से, .NET 3.5SP1 और .NET 2.0SP2 में, उन्होंने जेआईटी ऑप्टिमाइज़र में कुछ सुधार किए, जिसमें इनलाइनिंग में सुधार शामिल है, विशेष रूप से संरचनाओं के लिए। (मैं अनुमान लगा रहा हूं कि उन्होंने ऐसा इसलिए किया क्योंकि अन्यथा वे जिस नए कॉम्प्लेक्स स्ट्रक्चर की शुरुआत कर रहे थे, वह बहुत ही खराब था ... इसलिए कॉम्प्लेक्स टीम शायद JIT ऑप्टीमाइजर टीम पर भारी पड़ रही थी।) इसलिए, .NET 3.5 SP1 से पहले कोई भी दस्तावेज शायद है। इस मुद्दे के लिए भी प्रासंगिक नहीं है।

मेरा परीक्षण क्या दिखाता है: मैंने सत्यापित किया है कि मेरे पास नया JIT ऑप्टिमाइज़र है जो C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll फ़ाइल की जाँच करके संस्करण> = 3053 है और इसलिए उन सुधारों को करना चाहिए " JIT अनुकूलक के लिए। हालाँकि, इसके साथ भी, मेरी टाइमिंग और डिसएडफास्ट में दिख रहे दोनों शो क्या हैं:

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

JIT- निर्मित कोड एक संरचनात्मक विधि के लिए 'इस' में कहीं अधिक कुशलता से गुजरता है अगर आपने एक तर्क के रूप में एक संरचना पारित की।

JIT अभी भी बेहतर ढंग से सुधरता है अगर आप दो डबल्स के साथ एक स्ट्रक्चर पास करने के बजाय दो डबल्स पास करते हैं, यहां तक ​​कि मल्टीपियर के साथ लूप में स्पष्ट रूप से होने के कारण।

टाइमिंग: दरअसल, डिस्सैस को देखकर मुझे एहसास होता है कि लूप में ज्यादातर समय टेस्ट डेटा को लिस्ट से बाहर निकालने का होता है। यदि आप लूप के ओवरहेड कोड और डेटा तक पहुंचने के कारक हैं, तो एक ही कॉल करने के चार तरीकों के बीच अंतर नाटकीय रूप से अलग है। मुझे PlusEqual (तत्व) के बजाय PlusEqual (डबल, डबल) करने के लिए 5x से 20x स्पीडअप तक कहीं भी मिलता है। और ऑपरेटर + = के बजाय PlusEqual (डबल, डबल) करने के लिए 10x से 40x। वाह। उदास।

यहाँ समय का एक सेट है:

Populating List<Element> took 320ms.
The PlusEqual() method took 105ms.
The 'same' += operator took 131ms.
The 'same' -= operator took 139ms.
The PlusEqual(double, double) method took 68ms.
The do nothing loop took 66ms.
The ratio of operator with constructor to method is 124%.
The ratio of operator without constructor to method is 132%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 64%.
If we remove the overhead time for the loop accessing the elements from the List...
The ratio of operator with constructor to method is 166%.
The ratio of operator without constructor to method is 187%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 5%.

कोड:

namespace OperatorVsMethod
{
  public struct Element
  {
    public double Left;
    public double Right;

    public Element(double left, double right)
    {
      this.Left = left;
      this.Right = right;
    }

    public static Element operator +(Element x, Element y)
    {
      return new Element(x.Left + y.Left, x.Right + y.Right);
    }

    public static Element operator -(Element x, Element y)
    {
      x.Left += y.Left;
      x.Right += y.Right;
      return x;
    }    

    /// <summary>
    /// Like the += operator; but faster.
    /// </summary>
    public void PlusEqual(Element that)
    {
      this.Left += that.Left;
      this.Right += that.Right;
    }    

    /// <summary>
    /// Like the += operator; but faster.
    /// </summary>
    public void PlusEqual(double thatLeft, double thatRight)
    {
      this.Left += thatLeft;
      this.Right += thatRight;
    }    
  }    

  [TestClass]
  public class UnitTest1
  {
    [TestMethod]
    public void TestMethod1()
    {
      Stopwatch stopwatch = new Stopwatch();

      // Populate a List of Elements to multiply together
      int seedSize = 4;
      List<double> doubles = new List<double>(seedSize);
      doubles.Add(2.5d);
      doubles.Add(100000d);
      doubles.Add(-0.5d);
      doubles.Add(-100002d);

      int size = 2500000 * seedSize;
      List<Element> elts = new List<Element>(size);

      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        int di = ii % seedSize;
        double d = doubles[di];
        elts.Add(new Element(d, d));
      }
      stopwatch.Stop();
      long populateMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of += operator (calls ctor)
      Element operatorCtorResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        operatorCtorResult += elts[ii];
      }
      stopwatch.Stop();
      long operatorCtorMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of -= operator (+= without ctor)
      Element operatorNoCtorResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        operatorNoCtorResult -= elts[ii];
      }
      stopwatch.Stop();
      long operatorNoCtorMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of PlusEqual(Element) method
      Element plusEqualResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        plusEqualResult.PlusEqual(elts[ii]);
      }
      stopwatch.Stop();
      long plusEqualMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of PlusEqual(double, double) method
      Element plusEqualDDResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        Element elt = elts[ii];
        plusEqualDDResult.PlusEqual(elt.Left, elt.Right);
      }
      stopwatch.Stop();
      long plusEqualDDMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of doing nothing but accessing the Element
      Element doNothingResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        Element elt = elts[ii];
        double left = elt.Left;
        double right = elt.Right;
      }
      stopwatch.Stop();
      long doNothingMS = stopwatch.ElapsedMilliseconds;

      // Report results
      Assert.AreEqual(1d, operatorCtorResult.Left, "The operator += did not compute the right result!");
      Assert.AreEqual(1d, operatorNoCtorResult.Left, "The operator += did not compute the right result!");
      Assert.AreEqual(1d, plusEqualResult.Left, "The operator += did not compute the right result!");
      Assert.AreEqual(1d, plusEqualDDResult.Left, "The operator += did not compute the right result!");
      Assert.AreEqual(1d, doNothingResult.Left, "The operator += did not compute the right result!");

      // Report speeds
      Console.WriteLine("Populating List<Element> took {0}ms.", populateMS);
      Console.WriteLine("The PlusEqual() method took {0}ms.", plusEqualMS);
      Console.WriteLine("The 'same' += operator took {0}ms.", operatorCtorMS);
      Console.WriteLine("The 'same' -= operator took {0}ms.", operatorNoCtorMS);
      Console.WriteLine("The PlusEqual(double, double) method took {0}ms.", plusEqualDDMS);
      Console.WriteLine("The do nothing loop took {0}ms.", doNothingMS);

      // Compare speeds
      long percentageRatio = 100L * operatorCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator with constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * operatorNoCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator without constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * plusEqualDDMS / plusEqualMS;
      Console.WriteLine("The ratio of PlusEqual(double,double) to PlusEqual(Element) is {0}%.", percentageRatio);

      operatorCtorMS -= doNothingMS;
      operatorNoCtorMS -= doNothingMS;
      plusEqualMS -= doNothingMS;
      plusEqualDDMS -= doNothingMS;
      Console.WriteLine("If we remove the overhead time for the loop accessing the elements from the List...");
      percentageRatio = 100L * operatorCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator with constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * operatorNoCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator without constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * plusEqualDDMS / plusEqualMS;
      Console.WriteLine("The ratio of PlusEqual(double,double) to PlusEqual(Element) is {0}%.", percentageRatio);
    }
  }
}

आईएल: (उर्फ। उपरोक्त में से कुछ क्या संकलित हैं)

public void PlusEqual(Element that)
    {
00000000 push    ebp 
00000001 mov     ebp,esp 
00000003 push    edi 
00000004 push    esi 
00000005 push    ebx 
00000006 sub     esp,30h 
00000009 xor     eax,eax 
0000000b mov     dword ptr [ebp-10h],eax 
0000000e xor     eax,eax 
00000010 mov     dword ptr [ebp-1Ch],eax 
00000013 mov     dword ptr [ebp-3Ch],ecx 
00000016 cmp     dword ptr ds:[04C87B7Ch],0 
0000001d je     00000024 
0000001f call    753081B1 
00000024 nop       
      this.Left += that.Left;
00000025 mov     eax,dword ptr [ebp-3Ch] 
00000028 fld     qword ptr [ebp+8] 
0000002b fadd    qword ptr [eax] 
0000002d fstp    qword ptr [eax] 
      this.Right += that.Right;
0000002f mov     eax,dword ptr [ebp-3Ch] 
00000032 fld     qword ptr [ebp+10h] 
00000035 fadd    qword ptr [eax+8] 
00000038 fstp    qword ptr [eax+8] 
    }
0000003b nop       
0000003c lea     esp,[ebp-0Ch] 
0000003f pop     ebx 
00000040 pop     esi 
00000041 pop     edi 
00000042 pop     ebp 
00000043 ret     10h 
 public void PlusEqual(double thatLeft, double thatRight)
    {
00000000 push    ebp 
00000001 mov     ebp,esp 
00000003 push    edi 
00000004 push    esi 
00000005 push    ebx 
00000006 sub     esp,30h 
00000009 xor     eax,eax 
0000000b mov     dword ptr [ebp-10h],eax 
0000000e xor     eax,eax 
00000010 mov     dword ptr [ebp-1Ch],eax 
00000013 mov     dword ptr [ebp-3Ch],ecx 
00000016 cmp     dword ptr ds:[04C87B7Ch],0 
0000001d je     00000024 
0000001f call    75308159 
00000024 nop       
      this.Left += thatLeft;
00000025 mov     eax,dword ptr [ebp-3Ch] 
00000028 fld     qword ptr [ebp+10h] 
0000002b fadd    qword ptr [eax] 
0000002d fstp    qword ptr [eax] 
      this.Right += thatRight;
0000002f mov     eax,dword ptr [ebp-3Ch] 
00000032 fld     qword ptr [ebp+8] 
00000035 fadd    qword ptr [eax+8] 
00000038 fstp    qword ptr [eax+8] 
    }
0000003b nop       
0000003c lea     esp,[ebp-0Ch] 
0000003f pop     ebx 
00000040 pop     esi 
00000041 pop     edi 
00000042 pop     ebp 
00000043 ret     10h 

22
वाह, यह एक उदाहरण के रूप में संदर्भित किया जाना चाहिए कि स्टैकओवरफ़्लो पर एक अच्छा सवाल कैसा दिख सकता है! केवल ऑटो-जनरेटेड टिप्पणियों को छोड़ा जा सकता है। दुर्भाग्य से मैं वास्तव में समस्या में गोता लगाने के लिए बहुत कम जानता हूं, लेकिन मुझे वास्तव में सवाल पसंद है!
डेनिस ट्रब

2
मुझे नहीं लगता कि एक यूनिट टेस्ट एक बेंचमार्क चलाने के लिए एक अच्छी जगह है।
हेंक होल्टरमैन

1
क्यों संरचना को तेज होना है तो दो डबल्स? .NET संरचना में कभी भी इसके सदस्यों के आकार के योग के बराबर नहीं है। तो परिभाषा के अनुसार, यह बड़ा है, इसलिए परिभाषा के अनुसार इसे स्टैक पर पुश करने पर धीमा होना पड़ता है, फिर सिर्फ 2 डबल मान। यदि कंपाइलर पंक्ति 2 डबल मेमोरी में स्ट्रक्चर पैरामीटर को इनलाइन करेगा, तो क्या होगा अगर अंदर की विधि आप उस स्ट्रक्चर को रिफ्लेक्शन के साथ एक्सेस करना चाहते हैं। उस संरचना वस्तु से जुड़ी रनटाइम जानकारी कहां होगी? यह नहीं है, या मैं कुछ याद कर रहा हूँ?
बाघिन

3
@ टाइगरान: आपको उन दावों के लिए स्रोतों की आवश्यकता है। मुझे लगता है आप ग़लत हैं। केवल जब एक मूल्य प्रकार बॉक्सिंग हो जाता है, तो मेटाडेटा को मूल्य के साथ संग्रहीत करने की आवश्यकता होती है। स्थिर संरचना प्रकार के साथ एक चर में, कोई उपरि नहीं है।
बेन Voigt

1
मैं सोच रहा था कि केवल एक चीज गायब थी विधानसभा। और अब आपने इसे जोड़ लिया है (कृपया ध्यान दें, यह x86 कोडांतरक है और MSIL नहीं है)।
बेन वायगेट

जवाबों:


9

मुझे बहुत अलग परिणाम मिल रहे हैं, बहुत कम नाटकीय। लेकिन परीक्षण धावक का उपयोग नहीं किया, मैंने कोड को एक कंसोल मोड ऐप में चिपकाया। 5% परिणाम 32-बिट मोड में ~ 87%, 64-बिट मोड में ~ 100% जब मैं कोशिश करता हूं।

संरेखण युगल पर महत्वपूर्ण है, .NET रनटाइम केवल 32-बिट मशीन पर 4 के संरेखण का वादा कर सकता है। मुझे लगता है कि परीक्षण धावक स्टैक एड्रेस के साथ परीक्षण विधियों को शुरू कर रहा है जो 8. के ​​बजाय 4 से संरेखित है। मिसलिग्न्मेंट पेनल्टी बहुत बड़ी हो जाती है जब डबल कैश लाइन सीमा पार करता है।


क्यों .NET मूल रूप से केवल 4 युगल के संरेखण पर सफलता प्राप्त कर सकता है? 32 बिट मशीन पर 4 बाइट विखंडू का उपयोग करके संरेखण किया जाता है। वहाँ क्या समस्या है?
Tigran

रनटाइम x86 पर केवल 4 बाइट्स से संरेखित क्यों होता है? मुझे लगता है कि अगर यह प्रबंधित कोड कॉल प्रबंधित कोड पर अतिरिक्त देखभाल करता है तो यह 64 बिट में संरेखित कर सकता है । जबकि कल्पना में केवल कमजोर संरेखण की गारंटी है, कार्यान्वयन अधिक सख्ती से संरेखित करने में सक्षम होना चाहिए। (विवरण: "8-बाइट डेटा को ठीक से संरेखित किया जाता है, जब इसे एक मूल
आंतरिक पर

1
@ कोड - ठीक है, यह कर सकता है, सी कोड जनरेटर फ़ंक्शन प्रस्तावना में स्टैक पॉइंटर पर गणित करके ऐसा करते हैं। X86 घबराना बस नहीं करता है। यह देशी भाषाओं के लिए बहुत अधिक महत्वपूर्ण है क्योंकि स्टैक पर सरणियाँ आवंटित करना बहुत अधिक सामान्य है और उनके पास एक ढेर आवंटनकर्ता है जो 8 से संरेखित करता है इसलिए ढेर आवंटन की तुलना में स्टैक आवंटन को कम कुशल नहीं बनाना चाहेंगे। हम 32-बिट gc ढेर से 4 के संरेखण के साथ फंस गए हैं।
हंस पैसेंट

5

मुझे आपके परिणामों की नकल करने में थोड़ी कठिनाई हो रही है।

मैंने आपका कोड लिया:

  • इसे स्टैंडअलोन कंसोल एप्लिकेशन बनाया
  • एक अनुकूलित (रिलीज़) बिल्ड बनाया
  • "आकार" कारक को 2.5M से बढ़ाकर 10M कर दिया
  • इसे कमांड लाइन (आईडीई के बाहर) से चलाया गया

जब मैंने ऐसा किया, तो मुझे निम्नलिखित समय मिल गए जो आपसे बहुत अलग हैं। संदेह से बचने के लिए, मैं ठीक उसी कोड को पोस्ट करूँगा जिसका मैंने उपयोग किया था।

यहाँ मेरी टाइमिंग हैं

Populating List<Element> took 527ms.
The PlusEqual() method took 450ms.
The 'same' += operator took 386ms.
The 'same' -= operator took 446ms.
The PlusEqual(double, double) method took 413ms.
The do nothing loop took 229ms.
The ratio of operator with constructor to method is 85%.
The ratio of operator without constructor to method is 99%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 91%.
If we remove the overhead time for the loop accessing the elements from the List...
The ratio of operator with constructor to method is 71%.
The ratio of operator without constructor to method is 98%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 83%.

और ये आपके कोड के लिए मेरे संपादन हैं:

namespace OperatorVsMethod
{
  public struct Element
  {
    public double Left;
    public double Right;

    public Element(double left, double right)
    {
      this.Left = left;
      this.Right = right;
    }    

    public static Element operator +(Element x, Element y)
    {
      return new Element(x.Left + y.Left, x.Right + y.Right);
    }

    public static Element operator -(Element x, Element y)
    {
      x.Left += y.Left;
      x.Right += y.Right;
      return x;
    }    

    /// <summary>
    /// Like the += operator; but faster.
    /// </summary>
    public void PlusEqual(Element that)
    {
      this.Left += that.Left;
      this.Right += that.Right;
    }    

    /// <summary>
    /// Like the += operator; but faster.
    /// </summary>
    public void PlusEqual(double thatLeft, double thatRight)
    {
      this.Left += thatLeft;
      this.Right += thatRight;
    }    
  }    

  public class UnitTest1
  {
    public static void Main()
    {
      Stopwatch stopwatch = new Stopwatch();

      // Populate a List of Elements to multiply together
      int seedSize = 4;
      List<double> doubles = new List<double>(seedSize);
      doubles.Add(2.5d);
      doubles.Add(100000d);
      doubles.Add(-0.5d);
      doubles.Add(-100002d);

      int size = 10000000 * seedSize;
      List<Element> elts = new List<Element>(size);

      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        int di = ii % seedSize;
        double d = doubles[di];
        elts.Add(new Element(d, d));
      }
      stopwatch.Stop();
      long populateMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of += operator (calls ctor)
      Element operatorCtorResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        operatorCtorResult += elts[ii];
      }
      stopwatch.Stop();
      long operatorCtorMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of -= operator (+= without ctor)
      Element operatorNoCtorResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        operatorNoCtorResult -= elts[ii];
      }
      stopwatch.Stop();
      long operatorNoCtorMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of PlusEqual(Element) method
      Element plusEqualResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        plusEqualResult.PlusEqual(elts[ii]);
      }
      stopwatch.Stop();
      long plusEqualMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of PlusEqual(double, double) method
      Element plusEqualDDResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        Element elt = elts[ii];
        plusEqualDDResult.PlusEqual(elt.Left, elt.Right);
      }
      stopwatch.Stop();
      long plusEqualDDMS = stopwatch.ElapsedMilliseconds;

      // Measure speed of doing nothing but accessing the Element
      Element doNothingResult = new Element(1d, 1d);
      stopwatch.Reset();
      stopwatch.Start();
      for (int ii = 0; ii < size; ++ii)
      {
        Element elt = elts[ii];
        double left = elt.Left;
        double right = elt.Right;
      }
      stopwatch.Stop();
      long doNothingMS = stopwatch.ElapsedMilliseconds;

      // Report speeds
      Console.WriteLine("Populating List<Element> took {0}ms.", populateMS);
      Console.WriteLine("The PlusEqual() method took {0}ms.", plusEqualMS);
      Console.WriteLine("The 'same' += operator took {0}ms.", operatorCtorMS);
      Console.WriteLine("The 'same' -= operator took {0}ms.", operatorNoCtorMS);
      Console.WriteLine("The PlusEqual(double, double) method took {0}ms.", plusEqualDDMS);
      Console.WriteLine("The do nothing loop took {0}ms.", doNothingMS);

      // Compare speeds
      long percentageRatio = 100L * operatorCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator with constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * operatorNoCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator without constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * plusEqualDDMS / plusEqualMS;
      Console.WriteLine("The ratio of PlusEqual(double,double) to PlusEqual(Element) is {0}%.", percentageRatio);

      operatorCtorMS -= doNothingMS;
      operatorNoCtorMS -= doNothingMS;
      plusEqualMS -= doNothingMS;
      plusEqualDDMS -= doNothingMS;
      Console.WriteLine("If we remove the overhead time for the loop accessing the elements from the List...");
      percentageRatio = 100L * operatorCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator with constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * operatorNoCtorMS / plusEqualMS;
      Console.WriteLine("The ratio of operator without constructor to method is {0}%.", percentageRatio);
      percentageRatio = 100L * plusEqualDDMS / plusEqualMS;
      Console.WriteLine("The ratio of PlusEqual(double,double) to PlusEqual(Element) is {0}%.", percentageRatio);
    }
  }
}

मैंने बस वही किया, मेरे परिणाम आपके जैसे ही हैं। कृपया राज्य मंच और सीपीयू प्रकार।
हेनक होल्टरमैन

बहुत ही रोचक! मैंने दूसरों को मेरे परिणामों को सत्यापित किया है ... आप सबसे अलग हैं। आपके लिए पहला प्रश्न: मेरी पोस्ट में मेरे द्वारा बताई गई फ़ाइल का संस्करण क्रमांक क्या है ... C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll ... जो Microsoft दस्तावेज़ों ने संकेत किया है, वह है आपके पास JIT अनुकूलक का संस्करण। (अगर मैं अपने उपयोगकर्ताओं को अपने .NET को अपग्रेड करने के लिए बड़े स्पीडअप को देखने के लिए कह सकता हूं, तो मैं एक खुश कैम्पर बनूंगा। लेकिन मैं अनुमान लगा रहा हूं कि यह उतना आसान नहीं है।)
ब्रायन कैनेडी

मैं विजुअल स्टूडियो के अंदर चल रहा था ... विंडोज एक्सपी एसपी 3 पर चल रहा था ... एक VMware वर्चुअल मशीन में ... 2.7GHz इंटेल कोर i7 पर। लेकिन इसकी पूर्ण बार मुझे दिलचस्पी नहीं है ... यह अनुपात है ... मैं उन तीन तरीकों की उम्मीद करूँगा कि सभी समान प्रदर्शन करें, जो उन्होंने कोरी के लिए किया था, लेकिन मेरे लिए नहीं।
ब्रायन कैनेडी

मेरे प्रोजेक्ट गुण कहते हैं: कॉन्फ़िगरेशन: रिलीज़; प्लेटफ़ॉर्म: सक्रिय (x86); प्लेटफ़ॉर्म लक्ष्य: x86
कोरी कोसाक 30'11

1
Mscorwks के संस्करण को प्राप्त करने के आपके अनुरोध के बारे में ... क्षमा करें, क्या आप चाहते हैं कि मैं .NET 2.0 के खिलाफ इस चीज को चलाऊं? मेरी परीक्षाएँ .NET 4.0
कोरी कोसक

3

यहां .NET 4.0 चल रहा है। मैंने "किसी भी सीपीयू" के साथ संकलन किया, रिलीज़ मोड में .NET 4.0 को लक्षित किया। निष्पादन कमांड लाइन से था। यह 64-बिट मोड में चलता था। मेरी टाइमिंग थोड़ी अलग है।

Populating List<Element> took 442ms.
The PlusEqual() method took 115ms.
The 'same' += operator took 201ms.
The 'same' -= operator took 200ms.
The PlusEqual(double, double) method took 129ms.
The do nothing loop took 93ms.
The ratio of operator with constructor to method is 174%.
The ratio of operator without constructor to method is 173%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 112%.
If we remove the overhead time for the loop accessing the elements from the List
...
The ratio of operator with constructor to method is 490%.
The ratio of operator without constructor to method is 486%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 163%.

विशेष रूप से, PlusEqual(Element)की तुलना में थोड़ा तेज है PlusEqual(double, double)

जो भी समस्या .NET 3.5 में है, वह .NET 4.0 में मौजूद नहीं है।


2
हां, स्ट्रक्चर्स पर उत्तर "नया जेआईटी प्राप्त करें" प्रतीत होता है। लेकिन जैसा कि मैंने हेंक के जवाब पर पूछा है, ऑपरेटर की तुलना में विधियां इतनी तेज़ क्यों हैं? आपके दोनों तरीके आपके दोनों संचालकों की तुलना में 5x तेज हैं ... जो बिल्कुल वही काम कर रहे हैं। यह बहुत अच्छा है कि मैं फिर से संरचनाओं का उपयोग कर सकता हूं ... लेकिन दुख की बात है कि मुझे अभी भी ऑपरेटरों से बचना है।
ब्रायन कैनेडी

जिम, मुझे आपके सिस्टम पर फ़ाइल C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll के संस्करण को जानने में बहुत दिलचस्पी होगी ... अगर मेरा (.3620) की तुलना में नया है, लेकिन पुराना है। कोरी (.5446) की तुलना में, फिर यह समझा सकता है कि आपके ऑपरेटर अभी भी मेरी तरह धीमा क्यों हैं, लेकिन कोरी नहीं हैं।
ब्रायन कैनेडी

@ ब्रायन: फ़ाइल संस्करण 2.0.50727.4214।
जिम मेंथल सेप

धन्यवाद! इसलिए, मुझे यह सुनिश्चित करने की आवश्यकता है कि मेरे उपयोगकर्ताओं के पास 4214 या बाद में संरचना अनुकूलन और 5446 या बाद में ऑपरेटर अनुकूलन प्राप्त करने के लिए है। मुझे स्टार्टअप पर जाँच करने के लिए और कुछ चेतावनी देने के लिए कुछ कोड जोड़ने की आवश्यकता है। एक बार फिर धन्यवाद।
ब्रायन कैनेडी

2

@ कोरी कोसक की तरह, मैंने अभी वीएस 2010 एक्सप्रेस में इस कोड को रिलीज़ मोड में एक साधारण कंसोल ऐप के रूप में चलाया। मुझे बहुत अलग नंबर मिलते हैं। लेकिन मेरे पास Fx4.5 भी है, इसलिए ये एक साफ Fx4.0 के परिणाम नहीं हो सकते हैं।

Populating List<Element> took 435ms.
The PlusEqual() method took 109ms.
The 'same' += operator took 217ms.
The 'same' -= operator took 157ms.
The PlusEqual(double, double) method took 118ms.
The do nothing loop took 79ms.
The ratio of operator with constructor to method is 199%.
The ratio of operator without constructor to method is 144%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 108%.
If we remove the overhead time for the loop accessing the elements from the List
...
The ratio of operator with constructor to method is 460%.
The ratio of operator without constructor to method is 260%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 130%.

संपादित करें: और अब cmd लाइन से चलते हैं। इससे संख्याओं में अंतर और कम भिन्नता होती है।


हां, ऐसा प्रतीत होता है कि बाद में जेआईटी ने संरचना का मुद्दा तय कर दिया है, लेकिन मेरा सवाल यह है कि ऑपरेटर क्यों बने हुए हैं, इसलिए विधियां इतनी तेज हैं। देखो कितनी तेजी से दोनों plusEqual तरीकों समकक्ष + = ऑपरेटर की तुलना में कर रहे हैं। और यह भी दिलचस्प है कि कितनी तेजी से - = की तुलना में + = है ... आपकी टाइमिंग पहली है जहां मैंने देखा है।
ब्रायन कैनेडी

हेंक, मुझे आपके सिस्टम पर फ़ाइल का संस्करण C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll जानने की बहुत दिलचस्पी होगी ... यदि मेरा (.3620) की तुलना में नया है, लेकिन पुराना है। कोरी (.5446) की तुलना में, फिर यह समझा सकता है कि आपके ऑपरेटर अभी भी मेरी तरह धीमा क्यों हैं, लेकिन कोरी नहीं हैं।
ब्रायन कैनेडी

1
मैं केवल .50727 संस्करण पा सकता हूं लेकिन मुझे यकीन नहीं है कि क्या यह एफएक्स 40 / एफएक्स 45 के लिए प्रासंगिक है?
हेनक होल्टरमैन

आपको गुणों में जाना होगा और बाकी संस्करण संख्या को देखने के लिए संस्करण टैब पर क्लिक करना होगा।
ब्रायन कैनेडी

2

अन्य उत्तरों में उल्लिखित जेआईटी संकलक अंतर के अलावा, एक संरचनात्मक विधि कॉल और एक संरचनात्मक ऑपरेटर के बीच एक और अंतर यह है कि एक संरचनात्मक विधि कॉल thisएक refपैरामीटर के रूप में पास होगी (और अन्य मापदंडों को भी refपैरामीटर के रूप में स्वीकार करने के लिए लिखा जा सकता है ), जबकि ए स्ट्रक्चर ऑपरेटर मूल्य द्वारा सभी ऑपरेंड पारित करेगा। एक refपैरामीटर के रूप में किसी भी आकार की संरचना को पारित करने की लागत तय की जाती है, चाहे कितनी बड़ी संरचना हो, जबकि बड़ी संरचनाओं को पारित करने की लागत संरचना आकार के अनुपात में होती है। बड़ी संरचनाओं (यहां तक ​​कि सैकड़ों बाइट्स) का उपयोग करने में कुछ भी गलत नहीं है अगर कोई उन्हें अनावश्यक रूप से कॉपी करने से बच सकता है ; जबकि अनावश्यक प्रतियों को अक्सर तरीकों का उपयोग करते हुए रोका जा सकता है, लेकिन ऑपरेटरों का उपयोग करते समय उन्हें रोका नहीं जा सकता है।


हममम ... अच्छा, जो बहुत कुछ समझा सकता है! इसलिए, यदि ऑपरेटर इतना छोटा है कि यह इनलेट हो जाएगा, तो मुझे लगता है कि यह अनावश्यक प्रतियां नहीं बनाएगा। लेकिन यदि नहीं, और आपकी संरचना एक शब्द से अधिक है, तो आप इसे एक ऑपरेटर के रूप में लागू नहीं करना चाह सकते हैं यदि गति महत्वपूर्ण है। उस अंतर्दृष्टि के लिए धन्यवाद।
ब्रायन कैनेडी

BTW, एक चीज जो मुझे थोड़ा परेशान करती है जब गति के बारे में प्रश्नों का उत्तर दिया जाता है "बेंचमार्क!" क्या इस तरह की प्रतिक्रिया इस तथ्य को नजरअंदाज करती है कि कई मामलों में क्या मायने रखता है कि क्या एक ऑपरेशन में आमतौर पर 10us या 20us लगते हैं, लेकिन क्या परिस्थितियों के एक मामूली बदलाव के कारण इसे 1ms या 10ms तक ले जाया जा सकता है। क्या मायने नहीं रखता है कि किसी डेवलपर की मशीन पर कितनी तेजी से कुछ चलता है, बल्कि यह कि क्या ऑपरेशन कभी भी धीमी गति से होता है ; यदि विधि X अधिकांश मशीनों पर विधि Y से दुगुनी तेजी से चलती है, लेकिन कुछ मशीनों पर यह धीमी गति से 100 गुना होगी, तो विधि Y बेहतर विकल्प हो सकता है।
सुपरकैट

बेशक, यहाँ हम सिर्फ 2 डबल्स की बात कर रहे हैं ... बड़े स्ट्रक्चर्स की नहीं। स्टैक पर दो डबल्स को पास करना जहां उन्हें जल्दी से एक्सेस किया जा सकता है स्टैक पर 'इस' को पास करने की तुलना में यह धीमा नहीं है और फिर इस पर उन्हें संचालित करने के लिए उन्हें खींचने के लिए डीरेफेरेंस करना पड़ता है .. लेकिन यह मतभेद पैदा कर सकता है। हालांकि, इस मामले में, यह इनबिल्ट होना चाहिए, इसलिए जेआईटी ऑप्टिमाइज़र को समान कोड के साथ समाप्त होना चाहिए।
ब्रायन कैनेडी

1

यकीन नहीं है कि यह प्रासंगिक है, लेकिन यहां विंडोज 7 64-बिट पर .NET 4.0 64-बिट के लिए संख्याएं हैं। मेरा mscorwks.dll संस्करण 2.0.50727.5446 है। मैंने अभी कोड को LINQPad में चिपकाया और उसे वहां से चलाया। यहाँ परिणाम है:

Populating List<Element> took 496ms.
The PlusEqual() method took 189ms.
The 'same' += operator took 295ms.
The 'same' -= operator took 358ms.
The PlusEqual(double, double) method took 148ms.
The do nothing loop took 103ms.
The ratio of operator with constructor to method is 156%.
The ratio of operator without constructor to method is 189%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 78%.
If we remove the overhead time for the loop accessing the elements from the List
...
The ratio of operator with constructor to method is 223%.
The ratio of operator without constructor to method is 296%.
The ratio of PlusEqual(double,double) to PlusEqual(Element) is 52%.

2
दिलचस्प ... यह प्रतीत होता है कि 32b JIT ऑप्टिमाइज़र में जो ऑप्टिमाइज़ेशन जोड़े गए थे, वह अभी तक 64b JIT ऑप्टिमाइज़र के लिए नहीं बने हैं ... आपके अनुपात अभी भी मेरे जैसे ही हैं। निराश ... लेकिन जानकर अच्छा लगा।
ब्रायन केनेडी

0

मैं कल्पना करता हूं कि जब आप संरचना के सदस्यों तक पहुंच बना रहे हैं, तो यह उस सदस्य को एक्सेस करने के लिए एक अतिरिक्त ऑपरेशन कर रहा है, जो कि THIS सूचक + ऑफसेट है।


1
ठीक है, एक क्लास ऑब्जेक्ट के साथ, आप बिल्कुल सही होंगे ... क्योंकि विधि सिर्फ 'इस' पॉइंटर को पास करेगी। हालाँकि, संरचना के साथ, ऐसा नहीं होना चाहिए। स्टैक पर विधियों में संरचना को पारित किया जाना चाहिए। तो, पहला डबल बैठा होना चाहिए जहां 'यह' पॉइंटर होगा और उसके ठीक बाद स्थिति में दूसरा डबल होगा ... दोनों संभवतः सीपीयू में रजिस्टर हो रहे हैं। इसलिए, जेआईटी को केवल एक ऑफसेट का उपयोग करना चाहिए।
ब्रायन कैनेडी

0

हो सकता है कि सूची के बजाय आपको "सुप्रसिद्ध" ऑफ़सेट्स और इंडेक्स इंक्रीमेंट के साथ दोहरे [] का उपयोग करना चाहिए?

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.