Session D-RUSH

Rushmore-Optimierung

Peter Herzog
ProLib GmbH

Einleitung

Als die Programmierer von Fox Software Ihre überragende Technik entwickelten, suchte man nach einem griffigen Namen für diese Neuerung. An jenem Abend lief gerade der Hitchcock-Film “Nord-Nordwest” im Fernsehen, bei dem auch der “Mount Rushmore” zu sehen war. (jener berühmte Berg mit den vier Präsidentenköpfen). Der Name Rushmore hörte sich in bezug auf Geschwindigkeit sehr gut an und schon war die Rushmoretechnologie geboren. (Es gibt auch Lästerer, die sich fragen, wie wohl der Name ausgefallen wäre, wenn Dr.Dave damals gerade den Thriller “Die Fliegen” gesehen hätte....)

Im Grunde genommen war diese Technologie nichts Neues. Lediglich die Realisierung auf einer INTEL-Plattform war das Entscheidende und somit konnte diese Technik patentiert werden. Dies war auch einer der Hauptgründe, warum Microsoft das Produkt FoxPro im Jahre 1992 für ca 79 Millionen $ aufkaufte.

Was ist Rushmore

Rushmore ist eine patentierte Technik zum optimierten Zugriff auf Indexdateien anstelle der Echtdaten.

Wie funktioniert Rushmore

Auch FoxPro kocht nur mit Wasser. Das bedeutet, daß grundsätzlich immer ein Index vorhanden sein muß, um die sagenhafte Geschwindigkeit zu erreichen. Der Aufbau eines solchen Index ist nun das “Gewusst Wie”, um Rushmore funktionieren zu lassen.

Jeder Index ist in einer CDX Datei angelegt. Es wird jedoch immer nur eine CDX Datei verwendet, um alle Indextags aufzunehmen. Die Indexe sind in komprimierter Form in einem optimierbaren B-Baum abgelegt. Allein schon die komprimierte Form verringert den Datenzugriff auf die Festplatte, was ja immer die große Bremse darstellt.

Wenn die Rushmoreengine einen bestimmten Indexausdruck sucht, baut FoxPro im Speicher eine Bitmap auf. Jeder Datensatz wird als einzelnes Bit abgebildet. Eine solche Bitmap wird für jeden Indextag aufgebaut, der in die Rushmoreoptimierung aufgenommen wird.

Nun sucht FoxPro im Index (der als Binärer Baum aufgebaut ist) nach den einzelnen Datensätzen und setzt jeweils das richtige Bit im Speicher, wenn ein Treffer gemacht wird. Dies wird für jeden benötigten Indextag gemacht.

Nun werden die Bitmaps miteinander verglichen und nur die Datensätze genommen, wo in jeder Bitmap ein Treffer gelandet wurde.

Bildlich kann man sich eine solche Bitmap wie ein Stück Papier vorstellen, wobei ein Treffer einem Nadelstich gleichkommt. Alle Papierblätter werden nun übereinander gelegt und überall, wo das Licht durchscheint, ist ein Datensatz gefunden worden, der nun verarbeitet oder angezeigt werden kann. Dies ist eigentlich der ganze Trick an der Sache.

Der Bitmap-Aufbau hat natürlich zur Folge, daß Speicherplatz für die Bitmaps benötigt wird. Für 520000 Datensätze sind ca. 64 KB notwendig. Dies ist bei den heutigen Rechnern kein Problem mehr. Zur Zeit von DOS und der Standardversion von FoxPro 2.0 war dies jedoch die Grenze, bis zu der Rushmore funktioniert, da unter DOS (ohne 386er Modus) nur 64kb große durchgängige Bitmaps aufgebaut werden konnten.

Wo wird Rushmore eingesetzt

Rushmore wird automatisch bei allen Befehlen verwendet, die eine FOR-Klausel haben (Ausnahme INDEX ON .... FOR Ausdruck). Außerdem findet man Rushmore bei allen SQL-Select Befehlen und beim SET FILTER TO.

Beispiele:

SET FILTER TO ....
SELECT * FROM Datei WHERE...
LOCATE FOR ....
BROWSE FOR ....
REPORT FORM FOR ....
REPLACE FOR ....
DELETE FOR ....
usw.

Die Regeln, um Rushmore loslegen zu lassen

