Vergleichen der C # - und C ++ - Leistung in Bildverarbeitungsaufgaben

Es gibt eine Meinung, dass C # keinen Platz in Rechenaufgaben hat, und diese Meinung ist durchaus vernünftig: Der JIT-Compiler ist gezwungen, den Code während der Programmausführung mit minimalen Verzögerungen im laufenden Betrieb zu kompilieren und zu optimieren. Er hat einfach keine Möglichkeit, mehr Rechenressourcen auszugeben, um effizienteren Code zu generieren im Gegensatz zum C ++ - Compiler, der in dieser Angelegenheit Minuten und sogar Stunden dauern kann.





In den letzten Jahren hat sich die Effizienz des JIT-Compilers jedoch erheblich erhöht, und eine Reihe nützlicher Chips wurden in das Framework selbst integriert, z. B. Intrinsics .





Und jetzt habe ich mich gefragt: Ist es im Jahr 2020 möglich, mit .NET 5.0 Code zu schreiben, dessen Leistung C ++ nicht wesentlich unterlegen wäre? Es stellte sich heraus, dass Sie können.





Motivation

Ich beschäftige mich mit der Entwicklung von Bildverarbeitungsalgorithmen auf einem relativ niedrigen Niveau. Das heißt, dies ist kein Jonglieren mit Steinen in Python, sondern die Entwicklung von etwas Neuem und vorzugsweise Produktivem. Python-Code dauert unannehmbar lange, während die Verwendung von C ++ zu einer Verringerung der Entwicklungsgeschwindigkeit führt. Das optimale Gleichgewicht zwischen Produktivität und Leistung für solche Aufgaben wird mit C # und Java erreicht. Zur Bestätigung meiner Worte - das Fidschi- Projekt .





Zuvor habe ich C # für das Prototyping verwendet und vorgefertigte Algorithmen, die für die Leistung in C ++ von entscheidender Bedeutung sind, neu geschrieben, in die Bibliothek verschoben und die Bibliothek aus C # gezogen. In diesem Fall litt die Portabilität, und es war nicht sehr praktisch, den Code zu debuggen.





, .NET , , C++ C#?





: , , , . C++. .





, , C# C++:





  • GetPixel(x, y) SetPixel(x, y, value);





  • ;





  • (AVX).





(Array.Sort, std::sort), , , , . .





, , C# unmanaged - . - , C++ UB , C# - .





Github, , C#:





[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_ThisProperty(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    for (var j = 0; j < res.Height; j++)
    for (var i = 0; i < res.Width; i++)
        res[i, j] = img1[i, j] + img2[i, j];
}

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_Optimized(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    var w = res.Width;

    for (var j = 0; j < res.Height; j++)
    {
        var p1 = img1.PixelAddr(0, j);
        var p2 = img2.PixelAddr(0, j);
        var r = res.PixelAddr(0, j);

        for (var i = 0; i < w; i++)
            r[i] = p1[i] + p2[i];
    }
}

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void Sum_Avx(NativeImage<float> img1, NativeImage<float> img2, NativeImage<float> res)
{
    var w8 = res.Width / 8 * 8;

    for (var j = 0; j < res.Height; j++)
    {
        var p1 = img1.PixelAddr(0, j);
        var p2 = img2.PixelAddr(0, j);
        var r = res.PixelAddr(0, j);

        for (var i = 0; i < w8; i += 8)
        {
            Avx.StoreAligned(r, Avx.Add(Avx.LoadAlignedVector256(p1), Avx.LoadAlignedVector256(p2)));

            p1 += 8;
            p2 += 8;
            r += 8;
        }
        
        for (var i = w8; i < res.Width; i++)
            *r++ = *p1++ + *p2++;
    }
}

      
      







. (1/10 ) 256x256 float 32 bit.









dotnet build -c Release





g++ 10.2.0 -O0





g++ 10.2.0 -O1





g++ 10.2.0 -O2





g++ 10.2.0 -O3





clang 11.0.0 -O2





clang 11.0.0 -O3





Sum (naive)





115.8





757.6





124.4





36.26





19.51





20.14





19.81





Sum (opt)





40.69





255.6





36.07





24.48





19.60





20.11





19.81





Sum (avx)





21.15





60.41





20.00





20.18





20.37





20.23





20.20





Rotate (naive)





90.29





500.3





87.15





36.01





14.49





14.04





14.16





Rotate (opt)





34.99





237.1





35.11





34.17





14.55





14.10





14.27





Rotate (avx)





14.83





51.04





14.14





14.25





14.37





14.22





14.72





Median 3x3





4163





26660





2930





1607





2508





2301





2330





Median 5x5





11550





10090





8240





5554





5870





5610





6051





Median 7x7





23540





24470





17540





13640





12620





12920





13510





Convolve 7x7 (naive)





5519





30900





3240





3694





2775





3047





2761





Convolve 7x7 (opt)





2913





11780





2759





2628





2754





2434





2262





Convolve 7x7 (avx)





709.2





3759





729.8





669.8





684.2





643.8





638.3





Convolve 7x7 (avx*)





505.6





2984





523.4





511.5





507.8





443.2





443.3





: Convolve 7x7 (avx*) - , , .





Core i7-2600K @ 4.0 GHz.





:





  • (avx), C#, , C++. , C# !





  • C# , C# , C++ .





  • C# C++ 2 6 . .





Ja, Sie können Rechencode in C # schreiben, der eine Leistungsparität mit C ++ aufweist. Dazu müssen Sie jedoch auf manuelle Optimierungen im Code zurückgreifen: Was der C ++ - Compiler automatisch tut, müssen Sie in C # selbst tun. Wenn Sie keine Bindung zu C # haben, schreiben Sie daher weiter in C ++.





PS .NET verfügt über eine Killer-Funktion: Es ist die Möglichkeit, zur Laufzeit Code zu generieren. Wenn die Bildverarbeitungspipeline nicht im Voraus bekannt ist (z. B. vom Benutzer festgelegt), müssen Sie sie in C ++ aus Bausteinen zusammensetzen und möglicherweise sogar virtuelle Funktionen verwenden, während Sie in C # eine höhere Leistung erzielen können, indem Sie einfach eine Methode generieren.












All Articles