Delphi 10.1 Berlin, Reguläre Ausdrücke

Die derzeit aktuelle Version von Delphi, Version “10.1 Berlin”, enthält neben vielen weiteren Verbesserungen auch ein bemerkenswertes Feature, welches noch gar nicht so recht im Fokus der allgemeinen Wahrnehmung stand: Die eingebundene Bibliothek für reguläre Ausdrücke hat ein umfangreiches Update erfahren, welches sowohl den Speicherverbrauch als auch die Geschwindigkeit optimiert.

Was das konkret bedeutet, sehe ich an meinem Projekt “Delphi-Reference“, für das ich regelmäßig die Hilfe-Dateien von Delphi analysiere, um daraus einen Online-Index erstellen zu können. Hierzu extrahiere ich Metadaten aus dem Quelltext der HTML-Hilfeseiten. Die folgende Notation an Metadaten findet sich in allen Seiten wieder – anbei ein Beispiel für das Ereignis “Button.OnClick”:

<!--VCLInfo
parent='TButton' type='event' name='OnClick' ns='Vcl.StdCtrls'
delphi='True' cpp='True' VCLInfo-->

Aus diesem Fragment kann ich alle von mir benötigten Informationen beziehen:

  • Den Namen: name=”OnClick”
  • Den Typ: type=”event”
  • Das übergeordnetes Element: parent=”TButton”
  • Den Namespace (Unit): ns=”Vcl.StdCtrls”
Vorspiel: Kurzer Abriss über reguläre Ausdrücke

Um an diese Informationen heran zu kommen, nutze ich nicht die klassischen String-Funktionen wie “Pos()” oder “Copy()”, sondern reguläre Ausdrücke, die es mir erlauben, ein Suchmuster sehr präzise zu definieren und den Rest dann von den beteiligten Klassen erledigen zu lassen. Bis hierhin liest sich das derart gut, dass man sich offenkundig fragen muss, warum reguläre Ausdrücke nicht viel verbreiteter zum Einsatz kommen. Viele Entwickler haben bestenfalls von regulären Ausdrücken gehört, das Thema dann aber nicht weiter verfolgt. Zum Einen liegt es darin, dass die theoretische Informatik wohl kaum einen spröderen Namen hätte erfinden können: “Sexappeal” vermutet man dahinter eher nicht. Zum Anderen – und das dürfte die weitaus größere Hürde sein – erfordert schon das Lesen regulärer Ausdrücke ein wenig Übung, das Schreiben um so mehr.

Sehen wir uns die Notation der Metadaten erneut an:

<!--VCLInfo
parent='TButton' type='event' name='OnClick' ns='Vcl.StdCtrls'
delphi='True' cpp='True' VCLInfo-->

Wir erkennen feste Elemente wie “parent=”, “name=” und variable Blöcke wie “Button” oder “OnClick”, die je nach Hilfe-Seite unterschiedlich sein werden. Wenn wir die variablen Blöcke mal isolieren, dann könnte man obiges Fragment wie folgt schreiben:

<!--VCLInfo
parent='{irgendwas}' type='{irgendwas}' name='{irgendwas}' ns='{irgendwas}'
delphi='{irgendwas}' cpp='{irgendwas}' VCLInfo-->

Und damit haben wir begonnen, ein Suchmuster zu definieren, wenngleich es in dieser Form noch kein regulärer Ausdruck ist. Dazu müssen wir das “irgendwas” noch näher spezifizieren. Es handelt sich dabei ja stets um Buchstaben. Und davon jeweils mindestens einen, meist sogar mehrere. Die Notation dafür sieht wie folgt aus:

[A-Za-z]+

Mit den eckigen Klammern definieren wir eine Zeichenmenge, darin dann die Groß- und Kleinbuchstaben. Das Pluszeichen am Ende steht für “beliebig viele, aber mindestens einen”. Wenn man dann noch die Ziffern, den Unterstrich und den Punkt hinzunimmt – all das kann man bei Delphi in diesem Kontext finden – erweitert sich der Ausdruck zu:

[A-Za-z0-9\.\_]+

Wenn wir nun dies in unseren obigen Ausdruck einbauen, ergibt sich folgender regulärer Ausdruck, um die Metadaten aus den HTML-Quelltexten zu extrahieren (für die Übersicht umgebrochen in zwei Zeilen):

<!--VCLInfo
parent='([A-Za-z0-9\.\_]+)' type='([A-Za-z]+)' name='([A-Za-z0-9\.\_ ]+)' ns='([A-Za-z0-9\.]+)'
delphi='([A-Za-z]+)' cpp=''([A-Za-z]+)' VCLInfo-->