Rushmore ist davon abhängig, wie die Indizes aufgebaut sind.

  1. Für die wichtigsten Felder einen Indextag anlegen. Ohne Index muß die Rushmoreengine sequentiell durch den Datenbestand gehen, um den richtigen Datensatz zu finden.
  2. Nur Indices ohne FOR-Klausel können optimiert durchsucht werden.
    FoxPro muß sich eine Bitmap aller Datensätze anlegen. Sobald ein vorgefilterter Index vorhanden ist, kann dieser Index nicht zur Optimierung herangezogen werden, da die FOR-Klausel die Datenmenge filtert. Alle Indexausdrücke, die nicht in die FOR-Bedingung passen, werden erst gar nicht im Index aufgenommen. Anmerkung: Der “INDEX ON” Befehl hat ebenfalls eine FOR-Klausel. Aber verwechseln Sie diese nicht mit der “Rushmore FOR-Klausel”
  3. Jeder Indexausdruck muß identisch in der Abfrage angeführt werden.
    Beispiel:
    Ein Index wurde mit folgendem Befehl angelegt:
                   INDEX ON UPPER(NAME) TAG UNAME
    dann muß die Abfrage auch auf
                   BROWSE FOR UPPER(NAME)=”MAIER”
    lauten. Ansonsten wird die Rushmoreengine den Index nicht verwenden.
    Es ist egal, ob der abzufragende Wert rechts vom Vergleichsoperator dann geuppert ist oder nicht. Es wird immer der Ausdruck der Abfrage mit dem Ausdruck im Index verglichen.
  4. SET DELETED ON / OFF beachten. Wenn in FoxPro der Befehl SET DELETED ON gesetzt wird, dann werden alle gelöschten Datensätze (alle als gelöscht gekennzeichneten) nicht angezeigt.

    Das Kennzeichen, ob ein Datensatz als gelöscht gekennzeichnet ist, findet FoxPro bei jedem Datensatz an der ersten Byteposition.

    Dies bedeutet auch, daß FoxPro bei jedem Datensatz nachsehen muß, ob der Datensatz gelöscht wurde oder nicht. Egal ob gelöschte Datensätze vorhanden sind oder nicht. Dies ist der am meisten gemachte Fehler bei Neueinsteigern. Dieser Irrtum ist jedoch auch bei “alten Hasen” zu finden, die schon jahrelang mit FoxPro arbeiten. Sie können sich jedoch auf einfachste Weise abhelfen, wenn Sie einen Index auf das Delete-Flag legen, indem Sie einfach den Befehl INDEX ON DELETED() TAG _gel anwenden. (_gel ist ein x-beliebiger Tagname). Beim SET DELETED ON wird intern immer bei jeder Abfrage ein FOR NOT DELETED() angehängt. Wenn nun ein Index auf DELETED() gefunden wird, so wird dieser zur Abfrage herangezogen. FoxPro braucht nicht mehr auf der Festplatte nachsehen und schon haben wir wieder die volle Geschwindigkeit.
  5. Die SET COLLATE Einstellung. Seit FoxPro für Windows haben wir die Möglichkeit, verschiedene Sortierungen im Index zu verwenden. Dies erreichen Sie, indem Sie vor dem Anlegen des Index die Collatesequenz einstellen. (SET COLLATE TO “German” oder SET COLLATE TO “General”)

    Der Index wird dann in der Telefonbuch- oder Dudensortierung angelegt. Wenn nun jedoch im Programm nicht explizit auf diese Collatesequenz umgestellt wird, wird FoxPro den Index nicht zur Optimierung heranziehen.

    Kontrollieren Sie also immer die Collatesequenz, die aktuell eingestellt ist (z.B mittels ? SET(“Collate”) ), mit der Collatesequenz der einzelnen Indextags. (DISPLAY STATUS bei geöffneter Datei oder IDXCOLLATE())

    Bitte beachten Sie, daß ein Index, welcher nicht mit der Collatesequenz “MACHINE” angelegt wurde, nie 100%ig optimierbar ist. Dies liegt daran, daß alle anderen Sortiersequenzen sehr aufwendige Routinen verwenden, um  einen Buchstaben an der richtigen Stelle einzusortieren. FoxPro kann z.B. in den Sortierregeln Ausnahmen definieren, die ein Zeichen je nach den nachfolgenden max. vier Zeichen an ganz unterschiedlichen Stellen einsortiert. Ein Suchen in einem solchen Index kann dadurch nicht so schnell sein, wie ein Suchen in einem “MACHINE” Index, der nur ein Byte pro Buchstabe verwendet. “MACHINE” verwendet die ganz normale ANSI-Sortierung. Das bedeutet, daß Umlaute der deutschen Sprache immer nach den normalen Buchstaben einsortiert werden.

    Wir stehen nun natürlich vor dem Problem, gewisse Daten in ordentlich sortierter Reihenfolge anzeigen zu lassen, andererseits wollen wir nicht auf die Geschwindigkeit beim Suchen verzichten.

    Dazu gibt es wieder einen kleinen Trick, der anscheinend auch nicht überall bekannt zu sein scheint. FoxPro ist es generell egal, wie viele Indextags im Indexfile angelegt werden. Also können Sie auch verschiedene Indextags auf den selben Ausdruck anlegen. Den einen mit der Collatesequenz “MACHINE”, um 100% bei der Rushmoreoptimierung herauszuholen und einen anderen auf “GENERAL” oder “GERMAN”, um die Umlaute richtig zu sortieren. Sie müssen im Programm selbst nur auf die richtige Sequenz umschalten um zum gewünschten Ziel zu kommen. Ich empfehle Ihnen, generell mit der “MACHINE” Sequenz zu arbeiten und nur die wirklich benötigten Felder mit einer anderen Sequenz anzulegen. Überlegen Sie, wo eine deutsche Sortierung in Wirklichkeit benötigt wird. Mit wenigen Ausnahmen sind dies ein Matchcode, ein Name und ein Ort. Eine deutsche Sortierung bei der Postleitzahl oder bei der Kundennummer ist sinnlos.
  6. Abfragen auf FELD = .NULL. gehen grundsätzlich schief. Da .NULL. gleichbedeutend mit “Ich weiss es nicht” ist, kann so ein Wert nie zu vernünftigen Ergebnissen führen. Verwenden Sie anstelle dessen die ISNULL() Funktion, die ein normales logisches Ergebnis liefert.. Vergessen Sie aber nicht, daß ein Index angelegt sein muß, um wiederum die volle Geschwindigkeit zu erlangen.
  7. Abfragen eines leeren Feldes
    Normalerweise wird nur das Datumsfeld als Indextag angelegt. Die Abfragen lauten aber vielfach auf FOR EMPTY(Feld). Abhilfe bekommen Sie dadurch, daß Sie auf Feld = ”” bzw bei Datumswerten auf Feld = {} abfragen.
  8. Eine gesetzte Sortierreihenfolge hindert Rushmore, sich voll zu entfalten. Wenn mit SET ORDER ein bestimmter Index gesetzt wird, so zwingen Sie Rushmore in genau dieser Reihenfolge vorzugehen. Dies muß jedoch nicht immer der optimale Weg sein, um ans Ziel zu gelangen. Also schalten Sie, wenn möglich immer die Order mit “SET ORDER TO” ab.

