Warum noch ein Access-Buch?
Für wen ist das Buch?
Jetzt bestellen
+ direkter Download des eBooks!
Nur EUR 59,95!
Fehler gefunden?
Bitte melden!
Wünsche an das Buch?
Her damit!
Was denken die Leser über dieses Buch?
Lesen Sie aktuelle Rezensionen!
Kapitel des noch nicht veröffentlichten Buchs zum Downloaden, Probelesen und Kommentieren
Beispieldatenbanken
Zusätzliches Material

Das Buch im HTML-Format

Für unbestimmte Zeit bieten Addison-Wesley und André Minhorst den kompletten Inhalt des Buchs als Download an. Schauen Sie rein und informieren Sie sich über den Inhalt! Und wenn Ihnen das Buch nützlich erscheint und Sie glauben, dass Sie etwas gelernt haben oder durch das Gelesene sogar etwas Zeit und somit Geld bei Ihrer Arbeit einsparen konnten, können Sie sich ja beim Autor und beim Verlag revanchieren - beispielsweise durch den Kauf dieses Buchs.

Am schönsten wäre es natürlich, wenn Sie das Buch direkt hier bestellen - Sie erhalten das Buch dann direkt vom Verlag, und der Autor und Verlag haben dann noch mehr davon, als wenn Sie es anderswo kaufen.

Danke für Ihr Interesse!

14.7 Performance-Unterschiede messen

14.7.1 Werkzeug für Performance-Tests selbst gebaut

Wenn Sie erst einmal wissen, wie Sie die Systemzeit mit der gewünschten Genauigkeit ermitteln, ist der Rest ein Kinderspiel. Wie so oft hilft die API aus: Die beiden verwendeten Funktionen tragen die Namen QueryPerformanceCounter und QueryPerformanceFrequency. Wie Sie die beiden Funktionen deklarieren und einsetzen, erfahren Sie im Modul clsZeitmessung der Beispieldatenbank unter \Kap_14\Performance.accdb. Das Messen der Performance ist gleichbedeutend mit dem Messen der Zeit, die Access für eine bestimmte Aufgabe benötigt. Das Ganze macht natürlich wenig Sinn, wenn es keine Vergleichsmöglichkeiten gibt - Sie werden also zum Zweck der Performance-Steigerung immer mit mindestens zwei Varianten zur Erledigung der Aufgabe arbeiten und vergleichen, welche der beiden die Aufgabe schneller erledigt.

Voraussetzung für die Zeitmessung bestimmter Vorgänge ist, dass sich diese Vorgänge per VBA starten lassen und das Ende auch per VBA erkannt wird. Da sich in Access fast jeder Vorgang per VBA steuern lässt, bedeutet diese Voraussetzung keine Einschränkung, sondern in manchen Fällen lediglich eine Erschwerung der Aufgabe. Ist diese Voraussetzung erfüllt, messen Sie die Zeit eines Vorgangs einfach, indem Sie vor dem Start die Systemzeit erfassen, den Vorgang durchführen und anschließend die Differenz aus der aktuellen und der beim Start erfassten Systemzeit bilden. Nun dauern die zu testenden Vorgänge vermutlich meist nur wenige Millisekunden und benötigen auch nicht immer exakt die gleiche Zeit, sodass Sie die zu testende Funktionalität mit dem Ziel einer hohen Genauigkeit doch lieber mehrmals durchführen und die benötigte Zeit messen sollten. Je mehr Durchläufe, desto genauer das Ergebnis.

Testframework

Und um dabei den Code nicht mit Zeitmessungen zu überladen, erstellen Sie ein kleines Framework zum einfachen Testen der Performance der unterschiedlichen Varianten einer Funktionalität. Sie können mit dem nachfolgend vorgestellten Framework sowohl Prozeduren aus Standardmodulen als auch aus Formularen und Klassenmodulen testen. Der Test der Prozeduren aus Standardmodulen erfordert allerdings einen etwas anderen Ansatz als der von Prozeduren in Formular- und Klassenmodulen.

