C++ Neu übersetzter Code langsamer - Ursachen?

CBase7

Cadet 1st Year
Registriert
Okt. 2022
Beiträge
8
Hallo zusammen,

ich bin auf der Suche nach einem Performance-Problem mit selbst geschrieben C++-Code, bin mit meinem Latein am Ende und Suche nach Ideen, wie ich der Ursache auf den Grund komme.

Das Programm:
  • Selbst geschrieben in C++
  • Es ist ein rechenintensives, Multithreading Programm für ein Optimierungsproblem.
    Charakteristik: praktisch nur Integer-Arithmetik + Funktionsaufrufe; 32 Bit, 100% CPU-Last, fast keine Speicherzugriffe, keine Ausgaben, Laufwerks- oder Netzzugriffe (Charakteristik ähnlich zu Prime95).

Das Problem:
  • Das Programm hab ich in 2012 übersetzt (exe-Datei). Es läuft sehr schnell auf meinem heutigen Rechner.
  • Wenn ich den (unveränderten Code) heute neu übersetze, dann schafft das 2023er-EXE ca. 15% weniger Leistung, d.h. es benötigt 15% länger als die 2012er-Version für dieselben Berechnungen auf demselben Rechner!

Details:
  • 2012er-Version wurde mit MS Visual C++ 2010 Express übersetzt.
  • 2023er-Version wurde mit MS Visual Studio 2022 übersetzt.
  • Ausgeführt wird es heute unter Win11 auf einem I7-13700K.

Hat irgendjemand Ansatzpunkte, wo ich nach der Ursache suchen kann?
Vielen Dank für jeden Tipp!

Danke!!



P.S.
  • Compiler-Flags sind (annähernd) identisch:
    2012:
    /D "WIN32"/D "_UNICODE"
    /EHsc
    /FU"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll"
    /FU"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Data.dll"
    /FU"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Xml.dll"
    /FU"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.dll"
    /Fa"Release\"
    /Fd"Release\vc100.pdb"
    /Fo"Release\"
    /Fp"Release\Optimierer.pch"
    /GS
    /O2
    /W3
    /WX-
    /Zc:forScope
    /Zc:wchar_t
    /Zi
    /analyze-
    /errorReport:queue
    /fp:precise
    /nologo

    2023:

  • /D "WIN32"
    /D "_UNICODE"
    /EHsc
    /FC
    /FU"C:\Windows\assembly\GAC_32\System.Data\2.0.0.0__b77a5c561934e089\System.Data.dll"
    /FU"C:\Windows\assembly\GAC_MSIL\System.Xml\2.0.0.0__b77a5c561934e089\System.Xml.dll"
    /FU"C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.dll"
    /Fa"Release\"
    /Fd"Release\vc143.pdb"
    /Fo"Release\"
    /Fp"Release\Optimierer.pch"
    /GS
    /Gd
    /O2
    /W3
    /WX-
    /Zc:forScope
    /Zc:wchar_t
    /Zi
    /analyze-
    /diagnostics:column
    /errorReport:prompt
    /fp:precise
    /ifcOutput "Release\"
    /nologo
 
Interessantes Problem, kann leider nicht viel beitragen und bin gespannt, ob wir das hier im Forum noch gelöst bekommen :)

Aber zu dem hier:
CBase7 schrieb:
  • 2012er-Version wurde mit MS Visual C++ 2010 Express übersetzt.
  • 2023er-Version wurde mit MS Visual Studio 2022 übersetzt.
Hast Du ins 2023er VS die alte Projektdatei importiert und dabei automatisch konvertieren lassen? Wenn ja, dann vergleich mal die Settings gegen ein frisch angelegtes C++ Projekt von VS 2023.
 
CBase7 schrieb:
Compiler-Flags sind (annähernd) identisch:
Ich würde erstmal für identischen Voraussetzungen und nicht nur annähernd sorgen, damit du wirklich vergleichen kannst. Die unterschiedlichen DLLs und Compileroptionen können dafür natürlich schon verantwortlich sein.
 
  • Gefällt mir
Reaktionen: BeBur und Aduasen
Es kann sogar rein am Compiler liegen. Compiler werden ständig weiterentwickelt.
Normalerweise ja damit das Programm schneller wird, aber viele Entscheidungen sind ein trade off, und eine Optimierung kann im Fall X schnelleren Code haben, die andere Optimierung dafür im Fall Y. Kann sein, dass da etwas zum Nachteil für deinen Fall wurde.
Ggf. ist im Code sogar eine Compilerspezifische Optimierung im Code vorhanden, und die wurde mit einer neueren Version des Compilers obsolet.

