SORU
20 AĞUSTOS 2015, PERŞEMBE


Garip performans basit kriter artış

Dün ekler iki nokta yapılar (double dizilerini) yöntemi için birkaç dil karşılaştırılan article by Christoph Nahr titled ".NET Struct Performance" (C , C#, Java, JavaScript buldum.

Olarak çıktı, C sürümü C# aynı makinede ~3000ms altında (ve hatta daha kötü bir performans sergiliyor 64) alırken yürütmek için 1000ms (1e9 yineleme) hakkında alır.

Test kendim aldım C# kod (basitleştirilmiş ve biraz ara tek yöntem nerede parametrelerdir geçti değer), ve koştu bir i7-3610QM makine (3.1 Ghz, tek çekirdek), 8 GB RAM, Windows 8.1, kullanarak .4.5.2, BIRAKIN NET 32-bit (benim OS x 86 WoW64 64-bit) kurmak. Bu basitleştirilmiş versiyonu

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Point AddByVal(Point a, Point b)
    {
        return new Point(a.X   b.Y, a.Y   b.X);
    }

    public static void Main()
    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);
    }
}

Point basitçe ifade:

public struct Point 
{
    private readonly double _x, _y;

    public Point(double x, double y) { _x = x; _y = y; }

    public double X { get { return _x; } }

    public double Y { get { return _y; } }
}

Sonuçlar makalesinde benzer üretir koşu:

Result: x=1000000001 y=1000000001, Time elapsed: 3159 ms

İlk garip gözlem

Yöntem inlined olması gerektiğinden, kod yapılar tamamen kaldırıldı ve sadece her şeyi birlikte inlined eğer nasıl performans göstereceğini merak diye

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    public static void Main()
    {
        // not using structs at all here
        double ax = 1, ay = 1, bx = 1, by = 1;

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
        {
            ax = ax   by;
            ay = ay   bx;
        }
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", 
            ax, ay, sw.ElapsedMilliseconds);
    }
}

Ve neredeyse JİT-ter aynı sonucu (aslında birkaç denemeden sonra 1% daha yavaş), yani iyi bir iş, tüm işlev çağrıları optimize yapıyor gibi görünüyor:

Result: x=1000000001 y=1000000001, Time elapsed: 3200 ms

Ayrıca kriter struct herhangi bir performans ölçmek gibi görünüyor ve aslında sadece double temel aritmetik uzakta her şeyi optimize aldıktan sonra () ölçmek için görünmüyor anlamına gelir.

Garip şeyler

Şimdi işin garip kısmı. Ben sadece eklemekbaşka bir döngü dışında kronometre(Evet, birkaç denemeden sonra bu çılgın adım falan indirdim), kod çalışırüç kat daha hızlı:

public static void Main()
{
    var outerSw = Stopwatch.StartNew();     // <-- added

    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    outerSw.Stop();                         // <-- added
}

Result: x=1000000001 y=1000000001, Time elapsed: 961 ms

Bu çok saçma! Stopwatch açıkça tek bir saniye sonra bitiyor görebiliyorum çünkü beni yanlış sonuçlar veriyor gibi değil.

Herkes burada neler olabileceğini söyleyebilir mi?

(Update)

Ben JİTting ile ilgili bir şey olduğunu da kabul, ama bu test birkaç kez (dış kronometre olmadan) yinelenen her durumda yavaş:

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Point AddByVal(Point a, Point b)
    {
        return new Point(a.X   b.Y, a.Y   b.X);
    }

    public static void Main()
    {
        Test();
        Test();
        Test();
    }

    private static void Test()
    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", 
           a.X, a.Y, sw.ElapsedMilliseconds);
    }
}

Çıkış:

Result: x=1000000001 y=1000000001, Time elapsed: 3251 ms
Result: x=1000000001 y=1000000001, Time elapsed: 3249 ms
Result: x=1000000001 y=1000000001, Time elapsed: 3250 ms

Yine, dış kronometre eklendi, sihirli artışı elde ederiz

    public static void Main()
    {
        Test();
        Test();
        Test();
    }

    private static void Test()
    {
        var outerSw = Stopwatch.StartNew();

        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms", a.X, a.Y, sw.ElapsedMilliseconds);

        outerSw.Stop();
    }

Result: x=1000000001 y=1000000001, Time elapsed: 979 ms
Result: x=1000000001 y=1000000001, Time elapsed: 982 ms
Result: x=1000000001 y=1000000001, Time elapsed: 978 ms

(Güncelleme 2)

Burada sebebi değil JİTting olduğunu gösteren aynı programda iki yöntem:

public static class CSharpTest
{
    private const int ITERATIONS = 1000000000;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Point AddByVal(Point a, Point b)
    {
        return new Point(a.X   b.Y, a.Y   b.X);
    }

    public static void Main()
    {
        Test1();
        Test2();

        Console.WriteLine();

        Test1();
        Test2();
    }

    private static void Test1()
    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    private static void Test2()
    {
        var swOuter = Stopwatch.StartNew();

        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms", 
            a.X, a.Y, sw.ElapsedMilliseconds);

        swOuter.Stop();
    }
}

Çıkış:

Test1: x=1000000001 y=1000000001, Time elapsed: 3242 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 974 ms

Test1: x=1000000001 y=1000000001, Time elapsed: 3251 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 972 ms

(Güncelleme 3)

Here is a pastebin. 32-bit sürümü olarak çalıştırmanız gerekir .NET 4.x (bunu sağlamak için kod birkaç Çek var).

