Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Mehr Performance für mein erstes Delphiprogramm


2cHH - So 27.08.06 18:53
Titel: Mehr Performance für mein erstes Delphiprogramm
Hi,

mittlerweile habe ich mich mal versucht etwas mit der IDE vertraut zu machen. Weil ich sowieso schon länger vorhatte, ein einfaches Programm zu schreiben, mit dem man mehrzeilige Textersetzungen vornehmen kann, ohne sich erst mit RegEx oder einem Macrorecorder beschäftigen zu müssen, ist das dabei herausgekommen:

[url=http://img135.imageshack.us/img135/9097/1apptr9.png]user defined image[/URL]



Funktioniert auch, geht aber bei Texten mit über 50.000 Zeichen zu sehr in die Knie. Wie kann ich das Programm (oder besser die eine Zeile ;) ) schneller machen?


Delphi-Quelltext
1:
2:
3:
RichEditSource.Text := AnsiReplaceStr(RichEditSource.Text,
                                      RichEditSearch.Text,
                                      RichEditReplace.Text);


Das hier habe ich schon versucht, macht aber scheinbar keinen allzugrossen Unterschied:


Delphi-Quelltext
1:
2:
3:
4:
cont := RichEditSource.Text;
RichEditSource.Text := AnsiReplaceStr(cont,
                                      RichEditSearch.Text,
                                      RichEditReplace.Text);


Welche Möglichkeiten gibt es, um die Performance zu steigern?


Moderiert von user profile iconTino: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Mo 28.08.2006 um 08:39


BenBE - So 27.08.06 19:27

Das ist ein Systematischer Fehler in der VCL, da Du gleich zwei Flaschenhälse in der String-Replace-Routne drin hast.

Das Beste für Deinen Fall wäre, die StringReplace-Funktion komplett selber zu schreiben, was gar nicht so schwer ist.

Ich knn leider den Quelltext der beiden Routinen in der VCL nicht als Referenz die Flaschenhälse aufzeigen, da ich den auf nem anderen Rechner hab, grob soviel zu den Fehlern: Die VCL alloziiert Speicher in Schleifen, immer wenn eine neue Ersetzung vorgenommen wurde ... Ferner werden jedes mal 3 Kopien der Strings gehandhabt, die vollständig umkopiert werden müssen, was aber vollkommener Overhead ist ...

Eine optimierte Version sieht folgendermaßen aus:

  1. Berechne den maximal anfallenden Speicher, wenn das Maximum an Ersetzungen vorgenommen werden muss vs. Stringlänge ohne Ersetzung und nehme den größeren Wert (MaxReplaceSize := Max((OrigStringLen div OrigNeedleSize) * ReplacedStringLen + OrigStringLen mod OrigNeedleSize, OrigStringLen);)
  2. Reserviere einen String entsprechender Größe (SetLength(Result, MaxReplaceSize);)
  3. Durchsuche den OriginalString zeichenweise nach dem Suchmusterr:

    1. Wenn das Suchmuster nicht mit dem aktuellen Zeichen übereinstimmt, wird das aktuelle Zeichen in den Ausgabestring kopiert.
    2. Stimmt das erste Zeichen mit dem des Sucmusters überein, merkt man sich die aktuelle Position und prüft auf Übereinstimmung
      • Bei Übereinstimmung schreibt man den Ersetzungsstring in die Ausgabe
      • Bei nichtübereinstimmung kopiert man soviele Zeichen in die Ausgabe, wie man zur Feststellung der Abweichung vom Suchmuster gebraucht hat


  4. Anpassen des Result-Strings mit SetLength um überflüssige Zeichen abzuschneiden (SetLength(Reesult, ActualSize);).


Ich denk, die Frage ist aber in ASM\Opti besser aufgehoben ...


AndyB - So 27.08.06 20:14

user profile iconBenBE hat folgendes geschrieben:
Das ist ein Systematischer Fehler in der VCL

Du meinst wohl RTL und nicht VCL. Und auch in der RTL ist es "nur" der Speichermanager, der ab Delphi 2006 durch einen neuen ersetzt wurde, der dieses Verhalten nicht mehr so extrem auslebt.

Zitat:
Die VCL alloziiert Speicher in Schleifen

Hier: "VCL" durch "AnsiStringReplace" ersetzen.


(die VCL ist nicht für alles der Schuldige :lol: )


2cHH - So 27.08.06 20:25

AnsiReplaceStr ist also der Schludige, thnx.


Horst_H - Mo 28.08.06 11:08