Das SKIP - Problem

Ein oft beobachtetes Phänomen ist die entsetzliche Geschwindigkeit der Befehle SKIP, GO TOP und GO BOTTOM, sobald ein Filter eingesetzt wird. Dies liegt daran, daß diese Befehle keine FOR-Klausel besitzen.

Der negative Effekt tritt dann auf, wenn aus einer großen Anzahl von Datensätzen eine sehr kleine Menge an Daten herausgefiltert werden. Wenn nun durch die verbleibende Menge “geSKIPed” wird, so wird leider keine Rushmore Unterstützung eingesetzt und der nächste Datensatz wird sequentiell gesucht.

Beachten Sie, daß bereits ein SET DELETED ON einen solchen Filter setzt.

Um das Problem zu umschiffen, ist ein kleiner Trick notwendig. Als erstes benötigen wir einen Befehl, der den richtigen Datensatz findet und außerdem durch Rushmore unterstützt wird. Die Lösung ist der LOCATE-Befehl in Verbindung mit einem gesetzten Index.

Folgendes Szenario: Wir haben eine Tabelle mit Auftragsnummern. Die Auftragsnummer selbst ist unser Schlüsselbegriff “Aufnr”.

Als erstes muß die Order mit “SET ORDER TO Aufnr” eingestellt werden. Wenn nun der nächste Datensatz gesucht werden soll, so muß nur ein Index gesucht werden, der größer als der aktuelle ist.

Lnaufnr = AUFTRAG.Aufnr
LOCATE FOR AUFTRAG.Aufnr > Lnaufnr

Und schon haben wir den nächsten Datensatz gefunden. Dies ist also der Ersatz für den SKIP.