(Belki de çözüm?)(ayrıntılar için @HansPassant's answer okuma)

@Hans göre, yavaşlama 64-bit değişkenleri her zaman 32-bit uygulamalar uyumlu değil varlık nedeniyle oluyor. Eğer test yöntemlerden biri 32-bit bir değişken eklerseniz, hızlı C sürümü olarak gerçekleştirir:

private static void Test3()
{
    var magical_speed_booster = "whatever";

    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    GC.KeepAlive(magical_speed_booster);
}

(Güncelleme 4)

Aşağıdaki @usr @yorum Hans' cevap, optimum çözümü her iki yöntem için kontrol ettim, ve oldukça farklı:

Test1 on the left, Test2 on the right

Bu fark komik ilk halinde hareket derleyici yerine alan hizalama çift kaynaklanmış olabileceğini göstermek gibi görünüyor?

Eğer eklersem deikideğişkenler (8 bayt uzaklığı toplam), ben hala aynı hız artacak ve artık alan hizalama ile ilgili gibi görünüyor:

// this is still fast?
private static void Test3()
{
    var magical_speed_booster_1 = "whatever";
    var magical_speed_booster_2 = "whatever";

    {
        Point a = new Point(1, 1), b = new Point(1, 1);

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < ITERATIONS; i  )
            a = AddByVal(a, b);
        sw.Stop();

        Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
            a.X, a.Y, sw.ElapsedMilliseconds);
    }

    GC.KeepAlive(magical_speed_booster_1);
    GC.KeepAlive(magical_speed_booster_2);
}

CEVAP
20 AĞUSTOS 2015, PERŞEMBE


Her zaman "" sürüm programı. hızlı almak için çok basit bir yolu var Proje >Özellikleri >Sekme, işaretini kaldırın "32-bit" seçeneği, Platformu hedef seçimi AnyCPU olduğundan emin olun. oluşturmayı Tercih ediyorum

Gerçekten 32-bit, ne yazık ki hep C için varsayılan olarak açıktır tercih etme# projeleri. Tarihsel olarak, Visual Studio araç çok daha iyi 32-bit işlemleri, Microsoft uzakta yontma için geçerli olan bir sorun ile çalıştı. Bu seçenek, VS2015 özellikle görevden almak için zaman barikat 64-bit kodunu Düzenlemek için yepyeni 64 titreme ve evrensel bir destek ile son birkaç gerçek Devam değindi.

Keşfettiğiniz şey bu kadar çene çalmak yeter, öneminihizalamadeğişkenler için. İşlemci hakkında çok şey umurunda. Eğer bir değişken yanlış hizalanmış hafıza işlemci ekstra iş bayt doğru sırayla onları almak için shuffle var. İki farklı kayma sorunları var, bir bayt hala bekar L1 önbellek satırı içinde nerede olduğu ekstra bir dönemi uygun pozisyona vardiya maliyetleri. Ve ekstra kötü bir bayt parçası bir önbellek satırı ve parçası olduğu bulduğunuz bir, bir. İki ayrı bellek erişir ve onları birbirine yapıştırma gerektirir. Yavaş olarak üç kez.

double long tür bela-yapımcıları bir 32-bit işlem. 64-bit boyut. Ve böylece 4, CLR tarafından yanlış hizalanmış almak sadece bir 32-bit uyumu garanti edemez. 64-bit bir işlem için bir sorun değil, tüm değişkenler 8 ile uyumlu olmasını garanti etmektedir. Ayrıca temel sebebi C# dili olamaz onlara söz veriyorumatom. Ve neden çift dizileri 1000'den fazla eleman var ne zaman Büyük Nesne Yığın ayrılır. LOH 8 hizalama bir garanti sağlar. Ve hareket etti yani yerel bir değişken sorunu çözüldü, bir nesne başvurusu ekleme 4 bayt olduğunu açıklıyorçift4, hizalanmış almak artık değişken. Kazara.

32-bit C veya C compiler ek iş sağlamak için yaparçiftyanlış olamaz. Bir yığın işlevi, sadece garanti 4 ile uyumlu olduğunu düşünürsek girildiğinde yanlış hizalanmış olabilir çözmek için basit bir sorun değil. Böyle bir işlevi önsözünde ek iş 8 için bu şekilde yapmak gerekiyor. Aynı numarayı yönetilen bir program işe yaramaz, çöp toplayıcı, tam olarak yerel bir değişken bellekte bulunduğu büyük önem verir. GC yığını içinde bir nesne hala başvurulan keşfetmek, böylece gerekli. Anlaşma şekilde böyle bir değişken yöntemi girildiği zaman yanlış oldu yığını çünkü 4 hareket elde edemiyor.

Bu da temel sorundur .NET sinirlilik kolayca SIMD talimatları destekleyici değil. Çok daha güçlü hizalama gereksinimleri, işlemci kendisi de çözmek o tip arabaları var. SSE2 16, AVX, bir hizalama 32 bir uyum gerektirir. O içeri giremez yönetilen kod.

32-bit modunda çok öngörülemeyen çalışan son ama en az değil, ayrıca bu bir C perf yaptığını unutmayın# programı. Ne zaman bir erişimçiftyauzunbir alan olarak depolanan bir nesne daha sonra perf çöp toplayıcı yığın düzenler büyük ölçüde değiştirir. Bellek nesneleri hareket eden, böyle bir alan şimdi mis/hizalanır. Tabii ki çok rasgele, çok kafa rahatlatıcı :) olabilir

Peki, 64-bit kodunu basit giderir ama bir geleceği. Jetta Microsoft proje şablonu değiştirmeyecek sürece zorlayarak çıkarın. Ryujit hakkında daha güvende hissettiklerinde bir sonraki sürümü belki.

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

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • SignatureSeries

    SignatureSer

    24 Aralık 2006
  • FF Radioo

    FF Radioo

    14 ŞUBAT 2007
  • wwjoshdew

    wwjoshdew

    1 AĞUSTOS 2007