SORU
30 EYLÜL 2011, Cuma


Neden operatörler yöntem çağrıları daha çok yavaş? (yapılar yavaş sadece eski JİTs)

İntro:Ben C yüksek performanslı kod yazmak#. Evet, C bana daha iyi optimizasyon vereceğini biliyorum, ama ben hala kullanmayı tercih C#. Bu seçimi tartışmak istemiyorum. Daha doğrusu, benim gibi, yüksek performanslı kod yazmak için çalışıyoruz onlardan duymak istiyorum .NET Çerçeve.

Soru:

  • Neden kodu eşdeğer daha aşağıda yavaş operatör. yöntem diyorsun??
  • Neden bu yöntem kod içinde iki katına aşağıda geçiyor eşdeğer daha hızlı İkinci yöntem olan bir yapı geçerek çiftler içinde? (A: eski JİTs yapılar kötü optimize)
  • Bir şekilde elde etmektir .NET Derleyici tedavi etmek için TAM zamanında basit yapılar olarak etkin yapı üyeleri? (: Daha yeni JİT)

Ne ben biliyorum Orijinal .NET JİT Derleyici bir yapı dahil her şeyi satır içi olmaz. Tuhaf verilen yapılar sadece built-ins gibi optimize edilmiş olması gereken küçük bir değer türleri gereken yere, ama doğru kullanılmalıdır. Neyse ki, içinde .NET 3.5SP1 ve .NET 2.0SP2, özellikle yapılar için TAM zamanında İyileştirici, satır içi uygulaması için geliştirmeler de dahil olmak üzere bazı iyileştirmeler yaptılar. (Tanıtımı olduklarını aksi halde, yeni bir Kompleks yapı korkunç bir performans verecek çünkü bu yaptıklarını tahmin ediyorum... Karmaşık takım muhtemelen JİT Doktoru takımda deli gibi çarpıyordu.) Yani, herhangi bir belge önce .NET 3.5 SP1 muhtemelen bu konuyla çok alakalı değil.

Benim test gösterir C:\windows\microsoft.net\framework\v2.0.50727\mscorwks.dll dosya sürümü ^ var olduğunu kontrol ederek yeni JİT İyileştiricisi var olduğunu teyit ettim . = 3053 ve JİT doktoru Bu iyileştirmeler olmalıdır. Çözümü de zamanlamaları ve görünüşüm de ne ancak, bu durumda bile

İki çift ile bir yapı geçirilmesi için TAM zamanında üretilen kod doğrudan iki katına geçen kod çok daha az etkilidir.

Yapı bir yöntem için TAM zamanında üretilen kod 'bu' çok daha verimli bir tartışma gibi bir yapı geçtin. daha geçen

Bu JİT hala eğer çarpan açıkça bir döngü içinde olması nedeniyle bile iki çift ile bir yapı, geçen yerine iki çift geçersen daha iyi inlines.

Zamanlama: Aslında, demontaj bakarak döngüler çoğu zaman sadece Liste dışı test veri erişim olduğunu biliyoruz. Aynı kararları dört yol arasındaki farkı döngü havai kodu faktörü ve veri erişim eğer önemli ölçüde farklıdır. Her yerde PlusEqual(çift), çift yerine PlusEqual(Element) için 20x speedups 5 kat. Ve 10x operatör yerine PlusEqual yapmak için 40(çift, çift)=. Vay be. Üzücü.

İşte zamanlamaları birini ayarlayın:

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%.

Kod:

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

IL:(aka. yukarıda bazı derlenmiş alır)

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 

CEVAP
30 EYLÜL 2011, Cuma


Çok farklı sonuçlar, çok daha az dramatik alıyorum. Ama testi kullanmadın runner, konsol modu bir uygulama içine kodu yapıştırdım. 5% sonuç denediğimde 64-bit modunda 32-bit modunda, ~100% ~87%.

Hizalama iki katına çıkar, önemlidir .NET çalışma zamanı yalnızca 32-bit bir makinede 4 bir uyum söz verebilir. Bana kalırsa, test runner yığın adresi ile test yöntemlerini başlayan 8 yerine 4 hizalanmış. Hiza ceza çift önbellek satır sınırı geçtiği zaman çok büyük olur.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • DRDAnimation

    DRDAnimation

    28 EYLÜL 2012
  • SuicideSheeep

    SuicideSheee

    8 Ocak 2012
  • Truc Minh

    Truc Minh

    23 Ocak 2011