Hallo,

hier : http://fastcode.sourceforge.net/
gibt fuer viele Funktionen eine zum erhebliche schnellere Ersetzung.
Insbesondere ansistringreplace http://fastcode.sourceforge.net/challenge_content/AnsiStringReplace.html bis zu 17-fach= 1700%

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
Function       Name CPU   Presler Northwood Yonah Dothan AMD64 AMD64 X2 Total
StringReplace_JOH_IA32_4      1034  1010  1000  1000  1000  1000  6044
StringReplace_JOH_IA32_3      1000  1000  1104  1105  1049  1066  6324
StringReplace_JOH_PAS_4       1210  1202  1020  1029  1143  1140  6744
StringReplace_JOH_PAS_3       1316  1278  1105  1105  1236  1256  7296
StringReplace_EG_MMX_         1376  1462  1053  1070  1165  1187  7313
StringReplace_SHA_IA32_1      2183  2236  1662  1709  1548  1618  10956
StringReplace_EWC_IA32_2      2202  2182  1726  1733  1668  1682  11193
StringReplace_DKC_SSE2_19     2243  2386  1759  1770  1859  1903  11920
StringReplace_DKC_SSE2_20     2255  2440  1759  1775  1864  1905  11998
StringReplace_DKC_SSE_10      2366  2420  1778  1792  1885  1903  12144
StringReplace_DKC_SSE_9       2364  2400  1789  1790  1921  1957  12221
StringReplace_DKC_IA32_15     2400  2364  1851  1861  1918  1946  12340
StringReplace_DKC_IA32_16     2410  2378  1852  1865  1923  1955  12383
StringReplace_DKC_MMX_9       2417  2414  1846  1851  1927  1971  12426
StringReplace_DKC_MMX_10      2427  2494  1836  1857  1934  1976  12524
StringReplace_DKC_IA32_14     2460  2420  1869  1882  1962  1997  12590
StringReplace_DKC_MMX_11      2487  2688  1824  1827  1915  1948  12689
StringReplace_DKC_Pas_5       2511  2536  2055  2059  1900  1924  12985
StringReplace_DKC_Pas_4       3660  4112  2989  3011  2523  2784  19079
StringReplace_HV_IA32_1       8224  8096  5904  6128  5533  5734  39619
StringReplace_REF_PAS_1       8188  8598  5892  6044  6076  6580  41378
StringReplace_RTL_PAS_1      17376  16298  15058  15722  11217  11501  87172


Gruss Horst


2cHH - Mo 28.08.06 13:44

user profile iconHorst_H hat folgendes geschrieben:
hier : http://fastcode.sourceforge.net/
gibt fuer viele Funktionen eine zum erhebliche schnellere Ersetzung.


Hi,

thnx, aber ich hatte gestern abend schon selber eine Funktion geschrieben. Zuerst so wie von BenBE vorgeschlagen, dann habe ich noch versucht, die Funktion zu optimieren, so dass nicht immer ein Zeichen nach dem anderen in den Puffer kopiert wird, sondern stattdessen eine Variable hochgezählt, anhand der dann alle (bisherigen) Zeichen auf einmal kopiert werden können:



C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
char* strReplace(char* sCont, char* sSearch, char* sRepl)
    {
    int nCont = strlen(sCont);
    int nSearch = strlen(sSearch);
    int nRepl = strlen(sRepl);
    ;
    int nBuff = (nCont / nSearch) * nRepl 
              + (nCont % nSearch);
    nBuff = (nBuff < nCont) ? nCont : nBuff;
    char* sBuff = malloc(sizeof(char) * nBuff);
    *sBuff = '\0';
    ;
    int nToCopy = 0;
    while(*sCont)
        {
        if(*sCont == *sSearch)
            {
            char* pCont = sCont;
            char* pSearch = sSearch;
            while((*++pCont == *++pSearch) &&
                  (*pCont) && (*pSearch));
            ;
            if(!*pSearch)
                {
                strncat(sBuff, (sCont - nToCopy), nToCopy);
                nToCopy = 0;
                strcat(sBuff, sRepl);
                sCont += nSearch;
                continue;
                }
            }
        sCont++;
        nToCopy++;
        }
    strncat(sBuff, (sCont - nToCopy), nToCopy);
    return sBuff;
    }


Soweit ich es getestet habe, scheint die Funktion ok zu sein.


Die Frage ist jetzt, ob ich bei DevCpp einfach ein DLL-Projekt anlegen kann, die Funktion compilieren und dann die Funktion in Delphi einbinden kann?