Nun fehlt noch ein Ersatz für das SKIP –1. Dies ist ebenfalls ein leichtes, wenn alleine nur die Sortierreihenfolge umgedreht wird. Man kann dies durch ein einfaches SET ORDER TO (ORDER()) DESC erledigen. Wenn Sie wieder auf “normal” umschalten wollen, genügt nun ein SET ORDER TO (ORDER()) ASCE.

GO TOP und GO BOTTOM

Ein ähnliches Problem wie beim SKIP haben wir bei den Befehlen GO TOP und GO BOTTOM, die ebenfalls ohne Rushmore auskommen müssen. Genau so, wie beim SKIP können wir jedoch auch hier den LOCATE-Befehl verwenden.

Durch ein LOCATE FOR .T. wird durch Rushmore veranlaßt, daß der erste Datensatz gesucht wird, bei der die FOR-Bedingung ein .T. zurückliefert. Da dies nun schon beim ersten Datensatz ist, steht der Datensatzanzeiger beim ersten Datensatz. Ein wunderbarer Ersatz für GO TOP.

Anmerkung: LOCATE hat als Default bereits ein FOR .T. eingestellt, wodurch ein einfaches LOCATE ohne weitere Angaben genügt.

Es fehlt nun noch der Ersatz des GO BOTTOM Befehls. Leider funktioniert dies nicht mit LOCATE FOR .F. . Dies würde zwar den ersten Datensatz suchen, der nicht in die gefilterte Ansicht paßt, jedoch dauert dies nun wieder länger, da dieser Datensatz gesucht werden muß.

Mit SET ORDER TO (ORDER()) DESC kann die Reihenfolge wieder umgedreht werden. Nun ist ein LOCATE FOR .T. wieder erfolgreich, da in der umgekehrten Reihenfolge der erste gesucht wird, was automatisch den letzten Datensatz darstellt.

Der USE – Befehl

Wenn eine Datei geöffnet wird, achtet FoxPro bereits auf die Einstellung des SET DELETED Befehls. Wenn am Beginn sehr viele gelöschte Datensätze vorhanden sind, so muß sich also selbst der USE - Befehl bis zum ersten Datensatz vorkämpfen um seine internen Buffer füllen zu können.

Wenn Sie den Effekt bemerken, dann schalten Sie vor dem USE – Befehl oder vor dem DO FORM Befehl das SET DELETED auf OFF.

Das “Grid – Problem”

Mit der Einführung von Visual FoxPro 3.0, wurde ein neues Element eingeführt, das dem guten alten BROWSE sehr ähnlich ist, das GRID – Control.

Leider werden alle Controls, die als grafische Elemente instanziiert werden können nicht durch Rushmore unterstützt, so daß ebenfalls ein “SKIP – Effekt” eintritt und die Geschwindigkeit bei gefilterten Tabellen sehr verlangsamt werden kann.

Sie haben grundsätzlich zwei Möglichkeiten, um das Problem zu bewältigen. Sie können entweder zuerst einen Cursor, mit SELECT ... INTO CURSOR erstellen, oder die Datensätze mit SET KEY TO eingrenzen.

Der SET KEY TO ... (RANGE) Befehl

Dieser Befehl wurde uns erst seit der FoxPro für Windows Version 2.6 zur Verfügung gestellt und wurde zur Kompatibilität zu dBase 4 eingebaut.

Für diesen Befehl ist es unbedingt notwendig, daß eine Sortierreihenfolge mit “SET ORDER TO ...“ gesetzt wurde.

Nun kann der Index mit SET KEY TO eingeschränkt werden. Dies funktioniert so hervorragend schnell, daß sogar ein GO TOP und ein GO BOTTOM ohne Probleme funktioniert.

Wenn Sie sich einen Index geschickt anlegen, können Sie nahezu jedes Rushmore – Problem umgehen und haben wieder die notwendige Geschwindigkeit.

Dies ist auch die Lösung des “GRID – Problems”.

SELECT – SQL

Einer der gewaltigsten Befehle unter FoxPro ist der SQL – Befehl SELECT, welcher seit der Version 5.0 auf den ANSI92 – Standard gebracht wurde. Jetzt sind auch JOINS verfügbar, welche dem SELECT – Befehl ermöglichen alle möglichen Abfragen aus einer oder mehreren Tabellen vorzunehmen.

