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]
[/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
Tino: 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:
- 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);)
- Reserviere einen String entsprechender Größe (SetLength(Result, MaxReplaceSize);)
- Durchsuche den OriginalString zeichenweise nach dem Suchmusterr:
- Wenn das Suchmuster nicht mit dem aktuellen Zeichen übereinstimmt, wird das aktuelle Zeichen in den Ausgabestring kopiert.
- 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
- 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
BenBE 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
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
Christian 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
@
2cHH: 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
BenBE hat folgendes geschrieben: |
@ 2cHH: 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?
BenBE hat folgendes geschrieben: |
@ 2cHHein 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
2cHH hat folgendes geschrieben: |
BenBE hat folgendes geschrieben: | @ 2cHH: 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.
2cHH hat folgendes geschrieben: |
BenBE 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).
2cHH 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
BenBE 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?
BenBE 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]
[/URL]
[url=
http://img102.imageshack.us/img102/2526/replacedlltestta1.png]
[/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:
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
mkinzler 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
mkinzler 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]
[/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
2cHH hat folgendes geschrieben: |
mkinzler 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; stdcall; external '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:
BenBE 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
2cHH 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
2cHH 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 ;)
2cHH 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
Martok 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; StdCall; external '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 > 0) then 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 nToCopy := 0; while not (sCont^ = '\0') do begin if(sCont^ = sSearch^) then begin pCont := sCont; pSearch := sSearch; while(((++pCont)^ = (++pSearch)^) and (pCont^) and (pSearch^)); ; if(pSearch^ = '\0') then 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
mkinzler hat folgendes geschrieben: |
#0 statt '\0';
|
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^ = #0) then begin sBuff := sCont; end else begin sBuff^ := #0; nToCopy := 0; nSearch := StrLen(sSearch); while not (sCont^ = #0) do 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^ = #0) then 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
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!