Gruss aus Hamburg

Moderiert von user profile iconChristian S.: Code- durch C#-Tags ersetzt


mkinzler - Mo 28.08.06 14:04

Zitat:

Die Frage ist jetzt, ob ich bei DevCpp einfach ein DLL-Projekt anlegen kann, die Funktion compilieren und dann die Funktion in Delphi einbinden kann?
Sollte gehen.


BenBE - Mo 28.08.06 14:14

@user profile icon2cHH: Da Du mit PChars arbeitest, solltest Du ein zusätzliches Byte am Ende für den Nullterminator mitreservieren. Inwiefern strncat dies automatisch macht, weiß ich aber ATM nicht. Bei malloc muss er mit enthalten sein (Macht SetLength intern, weshalb ich das nicht mit aufgeführt hab).

Weiterhin scheint es mir so, als ob du Potetielle Buffer Overruns in deiner Routine hast, wenn das Suchmuster am Ende des Strings gefunden wird.


2cHH - Mo 28.08.06 14:36

user profile iconBenBE hat folgendes geschrieben:
@user profile icon2cHH: Da Du mit PChars arbeitest,


Sorry, da kann ich nicht ganz folgen.

Wieso PChar, in C gibt es imho nur eine Sorte Chars, von Unicode mal abgesehen?



user profile iconBenBE hat folgendes geschrieben:
@user profile icon2cHHein zusätzliches Byte am Ende für den Nullterminator mitreservieren....
Weiterhin scheint es mir so, als ob du Potetielle Buffer Overruns in deiner Routine hast, wenn das Suchmuster am Ende des Strings gefunden wird.


strcat passt schon, das geht ja alles in den Buffer, der ist gross genug, Leerchars oder fehlende Chars gibt es da auch nicht.

Potentielle Buffer Overruns - Wie soll das genau passieren? das würde ich gerne verhindern.




Hehe, vorhin habe ich mich nicht genau ausgedrückt, ich exporiere die DLL, soweit ok,
aber was mache ich in Delpi, gibt es da ein DLL-Import Tutorial?

Oder ist Delphi so performant, dass es zu überlegen wäre, den Code auf Delphi zu portieren?


Gruss aus Hamburg


mkinzler - Mo 28.08.06 14:42

Zitat:
Wieso PChar, in C gibt es imho nur eine Sorte Chars, von Unicode mal abgesehen?
Dieser Typ heißt in Delphi PChar ( Pointer to Char)


BenBE - Mo 28.08.06 14:51

user profile icon2cHH hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:
@user profile icon2cHH: Da Du mit PChars arbeitest,


Sorry, da kann ich nicht ganz folgen.

Wieso PChar, in C gibt es imho nur eine Sorte Chars, von Unicode mal abgesehen?

Jup, ABER ^^ Char* ist wird in Delphi als Zeiger auf ein Zeichen (Pointer to Char --> PChar) übersetzt. Und diese umfassen neben den Daten am Ende ein zusätzliches Byte zur Nullterminierung, welches Du zusätzlich mit reservieren musst.

user profile icon2cHH hat folgendes geschrieben:
user profile iconBenBE hat folgendes geschrieben:
ein zusätzliches Byte am Ende für den Nullterminator mitreservieren....
Weiterhin scheint es mir so, als ob du Potetielle Buffer Overruns in deiner Routine hast, wenn das Suchmuster am Ende des Strings gefunden wird.


strcat passt schon, das geht ja alles in den Buffer, der ist gross genug, Leerchars oder fehlende Chars gibt es da auch nicht.

Potentielle Buffer Overruns - Wie soll das genau passieren? das würde ich gerne verhindern.


Leerchars und Fehlende Chars mein ich auch nicht. Ich hab beim Überfliegen des Sources einen Teil deiner Abbruchbedingung übersehen, der EndOfString korrekt prüft. Würde dieser Teil fehlen, könnte es passieren, dass bei einem Match am Ende des String Daten hinter den zu vergleichenden Strings analysiert werden würden. Die Prüfung ist aber gegeben (bei näherem Hinsehen).

user profile icon2cHH hat folgendes geschrieben:
Hehe, vorhin habe ich mich nicht genau ausgedrückt, ich exporiere die DLL, soweit ok,
aber was mache ich in Delpi, gibt es da ein DLL-Import Tutorial?

Oder ist Delphi so performant, dass es zu überlegen wäre, den Code auf Delphi zu portieren?

Gruss aus Hamburg