Eine Besonderheit hat der SELECT – Befehl schon immer vorzuzeigen, da sich der Befehl nicht um geöffnete Dateien gekümmert hat und eventuell Dateien selbständig öffnen konnte. Auch ein vorher gesetzter SET FILTER Befehl wurde ignoriert und die Abfrage wurde nur auf die WHERE – Klausel beschränkt.

Seit der Version 5.0 ist sogar die SET DELETED Einstellung außer Kraft gesetzt, was jedoch in Insiderkreisen noch diskutiert wird, ob es sich dabei um ein Feature oder einen Bug handelt. Am sichersten ist es, die Abfrage auf Deleted() mit in die Where – Klausel aufzunehmen.

Ansonsten wird der SELECT – Befehl komplett von Rushmore unterstützt.

SYS(3054,x)

Komplett neu seit der Version 5.0 ist der SYS(3054,x). Wird der Parameter 1 mitgegeben, so wird eine Anzeige eingeschaltet, die angibt, ob eine Rushmoreunterstützte Abfrage optimierbar, teilweise optimierbar oder nicht optimierbar war.

Dies ist ein ideales Werkzeug, um seine eigene Programmierung zu überprüfen und festzustellen, ob man nicht noch ein Quentchen Geschwindigkeit aus seinen Abfragen herausholen kann.

Die undokumentierte Einstellung SYS(3054,11) schaltet auf eine Anzeige um, welche auch SELECT – Abfragen in Verbindung mit einer JOIN Anweisung anzeigen kann.

Auch für den FoxPro 2.6 Programmierer ist dies ebenfalls ein wunderbares Tool, um herauszufinden, ob alle Abfragen auch optimierbar ablaufen. Voraussetzung ist natürlich ein installiertes VFP 5.0.

Wenn Sie eine voll optimierte Abfrage starten und trotzdem eine längere Wartezeit  erleben müssen, so sollten Sie einmal Ihren Index neu aufbauen, da sicherlich dort ein kleiner Fehler enthalten sein könnte.

Mit SYS(3054,0) schalten Sie die Anzeige wieder ab.

1 zu 1 Relationen zwischen zwei Tabellen

Sollten Sie einmal eine Relation zwischen zwei Tabellen benötigen, die 100% parallel laufen müssen und ohne Index arbeiten wollen, so gibt es einen einfachen Trick, der absolut “Rushmorefrei” arbeitet.

Normalerweise verwendet man für Abhängigkeiten zwischen Tabellen die SET RELATION Einstellung. Dafür ist ein Index notwendig, da ansonsten für einen Datensatz der Vatertabelle alle Datensätze der Kindtabelle durchsucht wird.

Wenn allerdings eine Relation auf die RECNO() Funktion gemacht wird, so wird FoxPro beide Tabellen absolut sicher und sagenhaft schnell miteinander synchronisieren. Dabei aber beachten, daß auf der Zieltabelle (INTO ...) wirklich kein Index gesetzt ist.

z.B.: SET RELATION TO recno() INTO Places ADDITIVE

Bei welchen Abfragen wird Rushmore nicht unterstützt.

Bei jeder Abfrage haben wir die Möglichkeit einen sogenannten Scope mit anzugeben. Folgende Scopes sind möglich:

ALL verwendet alle Datensätze zur Abfrage.
REST     verwendet die restlichen (ab dem aktuellen Datensatz) zur Abfrage.
NEXT      nimmt nur die nächsten x Datensätze.
RECORD x  nimmt nur den angegeben Datensatz.

Die Scopes ALL und REST werden von Rushmore unterstützt. NEXT und RECORD werden nicht unterstützt.

Generell werden bei Abfragen Vergleichsoperatoren verwendet. Davon sind folgende “Rushmore – optimiert”:

< Kleiner als
> Größer als
=    Gleich
<= Kleiner gleich
>= Größer gleich
<> Ungleich
#   Ungleich
== Genau gleich
!= Ungleich
ISNULL()  Ist .NULL.
BETWEEN()  Zwischen
INLIST()  In einer Liste von ... enthalten

Gerell nicht optimierbar sind folgende Abfragen:

ISBLANK() Ist Blank
EMPTY()  Ist leer

ISBLANK können Sie durch die Abfrage Feld = ‘ ‘ (mit einem Leerzeichen dazwischen) ersetzen.

EMPTY können Sie ebenfalls durch Feld = ‘ ‘,  Feld = 0 oder Feld = {} ersetzen.

Nun wünsche ich Ihnen noch die schnellsten Abfragen, die Sie jemals gemacht haben und hoffe, Sie haben durch diesen Artikel profitiert.