Damit das Framework flexibel ist und Sie die zu testenden Prozeduren und die übrigen Informationen als Parameter eines einfachen Aufrufs übergeben können, benötigen Sie eine Funktion, der Sie nur den Namen der auszuführenden Routine übergeben müssen.

Aufrufen von Funktionen in Standardmodulen

Funktionen lassen sich ganz einfach mit der Eval-Anweisung aufrufen. Die Eval-Anweisung erwartet lediglich die Angabe des Namens der auszuführenden Funktion inklusive öffnender und schließender Klammer als Parameter:

Eval " Funktionsname()"

Diese Form des Aufrufs reicht für den Performancetest. Üblicherweise verwendet man die Eval-Anweisung allerdings, um das Resultat einer Funktion zurückzugeben - dann weist man das Ergebnis einer Variablen zu oder gibt es im Direktfenster aus:

Debug.Print Eval("LateBinding()")

Die Eval-Anweisung arbeitet nur mit Funktionen und nicht mit Sub-Prozeduren. Daher müssen Sie die zu testenden Anweisungen entweder direkt in eine Funktion schreiben oder die entsprechende Sub-Prozedur durch eine Funktion aufrufen lassen.

Aufrufen von Funktionen und Prozeduren in Formular- und Klassenmodulen

Für den Aufruf von Funktionen in Formular- und Klassenmodulen können Sie die Eval-Anweisung nicht verwenden. Selbst das Aufrufen öffentlich deklarierter Funktionen erfordert hier die Angabe des Objekts, in dem die Funktionen enthalten sind. Die Eval-Anweisung kann Funktionen nur über den reinen Funktionsnamen aufrufen. Hier kommt die CallByName-Methode ins Spiel: Sie ermöglicht die Ausführung von Funktionen und Sub-Prozeduren (genau genommen sogar auch von Property-Prozeduren, aber das ist in diesem Zusammenhang irrelevant).

Die CallByName-Methode hat folgende Syntax:

CallByName(object, procname, calltype,[args()])