Wenn man mit Delphi korrekt programmiert, kann es auch sehr performant sein ... Im Normalfall geht es aber mit C++ einen Tick schneller ...

Zum Import:

Delphi-Quelltext
1:
Function DeinReplaceStr(sCont: PChar; sSearch: PChar; sReplace: PChar): PChar; external 'Deine.dll' name 'strReplace';                    


2cHH - Mo 28.08.06 14:57

user profile iconBenBE hat folgendes geschrieben:
diese umfassen neben den Daten am Ende ein zusätzliches Byte zur Nullterminierung, welches Du zusätzlich mit reservieren musst.


Übergeben wird ja ein Nullterminierter String (sBuff). Wenn ich es richtig verstanden habe,
hätte man die Problematik, wenn man einzelne Chars übergeben würde, nicht?


user profile iconBenBE hat folgendes geschrieben:
Zum Import:

Delphi-Quelltext
1:
Function DeinReplaceStr(sCont: PChar; sSearch: PChar; sReplace: PChar): PChar; external 'Deine.dll' name 'strReplace';                    



Thnx, dann werde ich mal mein Glück versuchen.... ;)

Gruss aus Hamburg


2cHH - Mo 28.08.06 16:44

Hi,

die DLL habe ich jetzt fertig:

[url=http://img241.imageshack.us/img241/371/replacedllig1.png]user defined image[/URL]


[url=http://img102.imageshack.us/img102/2526/replacedlltestta1.png]user defined image[/URL]


Wie man sieht, lässt sich die DLL auch in das Projekt einbinden.
Leider bekomme ich unter Delphi keine korrekten Ergebnisse.

Man könnte denken, dass es an der DLL liegt, aber die ist wohl OK,
denn in C funktioniert das ganze, mit Ergebnis.


Bei Delphi muss es wohl Probleme it der Übergabe des Strings o.ä. geben,
mir wird bei jeder Eingabe immer nur:


Quelltext
1:
hù                    


zurückgegeben. Hier mein DLL-Aufruf:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
implementation

{$R *.dfm}

Function MyReplace(sSource: PChar; sSearch: PChar; sReplace: PChar): PChar; external 'replaceDLL.dll' name 'strReplace';

procedure TForm1.ButtonReplaceClick(Sender: TObject);
begin
pSource := PChar(RichEditSource.Text);
pSearch := PChar(RichEditSearch.Text);
pReplace := PChar(RichEditReplace.Text);
pResult := MyReplace(pSource,
                     pSearch,
                     pReplace);
RichEditSource.Text := AnsiString(pResult);
end;



Jibbet da irschendwälsche Driggs?
(Gibt es da irgendwelche Tricks)

Gruss aus Hamburg


mkinzler - Mo 28.08.06 16:53

Delphi verwendet eine andere Übergabekonvention. Verwende mal StdCall;


2cHH - Mo 28.08.06 17:11

user profile iconmkinzler hat folgendes geschrieben:
Delphi verwendet eine andere Übergabekonvention. Verwende mal StdCall;



Cool, jetzt funktioniert es, thnx.

*freu*



Gruss aus Hamburg


mkinzler - Mo 28.08.06 17:14

Im nächsten Schritt kannst du ja zu Übung die Funktion in Delphi implemnetieren ;-)


2cHH - Mo 28.08.06 17:54

user profile iconmkinzler hat folgendes geschrieben:
Im nächsten Schritt kannst du ja zu Übung die Funktion in Delphi implemnetieren



Das wäre bestimmt eine gute Übung, ich glaube, dass mache ich auch.




Aber mit der DLL habe ich mich doch etwas zu früh gefreut:

Auf der Konsole funktioniert die DLL auch mit grösseren Strings mehrmals hintereinander:

[url=http://img100.imageshack.us/img100/7027/replacedllconsolefu1.png]user defined image[/URL]

Bei dem GUI-Programm gibt es einen fehlerhaften Zugriffsversuch auf 0x00000000.
Die erste Ersetzung ist verflixt schnell, so wie ich mir das auch vorgestellt habe,
leider kann man danach keine zweite mehr machen und müsste das Programm neustarten.


Ich glaube, es könnte immer noch an dem DLL-Aufruf liegen, nur wo genau?


AndyB - Mo 28.08.06 20:04

user profile icon2cHH hat folgendes geschrieben:
user profile iconmkinzler hat folgendes geschrieben:
Delphi verwendet eine andere Übergabekonvention. Verwende mal StdCall;

Cool, jetzt funktioniert es, thnx.

Hast du auch die C-Funktion als __stdcall markiert?

Zudem hast du da ein Speicherleck, denn der von dir zurückgegebene char* wird nicht mehr freigegeben. (und versuche erst gar nicht den mit FreeMem freizugeben; er wurde mit malloc() reserviert, muss also auch mit free() freigegeben werden)


BenBE - Mo 28.08.06 20:34

Tipp: Schreib deine Funktion so um, dass der Ausgabepuffer und dessen Speichergröße von der Anwendung reserviert werden muss:


Delphi-Quelltext
1:
Function MyReplace(sSource: PChar; sSearch: PChar; sReplace: PChar; sResult: PChar; var nResult: Integer): Boolean; stdcallexternal 'replaceDLL.dll' name 'strReplace';                    



C#-Quelltext
1:
__stdcall bool strReplace(char* sCont, char* sSearch, char* sRepl, char* sResult, int32* nResult)                    


Wobei Result entweder True für "Ersetzung erfolgreich ausgeführt" und False "Nicht genug Speicher oder anderer Fehler!" bedeutet. Zu den Parametern sResult und nResult: sResult ist dein vorreservierter Puffer und nResult dessen größe. Ist die Größe nResult < dem Maximal benötigten Speicher, so wird nResult der Maximal zu erwartende Speichergröße zugewiesen und sResult unbeachtet gelassen. ist sResult nil, gilt das gleiche. Nach der Ersetzung wird sResult an der richtigen Stelle mit einem #0-Nullterminator abgeschlossen und nResult die Größe des Strings zugewiesen. In deinem Programm hast Du dann immer zwei Aufrufe, bei denen der erste kaum Rechenzeit benötigt (da nur eine kleine Berechnung durchgeführt wird) und der zweite Aufruf die eigentliche Ersetzung ausführt. Dann muss sich immer die Anwendung um die Speicherreservierung kümmern und du bekommst damit keine Probleme mehr in der DLL.


2cHH - Mo 28.08.06 20:46

Es funktioniert jetzt 1A, es war ein kleiner Fehler in der DLL, der grosse Auswirkungen hatte.
Das Proggi ist jetzt auch deutlich schneller, als mit AnsiReplaceStr.

*wieder freu*


Ja, stimmt, der Speicher wird solange reserviert, wie das Programm läuft.

In diesem Fall macht das aber nichts, weil das Programm nicht ständig laufen soll.
Es handelt sich um ein paar KB, die dann wieder freigegeben werden,
wenn das Programm beendet wird.



Trotzdem wäre es ganz interessant, das mal auszuprobieren.
Du meinst, Delphi hat auch eine Möglichkeit, um Speicher zu allozieren
und freizugeben, die mit der von C kombatibel ist?

Das wäre ja nicht schlecht. Werde ich nachher mal versuchen.



Die DLL brauche ich ja wohl nicht wie in C mit "Bool FreeLibrary(ModuleHandle)"
wieder freizugeben, ich denke das wird Delphi schon machen, oder?



So langsam fängt Delphi an, Spass zu machen ;)



---------------



ps:

user profile iconBenBE hat folgendes geschrieben:
Tipp: Schreib deine Funktion so um, dass der Ausgabepuffer und dessen Speichergröße von der Anwendung reserviert werden muss:


Sorry, das haben wir parallel gepostet.
Deine Quelltexte werde ich mir auf jeden Fall kopieren,
da kann ich bestimmt auch noch was von lernen.



ps2: Du hattest ganz zu Anfang übrigens Recht, da hat ein Byte gefehlt.
Die Funktion strlen rechnet in C nicht das '\0' mit.
Dann war noch ein Division durch 0 möglich, die ich jetzt vorher abfange.




Gruss aus Hamburg


AndyB - Mo 28.08.06 22:12

user profile icon2cHH hat folgendes geschrieben:
Du meinst, Delphi hat auch eine Möglichkeit, um Speicher zu allozieren
und freizugeben, die mit der von C kombatibel ist?

Das hängt davon ab, ob du gegen die msvcrtXX.dll (MSVC) / cp3245mt.dll (BDS 2006) linkst. In diesem Fall könntest du die free() Funktion importieren. Ansonsten hast du da wenig Changen (wie auch eine andere C++ DLL keine Change hätte)


Martok - Mo 28.08.06 23:57

user profile icon2cHH hat folgendes geschrieben:

Die DLL brauche ich ja wohl nicht wie in C mit "Bool FreeLibrary(ModuleHandle)"
wieder freizugeben, ich denke das wird Delphi schon machen, oder?

Im Prinzip nicht, aber wie neulisch schonmal jemand geschrieben hat: Du räumst dein Zimmer ja auch ab und zu mal auf. Im Idealfall nicht erst, wenn du ausziehst!
Bezogen auf den Fall: was du reservierst/anforderst/sonstwie erhältst, solltest du immer freigeben. Schon weil man vorher nie so genau weiß, ob wirklich alles von Delphi freigegeben wird.

Allerdings hat nicht mal Borland das so genau genommen, in der Graphics.pas ist auch ein Leck. TBitmap gibt irgendwas nicht richtig frei, weiß nicht mehr genau was. Das zeigt einem FastMM bei gesetztem Compilerschalter an. Ist schon interessant zu wissen, das auch Borland Fehler macht ;)


