Benutzer mit den meisten Antworten
Wie kann ich die Performance beim Zugriff auf COM / ActiveX-Komponenten verbessern

Frage
-
Ich arbeite zur Zeit an der Migration einer VB6-Anwendung nach .NET. Die Anwendung ist vor ca. 10 Jahren entstanden und verwendet eine für die heutige Zeit ungewöhnliche Methode des Datenzugriffs. Aus Performancefgründen wurde damals eine C++-DLL erstellt, welche ausgewählte Teile der Datenbank im Hauptspeicher chached. Um diese DLL wurde dann ein OCX gestrickt und dieses in die VB6-Anwendung eingebunden. Auch nach der Migration nacch .NET funktioniert diese Art des Zugriffs ganz gut. Lediglich die Performance der Anwendung ist um den Faktor 5 - 10 schlechter als unter VB6. Durch Profiling haben wir herausgefunden, dass ein Bottleneck die Aufrufe zum Datenzugriff sind. Es scheint, dass die Konvertierung der Daten zwischen Managed- und Unmanged Code relativ lange benötigen.
Um Diskussionen vorzubeugen: Wir haben uns bewusst entschieden, keine Neuimplementierung durchzuführen, weil uns das Risiko hinterher eine fehlerhafte Anwendung zu haben zu groß erschien (innerhalb der Anwendung werden komplizierte Berechnungen in Abhängigkeit von den Daten durchgeführt, die einfach nicht komplett zu testen wären). Ursprünglich wollten wir den C++-Teil in Managed Code überführen. Dies hat sich jedoch, auf Grund der Programmierung, als nicht durchführbar herausgestellt.
Kennt jemand irgendwelche Tricks, wie man die Kommunikation zwischen Managed- und Unmanaged Code optimieren kann?
Dank im Voraus für die Hilfe
Andreas Seibt- Verschoben Kay GizaMicrosoft employee Donnerstag, 10. Juni 2010 08:36 (aus:Visual Basic (ab Version 2002 / .NET))
Antworten
-
Hallo Andreas,
um zu entscheiden, ob der Weg über COM oder unmanaged Interop bessere Resultate liefert,
sollte man sich die Signaturen (Declares) der C++-DLL anschauen.
Wenn die DLL im Hinblick auf VB geschrieben wurde, ist der OCX Wrapper vermutlich
eher eine Ballast-Schicht.
Ziel sollte sein, das so früh als möglich mit "Option Strict On" gearbeitet werden kann
und das übrige Programm auf weitere Konvertierung verzichten kann.
Nützlich wäre, ob Arrays in IList(Of T) etc. wandelt, da man dadurch auf Anwendungsseite
mehr von .NET hat (z. B. Linq).
Was ein nativer .NET Wrapper anstatt des OCX Wrappers vermutlich am besten könnte,
nur eben auch einiges an Aufwand bedeutet. Wenn die jetzigen Klassen für die diversen
Tabellen einem ähnlichen Schema folgen, wie ich vermute, könnte man versuchen sich den
Rohcode via Reflection erzeugen zu lassen (und evtl. den Unit Test gleich dazu).
Verbessern kann man das COM Interop, in dem man einen ähnlichen Weg geht wie Office mit seinen PIAs.
Allerdings ist das ein relativ mühseliger Weg (ich hatte so etwas für ein kleineres OCX zu .NET 1.1
Zeiten probiert und schnell aufgegeben). Und wenn Variants übergeben werden, so müssen die
intern immer konvertiert werden (es existiert dafür eine interne System.Variant Struktur uam.).
Mit .NET 4.0 wird die Unterstützung für COM - Dank an Office, das davon nicht lassen will ;-)
besser werden. Ob das in Deinem Falle was bringt, kann ich nicht beurteilen (ich mache nichts
mit VSTO und Co., so dass mein Interesse daran gering ist - und die Zeitfrage bis RTM käme hinzu.
Visual Basic .NET erlaubt durch implizites Casten viel, was auf Kosten der Laufzeit geht,
wie Dir das DirectCast gezeigt hat.
Aufrufe von CompilerServices.Conversion sind ein typisches Zeichen für Konvertierung
von System.Object (was bei COM-Variant zunächst geliefert wird). Dabei wird über IConvertible,
TypeCode in den konkreten Typ konvertiert.
ForwardCallToInvokeMember steht für den COM-Proxy Aufruf via
http://msdn.microsoft.com/de-de/library/system.type.invokemember.aspx
und verpackt die Parameter mundgerecht. Arrays sind z. B. wiederum aufwändiger
als integrale Typen.
Am Ende bleibt vermutlich nur einen Teil auseinanderzunehmen und zu sehen,
was mit vertretbarem Aufwand realisierbar ist.
Und den neuen Teil der Anwendung so aufzubauen, dass man einen Austausch
vornehmen kann, wenn der Schmerz beim Kunden groß genug wird, dass er nicht
weiter an seiner 1 : 1 Vorgabe festhält.
Was die MSDN Dokumentation angeht:
Dort wird auf abstraktem allgemeinen Niveau dokumentiert, jedoch relativ akkurat -
wenn man COM / unmanged gearbeitet hat und als VB'ler sein Hard Core Visual Basic
nicht ganz vergessen hat (bei mir schwindet es mittlerweile dramatisch ;-)
Für C++ gibt es einen weiteren Bereich unter
Aufrufen systemeigener Funktionen aus verwaltetem Code
Die schmutzigeren Details findet man am ehesten in den Archiven der Microsoft Blogs
(runter bis .NET 1.0/2002 gehen), als Anfang:
http://www.google.de/search?hl=en&q=.net+marshalling+site%3Ablogs.msdn.com
Zu Deiner Anmerkung:
Und eine Oracle Anbindung wäre in .NET mit dem Oracle eigenen .NET Client heute
ohne größere Schmerzen realisierbar - Hinweis: Microsoft beerdigt den eigenen
http://blogs.msdn.com/adonet/archive/2009/06/15/system-data-oracleclient-update.aspx
Gruß Elmar- Als Antwort markiert Andreas Seibt Donnerstag, 30. Juli 2009 05:43
Alle Antworten
-
Hallo Andreas,
um konkreter was sagen zu können, solltest Du näher ausführen, was Du meinst mit:
Durch Profiling haben wir herausgefunden, dass ein Bottleneck die Aufrufe zum Datenzugriff sind.
Es scheint, dass die Konvertierung der Daten zwischen Managed- und Unmanged Code relativ lange benötigen.
Was wird da übergeben?
Da bei VB6 ADO üblich war, werden da Recordset Objekte oder ähnliches übergeben?
Wird intensiv mit Variant gearbeitet?
Du solltest Dich intensiv beschäftigen mit Interaktion mit nicht verwaltetem Code
Gruß Elmar -
Hallo Elmar,
wie bereits gesagt, ist die C++-DLL, welche den Datenzugriff managed vor ca. 10 Jahren entstanden. Ich habe leider keinen Zugriff auf die Sourcen, aber was ich weiss ist, dass die DLL auf eine Oracle-Datenbank zugreift und hierzu direkt den Oracle Client verwendet (also kein ADO oder ODBC). Im Prinzip erstellt die DLL eine Art DataSet (also eine In-Memory Repräsentation eines Teils der Datenbank). Es gibt diverse Klassen, die Tabellen der Datenbank repräsentieren. Listen von Datensätzen werden als Arrays verwaltet. Es gibt diverse Verwaltungsklassen, die auch in der .NET-Anwendung verwendet werden, um den Zugriff auf die Daten zu steuern. Des weiteren gibt es in der VB-Anwengung für jede C++-Datenklasse eine Wrapper-Klasse, welche den Zugriff kapselt. Eine Besonderheit ist, dass alle Methoden der DLL den Datentyp Variant sowohl als Parameter als auch als Rückgabewert verwenden (auch wenn Intrinsincs wie Integer oder String zurückgegeben werden). Dies macht natürlich ein Casting der Daten notwendig. Ich vermute, dass dieses Casting einen Teil der Performance-Probleme verursacht. Wenn ich mir die Profiler-Ergebnisse ansehe gitb es eine Reihe von Aufrufen "VisualBasic.CompilerServices.Conversions.To...". Ich habe testweise in einer Klasse alle impliziten Casts durch DirectCast() ersetzt und dadurch einen leichten Perfomance-Vorteil erzielt. Des weiteren erfolgt der Aufruf von Methoden in der DLL über Reflection mit "System.RuntimeType.ForwardCallToInvokeMember" was auch relativ langsam ist. Mein Problem ist, dass Die Datenabank weit über 100 Tabellen enthält und in der DLL und Anwendung entsprechend viele Klassen existieren. Die Umstellung des Codes auf eine typisierte Schnittstelle würde einfach zu viel Aufwand erfordern. Gibt es vielleicht eine Möglichkeit, das Marshalling über Attribute zu optimieren? Dann könnte ich einfach die generierte Interop-Assembly ersetzen.
Wie sähe es aus, wenn ich das OCX durch eine Wrapper-Assembly, die per P/INVOKE direkt auf die DLL zugreift, ersetzen würde. Könnte dies eine deutliche Perfomance-Verbesserung bewirken?
Die Seiten hinter dem Link habe ich mir schon früher mal angesehen. Es ist dort viel Material für Anfänger, die sich noch nicht mit dem Thema beschäftigt haben, zusammengestellt. Ich habe beim ersten Lesen wohl zu schnell die Geduld verloren und bin nicht tief genug eingestiegen (Generell ist es auf den MSDN-Seiten problematisch aus der Fülle der Informationen die für einen selbst relevanten zu filtern).
Eine Anmerkung: Meine Persönliche Meinung zu der Anwendung ist, dass sie eigentlich mit den heute zur Verfügung stehend Mitteln neu geschrieben werden sollte. Mein Kunde bestand aber auf einer 1:1-Migration und so muss ich mich heute mit den daraus resultierenden Problemen herumschlagen ;-(
Gruß
Andreas -
Hallo Andreas,
um zu entscheiden, ob der Weg über COM oder unmanaged Interop bessere Resultate liefert,
sollte man sich die Signaturen (Declares) der C++-DLL anschauen.
Wenn die DLL im Hinblick auf VB geschrieben wurde, ist der OCX Wrapper vermutlich
eher eine Ballast-Schicht.
Ziel sollte sein, das so früh als möglich mit "Option Strict On" gearbeitet werden kann
und das übrige Programm auf weitere Konvertierung verzichten kann.
Nützlich wäre, ob Arrays in IList(Of T) etc. wandelt, da man dadurch auf Anwendungsseite
mehr von .NET hat (z. B. Linq).
Was ein nativer .NET Wrapper anstatt des OCX Wrappers vermutlich am besten könnte,
nur eben auch einiges an Aufwand bedeutet. Wenn die jetzigen Klassen für die diversen
Tabellen einem ähnlichen Schema folgen, wie ich vermute, könnte man versuchen sich den
Rohcode via Reflection erzeugen zu lassen (und evtl. den Unit Test gleich dazu).
Verbessern kann man das COM Interop, in dem man einen ähnlichen Weg geht wie Office mit seinen PIAs.
Allerdings ist das ein relativ mühseliger Weg (ich hatte so etwas für ein kleineres OCX zu .NET 1.1
Zeiten probiert und schnell aufgegeben). Und wenn Variants übergeben werden, so müssen die
intern immer konvertiert werden (es existiert dafür eine interne System.Variant Struktur uam.).
Mit .NET 4.0 wird die Unterstützung für COM - Dank an Office, das davon nicht lassen will ;-)
besser werden. Ob das in Deinem Falle was bringt, kann ich nicht beurteilen (ich mache nichts
mit VSTO und Co., so dass mein Interesse daran gering ist - und die Zeitfrage bis RTM käme hinzu.
Visual Basic .NET erlaubt durch implizites Casten viel, was auf Kosten der Laufzeit geht,
wie Dir das DirectCast gezeigt hat.
Aufrufe von CompilerServices.Conversion sind ein typisches Zeichen für Konvertierung
von System.Object (was bei COM-Variant zunächst geliefert wird). Dabei wird über IConvertible,
TypeCode in den konkreten Typ konvertiert.
ForwardCallToInvokeMember steht für den COM-Proxy Aufruf via
http://msdn.microsoft.com/de-de/library/system.type.invokemember.aspx
und verpackt die Parameter mundgerecht. Arrays sind z. B. wiederum aufwändiger
als integrale Typen.
Am Ende bleibt vermutlich nur einen Teil auseinanderzunehmen und zu sehen,
was mit vertretbarem Aufwand realisierbar ist.
Und den neuen Teil der Anwendung so aufzubauen, dass man einen Austausch
vornehmen kann, wenn der Schmerz beim Kunden groß genug wird, dass er nicht
weiter an seiner 1 : 1 Vorgabe festhält.
Was die MSDN Dokumentation angeht:
Dort wird auf abstraktem allgemeinen Niveau dokumentiert, jedoch relativ akkurat -
wenn man COM / unmanged gearbeitet hat und als VB'ler sein Hard Core Visual Basic
nicht ganz vergessen hat (bei mir schwindet es mittlerweile dramatisch ;-)
Für C++ gibt es einen weiteren Bereich unter
Aufrufen systemeigener Funktionen aus verwaltetem Code
Die schmutzigeren Details findet man am ehesten in den Archiven der Microsoft Blogs
(runter bis .NET 1.0/2002 gehen), als Anfang:
http://www.google.de/search?hl=en&q=.net+marshalling+site%3Ablogs.msdn.com
Zu Deiner Anmerkung:
Und eine Oracle Anbindung wäre in .NET mit dem Oracle eigenen .NET Client heute
ohne größere Schmerzen realisierbar - Hinweis: Microsoft beerdigt den eigenen
http://blogs.msdn.com/adonet/archive/2009/06/15/system-data-oracleclient-update.aspx
Gruß Elmar- Als Antwort markiert Andreas Seibt Donnerstag, 30. Juli 2009 05:43
-
Hallo Elmar,
ich habe ja schon geahnt, dass meine Probleme nicht so einfach zu lösen sind. Du hast mich auf jeden Fall in meinen Überlegungen zur Lösung des Problems bestätigt und mir gute Argumetnationshilfen gegebüber meinem Kunden geliefert.
Ich hoffe, dass ich ihn überzeugen kann, noch ein wenig Geld in die Hand zu nehmen und die Datenbankschnittstelle von mir überarbeiten zu lassen.
Vielen Dank
Andreas