Für echte Vergleichbarkeit kannst du erstmal wirklich die gleichen Compileroptionen und libraries einbinden und dann die assembly diffen.
 
  • Gefällt mir
Reaktionen: TomH22
  • Wofür brauchst du die Sicherheitschecks mit /GS ?
  • Du benutzt kein LTO: /LTCG
  • Aggressiveres Inlining?: /Ob3
  • Probiere es mit Vektorisierung, auch wenn der MSVC das nicht gut kann: /arch:AVX2
  • Du generierst keine FMA-Instruktionen: /fp:contract
  • Anderen Compiler (Clang oder GCC) probiert? Kenne deinen Code nicht, aber Clang vektorisiert deutlich besser.
 
  • Gefällt mir
Reaktionen: nullPtr, jlnprssnr, ZuseZ3 und 2 andere
Wofür brauchst du das Program, bzw. wie viel Zeit willst du reinstecken um die Performance zu verbessern?
 
KitKat::new() schrieb:
Normalerweise ja damit das Programm schneller wird, aber viele Entscheidungen sind ein trade off, und eine Optimierung kann im Fall X schnelleren Code haben, die andere Optimierung dafür im Fall Y. Kann sein, dass da etwas zum Nachteil für deinen Fall wurde.
Das ist sehr plausibel.
Ich habe die Erfahrung z.B. mit dem GCC für RISC-V gemacht, da haben verschiedene Versionen auch bis zu 15% Abweichung. Da das Zielsystem eine in einem FPGA implementierte CPU mit nur 100Mhz Takt war, fallen diese Unterschiede relativ stark auf. Es handelte sich übrigens um Bare-Metal Code, also kein Einfluss vom Betriebssystem.
CBase7 schrieb:
Hat irgendjemand Ansatzpunkte, wo ich nach der Ursache suchen kann?
Du musst halt erst mal systematisch nach den Hot-Spots in Deinem Code suchen (meist ist ja nur ein sehr kleiner Teil der Codebasis für die Laufzeiten verantwortlich, üblicherweise die Leaf-Funktionen, also die Funktionen die die Arbeit machen und keine weiteren Funktionen mehr aufrufen). Wenn man die identifiziert hat, muss man sich den Code auf Assembler Ebene anschauen, wenn man wissen will, ob der Compiler unnötig schlechten Code produziert.
Kann auch viele Ursachen geben, z.B. ist ja "inline" nur eine Empfehlung, der Compiler kann auch einen Call daraus machen, das kostet natürlich dann extra Zeit.

Auch die Anordnung des Codes beim Linken kann deutliche Unterschiede hervorrufen, denn wenn man Pech hat, hat man eine schlechtere Cache-Hit Rate oder ähnliches.
Laut Wikichip sollte man diese Optionen setzen, wenn man Code für Alder Lake tunen möchte:

/arch:AVX2/tune:alderlake
 
Falls dich Auswirkungen der Compilerversion, -flags, Vektorisierung, Optimierungen, etc. interessieren, dann kann ich den Compiler Explorer definitiv empfehlen: https://godbolt.org/
Dank llvm-mca bekommt man auch grobe Abschätzungen für benötigte Taktzyklen, Laufzeit, IPC etc.
 
  • Gefällt mir
Reaktionen: nullPtr und Simon#G
@wusu hat schon gute Hinweise gegeben.

Zum Performance-Debugging würde ich allgemein raten:
1. Alle Runtime-Checks vom Compiler abschalten (hier: /GS; sonst auch /RTx). Es kann gut sein, dass neuere Compiler aggressivere Checks einbauen.
2. Applikation neu mit dem alten Compiler übersetzen, um Flags zu verifizieren
3. Beispiel runterkochen (Multithreading raus, Compute-Kernels alleine übersetzen, ...) und dann die Binaries diffen (wie @wusu geschrieben hat, ist das mit dem Compiler Explorer gut machbar)

Falls du ein runtergekochtes Beispiel teilen kannst, wäre das recht hilfreich.

Du kannst auch beide Applikationen einfach mal disassemblieren und schauen, ob dir ein krasser Unterschied auffällt (in der Vektorisierung, im Inlining, etc.).
 
Zurück
Oben