user profile icon2cHH hat folgendes geschrieben:
So langsam fängt Delphi an, Spass zu machen ;)

So ist Delphi nun mal :D

//Martok


2cHH - Di 29.08.06 10:23

user profile iconMartok hat folgendes geschrieben:
Bezogen auf den Fall: was du reservierst/anforderst/sonstwie erhältst, solltest du immer freigeben. Schon weil man vorher nie so genau weiß, ob wirklich alles von Delphi freigegeben wird.


Wird es ja, sobald das Programm beendet wird. ;)


Nein, Spass beiseite, ich habe mal etwas nachgefoscht, Delphi scheint die DLL nicht dynamisch nachzuladen, sondern gleich bei Programmstart. Das Nachladen ist wohl auch möglich (wie ich es verstanden habe), dann muss die DLL aber vorher auch explizit geladen werden.

Dynamisches Nachladen ist imho nur sinnvoll, wenn ein Programm länger läuft und dabei mehrere DLLs benutzt, die es aber nicht ständig braucht. Mein Programm braucht die DLL bei jedem Buttonklick und müsste die DLL dann jedesmal neu laden.

Ich habe das Programm auch mal in zwei Instanzen gestartet, die DLL wurde (laut Processexplorer) nur einmal geladen und von "beiden" Programmen benutzt. Genau so soll es meiner Meinung nach auch sein.




