Operatörler neden yöntem çağrılarından çok daha yavaş? (yapılar yalnızca eski JIT'lerde daha yavaştır)


84

Giriş: C # ile yüksek performanslı kod yazıyorum. Evet, C ++ 'nın bana daha iyi optimizasyon sağlayacağını biliyorum, ancak yine de C # kullanmayı seçiyorum. Bu seçimi tartışmak istemiyorum. Daha ziyade, benim gibi .NET Framework üzerinde yüksek performanslı kod yazmaya çalışanlardan haber almak isterim.

Sorular:

  • Aşağıdaki koddaki operatör neden eşdeğer yöntem çağrısından daha yavaş?
  • Yöntem, aşağıdaki kodda iki double'ı, içinde iki double bulunan bir yapıdan geçen eşdeğer yönteme göre neden daha hızlı geçiriyor? (A: eski JIT'ler yapıları kötü bir şekilde optimize eder)
  • .NET JIT Derleyicisinin basit yapıları yapının üyeleri kadar verimli bir şekilde ele almasını sağlamanın bir yolu var mı? (A: daha yeni JIT edinin)

Bildiğimi düşündüğüm şey: Orijinal .NET JIT Derleyicisi, bir yapı içeren hiçbir şeyi satır içi yapmaz. Tuhaf verilen yapılar, yalnızca yerleşikler gibi optimize edilmesi gereken, ancak doğru olan küçük değer türlerine ihtiyaç duyduğunuzda kullanılmalıdır. Neyse ki, .NET 3.5SP1 ve .NET 2.0SP2'de, JIT Optimizer'da özellikle yapılar için satır içi iyileştirmeler dahil olmak üzere bazı iyileştirmeler yaptılar. (Bunu yaptıklarını tahmin ediyorum çünkü aksi takdirde tanıttıkları yeni Complex yapı korkunç bir performans sergileyecekti ... bu yüzden Complex ekibi muhtemelen JIT Optimizer ekibini zorluyordu.) Dolayısıyla, .NET 3.5 SP1'den önceki herhangi bir dokümantasyon muhtemelen bu konuyla çok ilgili değil.

Testlerim ne gösteriyor: C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll dosyasının sürüm> = 3053 olup olmadığını kontrol ederek daha yeni JIT Optimizer'a sahip olduğumu doğruladım ve bu iyileştirmeleri de almalıyım JIT optimize ediciye. Bununla birlikte, bununla bile, zamanlamalarımın ve sökmeye baktığım her ikisinin de gösterdiği şey:

İki çiftli bir yapıyı geçmek için JIT tarafından üretilen kod, iki ikiliyi doğrudan geçen koddan çok daha az etkilidir.

Bir struct yöntemi için JIT tarafından üretilen kod, bir yapıyı bağımsız değişken olarak ilettiğinizden çok daha verimli bir şekilde 'bu' içinde geçer.

Açıkça bir döngüde olduğu için çarpanla bile, iki çiftli bir yapıyı geçmek yerine iki katlamayı geçerseniz, JIT hala daha iyi satır içi.

Zamanlamalar: Aslında, demonte etmeye baktığımda, döngülerin çoğunun yalnızca Listeden test verilerine eriştiğini fark ettim. Döngünün ek yük kodunu ve verilere erişimi dışarıda bırakırsanız, aynı çağrıları yapmanın dört yolu arasındaki fark önemli ölçüde farklıdır. PlusEqual (Element) yerine PlusEqual (çift, çift) yapmak için 5x'ten 20x'e kadar herhangi bir hızlanma elde ediyorum. Ve + = operatörü yerine PlusEqual (çift, çift) yapmak için 10x - 40x. Vay. Üzgün.

İşte bir dizi zamanlama:

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ıdakilerden bazıları derlenir)

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
Vay canına, bu Stackoverflow'da iyi bir sorunun nasıl görünebileceğine dair bir örnek olarak gösterilmelidir! Yalnızca otomatik olarak oluşturulan yorumlar atlanabilir. Maalesef soruna gerçekten dalmak için çok az şey biliyorum, ama soruyu gerçekten beğendim!
Dennis Traub

2
Birim Testinin bir kıyaslama yapmak için iyi bir yer olduğunu düşünmüyorum.
Henk Holterman