Die runden Klammern signalisieren, dass wir am konkreten Inhalt der jeweiligen Mengen interessiert sind und diese zurückgeliefert haben möchten.

Beispiel: Konkrete Anwendung in Delphi

Wir können dieses Wissen nun innerhalb von Delphi einsetzen. Hier meine – für dieses Beispiel reduzierte – Routine zum Anwenden des regulären Ausdrucks:

uses
  System.RegularExpressions;
 
function ParseString( const AString : string; out MetaData : TMetaData ) : boolean;
var
  LRegEx : TRegEx;  // ist ein Record, keine Klasse - ein .Free also nicht erforderlich
  LPattern : string;
  LMatch : TMatch;
begin
  LRegEx := TRegEx.Create( '', [] );
  LPattern := '\<\!--VCLInfo parent=''([A-Za-z0-9\.\_]+)'' type=''([A-Za-z]+)'' '+
    'name=''([A-Za-z0-9\.\_ ]+)'' ns=''([A-Za-z0-9\.]+)'' delphi=''([A-Za-z]+)'' '+
    'cpp=''([A-Za-z]+)'' VCLInfo-->';
  LMatch := LRegEx.Match( AString, LPattern );
  if LMatch.Success AND (LMatch.Groups.Count = 7) then
  begin
    MetaData.Parent := LMatch.Groups[1].Value;
    MetaData.Kind :=  LMatch.Groups[2].Value;
    MetaData.Name :=  LMatch.Groups[3].Value;
    MetaData.Namespace :=  LMatch.Groups[4].Value;
    MetaData.IsDelphi := SameText(LMatch.Groups[5].Value, 'TRUE');
 
    result := TRUE;
  end
  else
    result := FALSE;
end;

function ParseString( const AString : string; out MetaData : TMetaData ) : boolean;
var
LRegEx : TRegEx; // ist ein Record, keine Klasse – ein .Free also nicht erforderlich
LPattern : string;
LMatch : TMatch;
begin
LRegEx := TRegEx.Create( ”, [] );
LPattern := ‘\<\!–VCLInfo parent=”([A-Za-z0-9\.\_]+)” type=”([A-Za-z]+)” ‘+
‘name=”([A-Za-z0-9\.\_ ]+)” ns=”([A-Za-z0-9\.]+)” delphi=”([A-Za-z]+)” ‘+
‘cpp=”([A-Za-z]+)” VCLInfo–>’;
LMatch := LRegEx.Match( AString, LPattern );
if LMatch.Success AND (LMatch.Groups.Count = 7) then
begin
MetaData.Parent := LMatch.Groups[1].Value;
MetaData.Kind := LMatch.Groups[2].Value;
MetaData.Name := LMatch.Groups[3].Value;
MetaData.Namespace := LMatch.Groups[4].Value;
MetaData.IsDelphi := SameText(LMatch.Groups[5].Value, ‘TRUE’);

result := TRUE;
end
else
result := FALSE;
end;

Hier erhalten wir also dediziert die oben angesprochenen Metadaten und können sie nach Bedarf weiter verarbeiten.

Auswirkungen in der Praxis

Interessant ist das neue Laufzeitverhalten unter Delphi 10.1 Berlin. Dieser Code hat exakt so auch schon unter Delphi 10 Seattle funktioniert, doch nun ist er allerdings maßgeblich performanter. Anbei ein paar Zahlen:

Der von mir betrachtete Teil der Hilfe von Delphi 10.1 Berlin umfasst 107.699 Dateien mit einem Gesamtumfang von 807 MBytes.

cap

Verarbeitungsgeschwindigkeit in MB/s – größere Werte sind besser


  • Delphi 10 Seattle:
    25.3 MB/s – benötigte Zeit rund 30 Sekunden
  • Delphi 10.1 Berlin:
    52.1 MB/s – benötigte Zeit rund 15 Sekunden

Die gemessene Werte sind natürlich spezifisch für mein System, welches zwischen den Tests neu gestartet wurde, um Einflüsse wie den Cache des Dateisystems weitgehend auszuschliessen.
Dennoch ist die relative Steigerung der Performance bemerkenswert.

0
Daniel Wolf

About the Author:

Daniel Wolf ist als Software-Architekt im Miniatur Wunderland Hamburg tätig, war zuvor viele Jahre als technischer Berater tätig und spricht regelmäßig auf Konferenzen. Er betreibt das Forum "Delphi-PRAXiS". » Mehr Details zur Person
  Verwandte Blog-Einträge