Aber bevor ich mich daran mache, die Funktion von C auf Delphi umzuschreiben, wollte ich nochmal sehen, ob ich den Speicher nicht von Delphi aus reservieren kann. So ähnlich wie es schon vorgeschlagen wurde, allerdings will ich erstmal nur den Zeiger auf den Speicher übergeben, keine boolschen Variablen.


Die DLL habe ich schon so umgeschrieben, dass sie als ersten Parameter den Zeiger auf den Puffer erwartet. Der Speicher wird in Delphi mit der Funktion GetMem reserviert, weil eine Initialisierung mit 0, wie sie durch AllocMem vorgenommen wird, nicht nötig wäre:



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
var
  Form1: TForm1;
  sCont: PChar;
  pCont: ^Char;
  nSize: Integer;

implementation

{$R *.dfm}

Function MyReplace(sBuffer: PChar; sSource: PChar; sSearch: PChar; sReplace: PChar): PChar; StdCallexternal 'replaceDLL.dll' name 'strReplace';

procedure TForm1.Button1Click(Sender: TObject);
begin
sCont := PChar(RichEdit1.Text);
nSize := StrLen(sCont);
GetMem(pCont, nSize);
RichEdit1.Text := MyReplace(pCont,
                            sCont,
                            '1',
                            '2');
end;



Mein Problem ist jetzt, dass ich ^Char nicht als Typ für das Funktionsargument angeben kann (wird scheinbar nicht akzeptiert), aber PChar wohl nicht mit ^Char kompatibel ist.

Wie kann man den Zeiger auf den Puffer übergeben?



ps: die Fehlermeldung ist:

Zitat:
Incompatible types: 'Unit1.Char' and 'System.Char'


Motzi - Di 29.08.06 11:02

Hi,

ich will an dieser Stelle nur einen kleinen Hinweis auf 2 meiner Tutorials absetzen. ;) Es handelt sich dabei um Tutorials zu den Themen "Pointer" und "Strings", beide speziell auf Delphi ausgerichtet. Da du sowohl mit dynamisch reserviertem Speicher als auch Strings/PChars und diversen Casts dazwischen arbeitest, könnten dir ein paar Tipps aus den Tutorials vermutlich noch ein bisschen bei der Optimierung weiterhelfen.

Gruß, Motzi