1
Neden yapı iki çiftten daha hızlı olmak zorunda? .NET yapısında, üyelerinin boyutlarının toplamına ASLA eşit değildir. Yani tanım gereği daha büyük, yani tanım gereği yığını itmek için daha yavaş olmalı, sonra sadece 2 çift değer. Eğer derleyici 2. satırdaki struct parametresini çift bellekle satır içine alacaksa, ya yöntem içinde yansıma ile bu yapıya erişmek istersiniz. Bu struct nesnesine bağlı çalışma zamanı bilgileri nerede olacak? Değil mi, yoksa bir şey eksik mi?
Tigran

3
@Tigran: Bu iddialar için kaynaklara ihtiyacınız var. Bence hatalısın. Yalnızca bir değer türü kutuya alındığında, meta verilerin değerle depolanması gerekir. Statik yapı tipine sahip bir değişkende ek yük yoktur.
Ben Voigt

1
Eksik olan tek şeyin meclis olduğunu düşünüyordum. Ve şimdi bunu eklediniz (lütfen unutmayın, bu x86 assembler'dır ve MSIL DEĞİL).
Ben Voigt

Yanıtlar:


9

Çok farklı sonuçlar alıyorum, çok daha az dramatik. Ancak test çalıştırıcısını kullanmadım, kodu bir konsol modu uygulamasına yapıştırdım. % 5 sonuç 32 bit modunda ~% 87, denediğimde 64 bit modunda ~% 100 oluyor.

Hizalama çiftlerde kritiktir, .NET çalışma zamanı 32 bitlik bir makinede yalnızca 4 hizalama vaat edebilir. Bana öyle geliyor ki test koşucusu test yöntemlerine 8 yerine 4'e hizalanmış bir yığın adresiyle başlıyor. İkili önbellek sınırı sınırını geçtiğinde yanlış hizalama cezası çok büyük oluyor.


Neden .NET sadece 4 çiftin hizalanmasında temelde başarılı olabilir? Hizalama 32 bit makinede 4 baytlık yığınlar kullanılarak yapılır. Orada sorun nedir?
Tigran

X86'da çalışma zamanı neden yalnızca 4 bayta hizalı? Ben düşünüyorum olabilir o yönetilmeyen kod çağrıları kod yönetilen zaman ek ilgilenir eğer 64 bit hizalayın. Spesifikasyon sadece zayıf hizalama garantilerine sahipken, uygulamalar daha sıkı bir şekilde hizalanabilmelidir. (Spec: "8 baytlık veriler, yerel bir int'e atomik erişim için temel donanımın gerektirdiği aynı sınırda depolandığında düzgün şekilde hizalanır")
CodesInChaos

1
@Code - Pekala, C kodu üreteçleri bunu işlev önsözündeki yığın işaretçisi üzerinde matematik yaparak yapabilir. X86 titremesi sadece yapmaz. Öyle çok çok daha yaygındır daha önemli yığın diziler tahsis beri yerli diller için ve 8 hizalar böylece yığın ayırma daha yığın ayırma az verimli olmak için asla bir yığın ayırıcısı var. 32 bitlik gc yığınından 4'lük bir hizalamayla sıkışıp kaldık.
Hans Passant

5

Sonuçlarınızı kopyalamakta bazı zorluklar yaşıyorum.

Kodunuzu aldım:

  • onu bağımsız bir konsol uygulaması yaptı
  • optimize edilmiş (yayınlanmış) bir yapı oluşturdu
  • "boyut" faktörünü 2,5 milyondan 10 milyona yükseltti
  • komut satırından çalıştırdı (IDE'nin dışında)

Bunu yaptığımda, sizinkinden çok farklı olan aşağıdaki zamanlamaları aldım. Şüpheye mahal vermemek için, tam olarak kullandığım kodu göndereceğim.

İşte zamanlamalarım

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

Ve bunlar kodunuzda yaptığım düzenlemeler:

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

Ben de aynısını yaptım, sonuçlarım daha çok seninki gibi. Lütfen platformu ve CPu türünü belirtin.
Henk Holterman

Çok ilginç! Başkalarının sonuçlarımı doğrulamasını sağladım ... ilk farklı olan sizsiniz. Sizin için ilk soru: Gönderimde bahsettiğim dosyanın sürüm numarası nedir ... C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll ... Microsoft belgelerinin söylediği budur sahip olduğunuz JIT Optimizer sürümü. (Kullanıcılarıma büyük hızlanmaları görmek için .NET'lerini yükseltmelerini söyleyebilirsem, mutlu bir kampçı olurum. Ama tahminim bu kadar kolay olmayacak.)
Brian Kennedy

Visual Studio'da çalıştırıyordum ... Windows XP SP3 üzerinde çalışıyor ... bir VMware sanal makinesinde ... 2.7GHz Intel Core i7 üzerinde. Ama beni ilgilendiren mutlak zamanlar değil ... oranlar ... Bu üç yöntemin hepsinin benzer şekilde performans göstermesini beklerdim, Corey için yaptılar, ama benim için yapmıyorlar.
Brian Kennedy

Proje özelliklerim şunu söylüyor: Configuration: Release; Platform: Aktif (x86); Platform hedefi: x86
Corey Kosak

1
Mscorwks sürümünü alma isteğinizle ilgili olarak ... Pardon, bu şeyi .NET 2.0'a karşı çalıştırmamı mı istediniz? Testlerim .NET 4.0 üzerindeydi
Corey Kosak

3

.NET 4.0 burada çalıştırılıyor. Yayın modunda .NET 4.0'ı hedefleyen "Herhangi bir CPU" ile derledim. Yürütme komut satırından yapıldı. 64 bit modunda çalıştı. Zamanlamalarım biraz farklı.

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

Özellikle PlusEqual(Element)biraz daha hızlıdır PlusEqual(double, double).

.NET 3.5'teki sorun ne olursa olsun, .NET 4.0'da yok gibi görünüyor.


2
Evet, Structs'taki cevap "yeni JIT'i edinin" gibi görünüyor. Ama Henk'in cevabını sorduğum gibi, yöntemler neden Operatörlerden çok daha hızlı? Her iki yönteminiz de her iki operatörünüzden 5 kat daha hızlı ... ki bunlar tamamen aynı şeyi yapıyor. Yapıları tekrar kullanabilmem harika ... ama yine de operatörlerden kaçınmam gerektiği için üzücü.
Brian Kennedy

Jim, sisteminizdeki C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll dosyasının sürümünü bilmek isterim ... benimkinden (.3620) daha yeniyse, ancak daha eskiyse Corey's'den (.5446), o zaman bu sizin operatörünüzün neden hala benimki gibi yavaş olduğunu açıklayabilir, ancak Corey's öyle değil.
Brian Kennedy

@Brian: Dosya sürümü 2.0.50727.4214.
Jim Mischel

TEŞEKKÜRLER! Bu nedenle, yapı optimizasyonlarını almak için kullanıcılarımın 4214 veya daha sonrasına ve operatör optimizasyonunu almak için 5446 veya sonrasına sahip olduğundan emin olmam gerekiyor. Bunu başlangıçta kontrol etmek ve bazı uyarılar vermek için bazı kodlar eklemem gerekiyor. Tekrar teşekkürler.
Brian Kennedy

2

@Corey Kosak gibi, bu kodu VS 2010 Express'te Yayın modunda basit bir Konsol Uygulaması olarak çalıştırdım. Çok farklı sayılar alıyorum. Ama aynı zamanda Fx4.5'im var, bu yüzden bunlar temiz bir Fx4.0 için sonuçlar olmayabilir.

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

Düzenleme: ve şimdi cmd satırından çalıştırın. Bu bir fark yaratır ve sayılarda daha az değişiklik olur.


Evet, görünen o ki, daha sonra JIT yapı sorununu çözdü, ancak yöntemlerin neden operatörlerden çok daha hızlı olduğuna dair sorum kaldı. Her iki PlusEqual yönteminin de eşdeğer + = operatöründen ne kadar hızlı olduğuna bakın. Ve aynı zamanda - = + = 'dan ne kadar hızlı olduğu da ilginç ... bunu gördüğüm ilk yerde sizin zamanlamalarınız.
Brian Kennedy

Henk, sisteminizdeki C: \ Windows \ Microsoft.NET \ Framework \ v2.0.50727 \ mscorwks.dll dosyasının sürümünü bilmek isterim ... benimkinden daha yeni (.3620) ama daha eski ise Corey's'den (.5446), o zaman bu sizin operatörünüzün neden hala benimki gibi yavaş olduğunu açıklayabilir ama Corey's öyle değil.
Brian Kennedy

1
Yalnızca .50727 sürümünü bulabilirim ancak bunun Fx40 / Fx45 için uygun olup olmadığından emin değilim?
Henk Holterman

Sürüm numarasının geri kalanını görmek için Özellikler'e gitmeli ve Sürüm sekmesine tıklamalısınız.
Brian Kennedy

2

Diğer yanıtlarda bahsedilen JIT derleyici farklılıklarına ek olarak, bir struct yöntem çağrısı ile bir struct operatörü arasındaki diğer bir fark, bir struct yöntemi çağrısının thisbir refparametre olarak geçmesidir (ve diğer parametreleri de refparametre olarak kabul etmek için yazılabilir ), struct operatörü tüm işlenenleri değere göre geçirir. Herhangi bir boyuttaki bir yapıyı refparametre olarak geçirmenin maliyeti, yapı ne kadar büyük olursa olsun sabittir, daha büyük yapıları geçme maliyeti ise yapı boyutuyla orantılıdır. Gereksiz yere kopyalamaktan kaçınılabilirse , büyük yapıları (yüzlerce bayt bile) kullanmakta yanlış bir şey yoktur ; Gereksiz kopyalar genellikle yöntemler kullanılırken önlenebilirken, operatörler kullanıldığında önlenemezler.


Hmmm ... pekala, bu çok şeyi açıklayabilir! Bu nedenle, operatör satır içi olacak kadar kısaysa, gereksiz kopyalar yapmayacağını varsayıyorum. Ancak değilse ve yapınız birden fazla kelimeyse, hız kritikse, bunu bir operatör olarak uygulamak istemeyebilirsiniz. Bu anlayış için teşekkürler.
Brian Kennedy

BTW, hızla ilgili sorulara "kıyaslama yapın!" Şeklinde yanıt verildiğinde beni biraz rahatsız eden bir şey var. Böyle bir yanıt, birçok durumda önemli olan bir işlemin genellikle 10us mu yoksa 20us mu sürdüğü, ancak koşullardaki küçük bir değişikliğin 1ms veya 10ms sürmesine neden olup olmayacağı gerçeğini göz ardı etmesidir. Önemli olan bir şeyin geliştiricinin makinesinde ne kadar hızlı çalıştığı değil, daha çok işlemin önemli olacak kadar yavaş olup olmayacağıdır ; X yöntemi, çoğu makinede Y yönteminin iki katı hızlı çalışıyorsa, ancak bazı makinelerde 100 kat daha yavaş çalışıyorsa, Y yöntemi daha iyi bir seçim olabilir.
supercat

Tabii ki, burada sadece 2 çiftten bahsediyoruz ... büyük yapılardan değil. Hızla erişilebilecekleri yığın üzerinde iki ikiye katlamak, yığında 'bunu' geçmek ve daha sonra onları işlemesi için onları çekmek için başvuruda bulunmaktan daha yavaş değildir .. ancak farklılıklara neden olabilir. Bununla birlikte, bu durumda, satır içi olmalıdır, böylece JIT Optimizer tam olarak aynı kodla sonuçlanmalıdır.
Brian Kennedy

1

Bunun alakalı olup olmadığından emin değilim, ancak Windows 7 64-bit üzerinde .NET 4.0 64-bit için rakamlar burada. Mscorwks.dll sürümüm 2.0.50727.5446. Kodu LINQPad'e yapıştırdım ve oradan çalıştırdım. İşte sonuç:

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
İlginç ... 32b JIT Optimizer'a eklenen optimizasyonların henüz 64b JIT Optimizer'a ulaşmadığı görülüyor ... oranlarınız hala benimkine çok benziyor. Hayal kırıklığı ... ama bilmek güzel.
Brian Kennedy

0

Yapının üyelerine eriştiğinizde, üyeye erişmek için fazladan bir işlem yaptığını hayal ediyorum, BU işaretçi + ofset.


1
Bir sınıf nesnesiyle, kesinlikle haklı olursunuz ... çünkü yönteme sadece 'bu' gösterici aktarılır. Ancak, yapılarda bu böyle olmamalı. Yapı, yığın üzerindeki yöntemlere aktarılmalıdır. Bu nedenle, ilk ikili 'bu' işaretçisinin olacağı yerde ve ikinci çift ise hemen arkasındaki konumda oturmalıdır ... her ikisi de muhtemelen CPU'daki kayıtlardır. Dolayısıyla, JIT en fazla bir ofset kullanıyor olmalıdır.
Brian Kennedy

0

Liste yerine "iyi bilinen" uzaklıklar ve indeks artışlarıyla double [] kullanmalısınız?

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.