Die Parameter erwarten die folgenden Informationen:

  • object: Objektvariable mit Verweis auf das Objekt, das die auszuführende Prozedur enthält
  • procname: Name der Prozedur (ohne Klammern!)
  • calltype: Erwartet eine der Konstanten vbLet, vbGet, vbSet oder vbMethod. Im vorliegenden Fall ist vbMethod der richtige Wert.
  • args(): Parameter-Array für die Werte, die an eventuell vorhandene Parameter der in proctype benannten Prozedur übergeben werden müssen
  • Eigenschaften und Methoden des Frameworks

    Das Framework besteht aus einer einzigen Klasse mit zwei Methoden und einigen Eigenschaften. Die Klasse stellt die folgenden Eigenschaften zur Eingabe der für den Test benötigten Parameter bereit:

  • ErsteRoutine: Name der ersten Routine
  • ZweiteRoutine: Name der zweiten Routine
  • AnzahlDurchgaenge: Anzahl der Durchgänge jeder Funktion
  • Objekt: Objektvariable mit Verweis auf ein Objekt, in dem sich die Prozeduren befinden
  • Die ersten drei Eigenschaften müssen vor dem Aufruf des Performancetests übergeben werden. Die Letzte brauchen Sie nur mit einer Objektvariablen zu belegen, wenn die zu testende Routine sich in einem Formular- oder Klassenmodul befindet.

    Test von Prozeduren aus Standardmodulen

    Für den Test einer öffentlichen Funktion eines Standardmoduls rufen Sie die Methode Performancetest_Standard auf. Diese startet die Zeitmessung, indem sie die Startzeit in der Variablen lngStartzeit speichert. Anschließend wird die erste Funktion in einer Schleife mit der in der Eigenschaft AnzahlDurchgaenge angegebenen Anzahl aufgerufen. Die Differenz der jetzigen Systemzeit und der Startzeit wird in der Variablen mErsteZeit gespeichert.

    Auf die gleiche Weise ermittelt die Methode die Zeit für die Durchläufe der zweiten Funktion und speichert sie in der Variablen mZweiteZeit.

    Nach der Zeitmessung berechnet die Methode noch zwei Werte: die absolute Differenz zwischen den gemessenen Zeiten und die relative Differenz.

    Die ermittelten Werte können anschließend über die Eigenschaften ErsteZeit, ZweiteZeit, AbsoluteDifferenz und RelativeDifferenz ausgelesen werden.

    Test von Prozeduren aus Formular- und Klassenmodulen

    Der Test öffentlicher Prozeduren aus Formular- und Klassenmodulen läuft ähnlich wie der von Prozeduren in Standardmodulen ab. Sie müssen lediglich noch eine Objektvariable mit einem Verweis auf eine Instanz des Formulars oder der Klasse übergeben, in der sich die Prozedur befindet.

    Beachten Sie, dass die zu testenden Prozeduren als öffentlich deklariert sein müssen.

    Anschließend rufen Sie die Methode Performancetest_Objekt auf. Die Ergebnisse werden in den gleichen Eigenschaften zur Verfügung gestellt wie bei der zuvor beschriebenen Methode.

    Die Klasse clsZeitmessung

    Nachfolgend finden Sie das Listing mit der kompletten Klasse clsZeitmessung. Es enthält die Eigenschaften zur Eingabe der Parameter sowie zur Ausgabe der Ergebnisse und die beiden Prozeduren Performancetest_Standard und Performancetest_Objekt.

    Option Compare Database
    Option Explicit

    Private Declare Function QueryPerformanceCounter Lib "kernel32.dll" _
        (ByRef lpPerformanceCount As Currency) As Long
    Private Declare Function QueryPerformanceFrequency Lib "kernel32.dll" _
        (ByRef lpFrequency As Currency) As Long

    Dim curStartzeit As Currency
    Dim mFreq As Currency
    Dim mZeit As Currency
    Dim mErsteProzedur As String
    Dim mZweiteProzedur As String
    Dim mErsteZeit As Currency

    Dim mZweiteZeit As Currency
    Dim mNull As Currency
    Dim mAnzahlDurchgaenge As Long
    Dim mAbsoluteDifferenz As Currency
    Dim mRelativeDifferenz As Currency
    Dim mObjekt As Object

    Private Sub Class_Initialize()
        'Ermitteln der rechnerspezifischen Counter-Frequenz
        QueryPerformanceFrequency mFreq
    End Sub

    Public Property Set Objekt(obj As Object)
        Set mObjekt = obj
    End Property

    Public Property Let ErsteProzedur(strErsteProzedur As String)
        mErsteProzedur = strErsteProzedur
    End Property

    Public Property Let ZweiteProzedur(strZweiteProzedur As String)
        mZweiteProzedur = strZweiteProzedur
    End Property

    Public Property Let AnzahlDurchgaenge(lngAnzahlDurchgaenge As Long)
        mAnzahlDurchgaenge = lngAnzahlDurchgaenge
    End Property

    Public Property Get AbsoluteDifferenz() As Currency
        AbsoluteDifferenz = mAbsoluteDifferenz
    End Property

    Public Property Get RelativeDifferenz() As Currency
        RelativeDifferenz = mRelativeDifferenz
    End Property

    Public Property Get Zeit()
        Zeit = mZeit
    End Property

    Public Property Get ErsteZeit() As Currency
        ErsteZeit = mErsteZeit
    End Property

    Public Property Get ZweiteZeit() As Currency
        ZweiteZeit = mZweiteZeit
    End Property

    Private Sub Starten()
        QueryPerformanceCounter curStartzeit
    End Sub

    Private Sub Stoppen()
        Dim curStop As Currency
        
        QueryPerformanceCounter curStop
        'Umrechnung Counter in Millisekunden über Counterfrequenz
        mZeit = (curStop - curStartzeit) / mFreq * CCur(1000)
    End Sub

    Public Sub Performancetest_Standard()
        Dim l As Long
        Dim mNull As Double

        Starten
        For l = 1 To mAnzahlDurchgaenge
            Eval mErsteProzedur
        Next l
        Stoppen
        mErsteZeit = mZeit - mNull

        Starten
        For l = 1 To mAnzahlDurchgaenge
            Eval mZweiteProzedur
        Next l
        Stoppen
        mZweiteZeit = mZeit - mNull
        
        If mErsteZeit = 0 Or mZweiteZeit = 0 Then
            Debug.Print "###Zeit nicht messbar, AnzahlDurchgaenge erhöhen."
        Else
            mAbsoluteDifferenz = (mZweiteZeit - mErsteZeit)
            mRelativeDifferenz = AbsoluteDifferenz / mZweiteZeit * 100
        End If
    End Sub

    Public Sub Performancetest_Objekt()
        Dim l As Long
        Starten
        For l = 1 To mAnzahlDurchgaenge
            CallByName mObjekt, mErsteProzedur, VbMethod
        Next l
        Stoppen
        mErsteZeit = mZeit
        Starten

        For l = 1 To mAnzahlDurchgaenge
            CallByName mObjekt, mZweiteProzedur, VbMethod
        Next l
        Stoppen
        mZweiteZeit = mZeit
        If mErsteZeit = 0 Or mZweiteZeit = 0 Then
            Debug.Print "###Zeit nicht messbar, AnzahlDurchgaenge erhöhen."
        Else
            mAbsoluteDifferenz = (mZweiteZeit - mErsteZeit)
            mRelativeDifferenz = AbsoluteDifferenz / mZweiteZeit * 100
        End If
    End Sub

    Listing 14.26: Die Klasse clsZeitmessung

    Einsatz der Klasse clsZeitmessung

    Die Methoden und Eigenschaften lassen sich am einfachsten in einer Routine wie im folgenden Listing verwenden. Die Prozedur ZeitMessenStandard deklariert und instanziert ein Objekt des Typs clsZeitmessung.

    Es weist die Anzahl der Durchgänge zu und übergibt die Namen der zu vergleichenden Funktionen. Nach dem Ausführen des Tests mit der Methode Performancetest_ Standard fragt die Routine die ermittelten Werte ab und gibt sie im Testfenster aus.

    Public Sub ZeitMessenStandard(strErsteProzedur As String, _
        strZweiteProzedur As String, lngAnzahlDurchgaenge As Long)
        Dim objZeitmessung As clsZeitmessung
        Set objZeitmessung = New clsZeitmessung
        With objZeitmessung
            .AnzahlDurchgaenge = lngAnzahlDurchgaenge
            .ErsteProzedur = strErsteProzedur & "()"
            .ZweiteProzedur = strZweiteProzedur & "()"
            .Performancetest_Standard
            Debug.Print "Zeit '" & strErsteProzedur & "': " _
                & .ErsteZeit & "ms"
            Debug.Print "Zeit '" & strZweiteProzedur & "': " _
                & .ZweiteZeit & "ms"
            Debug.Print "Absolute Differenz: " & .AbsoluteDifferenz & "ms"
            Debug.Print "Relative Differenz: " _
                & Format(.RelativeDifferenz, "0.00") & "%"
        End With
        Set objZeitmessung = Nothing
    End Sub

    Listing 14.27: Diese Routine verwendet die Methoden und Eigenschaften der Klasse clsZeitmessung

    Bei beiden Funktionen, die Sie weiter oben in Abschnitt »Objektvariablen verwenden, wenn ein Objekt mehr als einmal referenziert wird« kennen gelernt haben, sehen der Aufruf der Prozedur ZeitMessen im Direktfenster und das Ergebnis wie folgt aus:

    ZeitmessenStandard "FormularAuslesenOhneReferenz", _
        "FormularAuslesenMitReferenz", 10000
    Zeit 'FormularAuslesenOhneReferenz': 1360ms
    Zeit 'FormularAuslesenMitReferenz': 1228ms
    Absolute Differenz: -132ms
    Relative Differenz: -9,30%

    Wenn Sie zwei Prozeduren miteinander vergleichen möchten, die sich in einem Formular oder einer Klasse befinden, verwenden Sie die Prozedur ZeitMessenObjekt. Diese Funktion erwartet zusätzlich eine Objektvariable mit der Instanz des Formulars oder der Klasse, in dem sich bzw. in der sich die zu testende Routine befindet.

    Außerdem müssen die zu testenden Routinen mit dem Schlüsselwort Public deklariert sein. Die Prozedur ZeitMessenObjekt liefert genau die gleichen Informationen zurück wie die Prozedur ZeitMessenStandard.

    Public Sub ZeitMessenObjekt(strErsteProzedur As String, _
        strZweiteProzedur As String, lngAnzahlDurchgaenge As Long, obj As Object)
        Dim objZeitmessung As clsZeitmessung
        Set objZeitmessung = New clsZeitmessung
        With objZeitmessung
            Set .Objekt = obj
            .AnzahlDurchgaenge = lngAnzahlDurchgaenge
            .ErsteProzedur = strErsteProzedur
            .ZweiteProzedur = strZweiteProzedur
            .Performancetest_Objekt
            Debug.Print "Zeit '" & strErsteProzedur & "': " & .ErsteZeit & "ms"
            Debug.Print "Zeit '" & strZweiteProzedur & "': " _
                & .ZweiteZeit & "ms"
            Debug.Print "Absolute Differenz: " & .AbsoluteDifferenz & "ms"
            Debug.Print "Relative Differenz: " _
                & Format(.RelativeDifferenz, "0.00") & "%"
        End With
        Set objZeitmessung = Nothing
    End Sub

    Listing 14.28: Prozedur zur Performancemessung von Prozeduren in Formular- und Klassenmodulen

    Ein Beispiel für den Einsatz dieser Prozedur ist ein Performancetest, der typische Formular-Eigenheiten unter die Lupe nimmt. Weiter oben in Abschnitt 14.5.1 unter »Me statt Verweis auf Forms- oder Reports-Auflistung« finden Sie zwei Prozeduren, die ein Textfeld eines Formulars einmal über das Schlüsselwort Me! und einmal über Forms!! referenzieren.

    Der Performancevergleich liefert folgendes Ergebnis:

    Zeitmessenobjekt "FormularbezugMitFormsFormularname", "FormularbezugMitMe", 10000, Forms("frmKontakte")
    Zeit 'FormularbezugMitFormsFormularname': 571ms
    Zeit 'FormularbezugMitMe': 407ms
    Absolute Differenz: -164ms
    Relative Differenz: -40,29%

    Die Variante mit dem Schlüsselwort Me ist also um rund 40 Prozent schneller.

    Komfortable Zeitmessung

    Am einfachsten erledigen Sie die Zeitmessung mit dem Formular aus Abbildung 14.13.

    Abbildung 14.13: Performance vergleichen mit komfortabler Benutzeroberfläche

    Es verwendet die Methoden und Eigenschaften der Klasse clsZeitmessung. Um alles gemeinsam in einer eigenen Datenbank einzusetzen, müssen Sie folgende Objekte importieren:

  • Klasse clsZeitmessung
  • Formular frmPerformance
  • Außerdem müssen Sie einen Verweis auf die Bibliothek Microsoft Visual Basic for Applications Extensibility 5.3 anlegen. Anschließend können Sie loslegen: Wählen Sie das Modul oder das Formular aus, das die zu vergleichenden Routinen enthält, geben Sie die Anzahl der Durchläufe ein, wählen Sie die Testkandidaten aus und klicken Sie auf Test starten. Fertig! Das Formular liefert Ihnen die passenden Ergebnisse.

    Falls Sie damit liebäugeln, dieses Formular in einen Assistenten einzubauen, sollten Sie Folgendes beachten: Sie können Routinen aus fremden Datenbanken (Sie würden ja von der .accda-Datenbank auf die Routinen der aufrufenden .accdb-Datenbank zugreifen) nur mit der Methode Application.Run aufrufen. Und die verfälscht teilweise die Ergebnisse. Das Integrieren der beiden Objekte in die zu testende Datenbank dürfte wesentlich bessere Ergebnisse liefern.

    Die Beispieldatenbank zu diesem Kapitel finden Sie auf der Buch-CD unter \Kap_14\Performance.accdb.

    Nächster Abschnitt:

    15 Objektorientierte Programmierung

    © 2006-2008 André Minhorst Alle Rechte vorbehalten.