Edit: PChar ist zwar als ^char definiert, aber dennoch sind es für Delphi 2 verschiedene Typen (Delphi hat eben eine sehr strenge Typprüfung)! Du kannst das aber ganz einfach beheben indem du pCont als PChar deklarierst.


Martok - Di 29.08.06 12:05

Hm, mein Fehler. Ich hab nicht gesehen, dass du die DLL statisch einbindest. Ich hab DLLs eigentlich immer dynamisch, schon falls der User eine alte Version der DLL hat, die möglicherweise eine Funktion noch nicht enthält. Dann kann man das Programm wenigstens trotzdem starten. Das geht mit statisch eingebundenen nicht.

Aber wie dem auch sei: versuch doch mal eine Delphi-Portierung deiner Funktion. Ist gar nicht schwierig, sieht hinterher bloß anders aus ;)

//Martok


2cHH - Di 29.08.06 14:50

Hi,


ich habe jetzt die Speicherbelegung in Delphi hinbekommen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
procedure TForm1.ButtonReplaceClick(Sender: TObject);

var
  pSource: PChar;
  pSearch: PChar;
  pReplace: PChar;
  pResult: PChar;
  nSource: Integer;
  nSearch: Integer;
  nReplace: Integer;
  nResult: Integer;

begin
  pSource := PChar(RichEditSource.Text);
  pSearch := PChar(RichEditSearch.Text);
  pReplace := PChar(RichEditReplace.Text);
  ;
  nSource := StrLen(pSource);
  nSearch := StrLen(pSearch);
  nReplace := StrLen(pReplace);
  ;
  if(nSearch > 0then
  begin
    nResult := (nSource div nSearch)* nReplace + nSource mod nSearch;
    if(nResult < nSource) then nResult := nSource;
    GetMem(pResult, nResult + 1);
    MyReplace(pResult, pSource, pSearch, pReplace);
    RichEditSource.Text := AnsiString(pResult);
    FreeMem(pResult);
  end;
end;



Funktioniert 1A mit der DLL. Jetzt habe ich mir überlegt, die gleiche Funktion in Delphi zu realisieren. Das String-Tutorial habe ich auch schon angefangen. Wenn es nachher um das optimieren des Delphi-Code geht, wird es mir bestimmt helfen.

Erstmal habe ich ohne Optimierung einfach versucht, die Funktion 1:1 zu übertragen. Dass dabei nicht das Maximum an Performance entstehen wird, ist klar. Das Übertragen allein ist aber auch ein gute Übung, weil ich dabei von C auf Delphi umdenken muss.


Ein paar Sachen lassen sich leider nicht so einfach wie in C realisieren, imho hat der Programmierer bei C mehr Freiheiten was z.B. Casts angeht. Die Typenstrenge hat bestimmt auch Vorteile, macht mir aber zur Zeit noch Probleme. ;)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
procedure DelphiStrReplace(sBuff: PChar; sCont: PChar; sSearch: PChar; sRepl: PChar);
var
  nToCopy: Integer;
  pCont: PChar;
  pSearch: PChar;

begin
  if not (sSearch^ = '\0'then
  begin
    sBuff := sCont;
  end
  else
  begin
    //sBuff^ := '\0';//hier gibt es Troubles
    nToCopy := 0;
    while not (sCont^ = '\0'do
    begin
      if(sCont^ = sSearch^) then
      begin
        pCont := sCont;
        pSearch := sSearch;
        while(((++pCont)^ = (++pSearch)^) and (pCont^) and (pSearch^));//hier auch
        ;
        if(pSearch^ = '\0'then             //soweit ist der Compiler noch nicht gekommen
        begin
          strncat(sBuff, (sCont - nToCopy), nToCopy);
          nToCopy := 0;
          strcat(sBuff, sRepl);
          sCont += nSearch;
          continue;
        end;
      end;
      sCont++;
      nToCopy++;
    end;
    strncat(sBuff, (sCont - nToCopy), nToCopy);
  end;
end;



Eine Stringkonstante 'abc', ein Character 'a'. Soweit ich es verstanden habe. Problem ist jetzt aber der NullChar, der den String abschliesst '\0' sollte nicht als String, sondern als Char interpretiert werden.

Bin ich überhaupt auf dem richtigen Weg, lässt sich das ganze so wie in C realisieren?
Muss ich das continue z.B. durch ein Flag ersetzen?


Gruss aus Hamburg


mkinzler - Di 29.08.06 15:01

#0 statt '\0';
Inkrement per ++pCont geht auch nicht, selbiges gilt für sCont += nSearch;
du kannst PChar direkt durch Addieren weitercshieben


Delphi-Quelltext
1:
2:
3:
p := PChar( 'ABC');
inc(p);
=> p := 'BC';


2cHH - Di 29.08.06 20:57

user profile iconmkinzler hat folgendes geschrieben:
#0 statt '\0';

Delphi-Quelltext
1:
inc(p);                    




thnx, ich hab's jetzt in Delphi hinbekommen:

*freu* :rofl:




Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
procedure DelphiStrReplace(sBuff: PChar; sCont: PChar; sSearch: PChar; sRepl: PChar);

var
  pCont: PChar;
  pSearch: PChar;
  nToCopy: Integer;
  nSearch: Integer;

begin
  if (sSearch^ = #0then
  begin
    sBuff := sCont;
  end
  else
  begin
    sBuff^ := #0;
    nToCopy := 0;
    nSearch := StrLen(sSearch);
    while not (sCont^ = #0do
    begin
      if(sCont^ = sSearch^) then
      begin
        pCont := sCont;
        pSearch := sSearch;
        while( (pCont^ = pSearch^) and (not (pCont^ = #0)) and (not (pSearch^ = #0)) ) do
        begin
          inc(pCont);
          inc(pSearch);
        end;

        if(pSearch^ = #0then
        begin
          StrLCat(sBuff, (sCont - nToCopy), (StrLen(sBuff) + nToCopy));
          nToCopy := 0;
          StrCat(sBuff, sRepl);
          sCont := sCont + nSearch;
          continue;
        end;
      end;
      inc(sCont);
      inc(nToCopy);
    end;
    StrLCat(sBuff, (sCont - nToCopy), (StrLen(sBuff) + nToCopy));
  end;
end;


So sieht die direkte Portierung der Funktion aus der DLL aus. Leider erreicht sie nicht die Performance der DLL. Dass die Funktion in C etwas schneller ist, kann man wohl als normal betrachten, immerhin ist die DLL in reinem C geschrieben und auch so kompiliert, das ist imho (weil einfacher gestrickt) auch noch mal etwas schneller als C++.

Trotzdem glaube ich, dass aus der Implemantation in Delphi noch mehr rauszuholen ist, so dass sie noch dichter an die Funktion in C herannkommen kann. Das NonPlusUltra wäre meiner Meinung nach, durch Assemblerbefehle immer gleich 4 Chars auf einmal zu laden, aber so weit wollte ich bei meinem ersten Delphiprogramm nicht gehen.

Ich könnte mir vorstellen, dass StrCat und StrLCat zu langsam sind. Hinzu kommt noch, dass alle Längen vor der Aktion bekannt sind. StrCat und StrLCat müssen imho erst das NullChar finden, dann das NullChar entfernen, den Inhalt dazukopieren und dann das Nullchar wieder anhängen. Das tut hier eigentlich doch nicht Not, weil die Position vom NullChar mitgeführt werden kann.


Wenn ich nachdem gehe, was ich im Stringtutorial bis jetzt gelesen habe, was zugegeben noch nicht viel ist, könnte man auch einen Char-Array deklarieren.



Mal sehen, was am besten funktioniert.....

Gruss aus Hamburg


2cHH - Mi 30.08.06 00:54

Ich habe jetzt die StrCat-Funktionen rasugeworfen und durch etwas schnelleres ersetzt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
procedure MyStringPaste(nBuff: Integer; sBuff: PChar; sToPaste: PChar; nToPaste: Integer);

var
  nIndex: Integer;
  nCount: Integer;
  nLastPos: Integer;

begin
  nCount := 0;
  nLastPos := nBuff + nToPaste - 1;
  for nIndex := nBuff to nLastPos do
  begin
    sBuff[nIndex] := sToPaste[nCount];
    inc(nCount);
  end;
  sBuff[nIndex ] := #0;

end;


nBuff ist der OffSet im Puffer, von dem an geschrieben werden soll und wird von der Replace-Funktion als erster Parameter zusätzlich übergeben.

Das hat ganz schön was gebracht. ;)
Hätte ich nicht erwartet, wie schnell das jetzt schon läuft.


Damit ich das mal alles vergleichen kann, wie stelt man den Delphi-Compiler so ein, das er den schnellsten Code produziert?

Es gilt 16 MB Textersetzung (jedes zweite Zeichen) in ca. 3 sec zu schlagen.
(mehr konnte ich die Version mit DLL nicht verbessern)


Gruss aus Hamburg