![]() |
Datenbank: Firebird • Version: 2.5 • Zugriff über: ADO.NET / IBDAC
Vergleich Firebird: C# vs. Delphi Win32
Hi, ich lerne gerade C# und wollte einen Datenimport für Firebird programmieren.
Leider musste ich feststellen, dass meine C#-Lösung ca. 8x langsamer ist als meine Delphi-Lösung. Habt ihr eine Idee, was ich hier falsch mache? Habe mal ein Beispielprojekt gemacht. Delphi-Code:
Delphi-Quellcode:
C#-Code:
procedure TForm1.Button1Click(Sender: TObject);
var Con: TIBCConnection; Qry: TIBCQuery; i: integer; const max = 10000; begin Con := TIBCConnection.Create(nil); try Con.Server := 'localhost'; Con.Username := 'SYSDBA'; Con.Password := 'masterkey'; Con.Database := 'F:\Test.fdb'; Con.Open; Qry := TIBCQuery.Create(nil); try Qry.Connection := Con; Qry.Params.CreateParam(ftInteger,'Par1',ptInput); Qry.Params.CreateParam(ftFloat,'Par2',ptInput); Qry.Params.CreateParam(ftString,'Par3',ptInput); Qry.Params.CreateParam(ftDateTime,'Par4',ptInput); Qry.SQL.Text := 'UPDATE OR INSERT INTO TEST (FELD1, FELD2, FELD3, FELD4) VALUES (:Par1, :Par2, :Par3, :Par4);'; Qry.Prepare; ProgressBar1.Max := max; ProgressBar1.Position := 0; for i := 0 to max do begin Qry.ParamByName('Par1').Value := i; Qry.ParamByName('Par2').Value := i; Qry.ParamByName('Par3').Value := 'Test'; Qry.ParamByName('Par4').Value := Now; Qry.Execute; ProgressBar1.Position := i; Label1.Caption := inttostr(i) + ' / ' + inttostr(max); Application.ProcessMessages; end; finally qry.Free; end; finally Con.Free; end; end;
Code:
Ich habe auch schon Transaktionen eingebaut, das hat allerdings auch nichts gebracht.
private void button2_Click(object sender, EventArgs e)
{ FbConnection Con = new FbConnection(); FbConnectionStringBuilder ConStrBuilder = new FbConnectionStringBuilder(); // ConnectionString bauen ConStrBuilder.DataSource = "localhost"; ConStrBuilder.UserID = "SYSDBA"; ConStrBuilder.Password = "masterkey"; ConStrBuilder.Database = "F:\\Test.fdb"; // ConnectionString zuweisen Con.ConnectionString = ConStrBuilder.ConnectionString; Con.Open(); FbCommand Cmd = new FbCommand(); Cmd.Connection = Con; Cmd.Parameters.Add("Par1", FbDbType.Integer); Cmd.Parameters.Add("Par2", FbDbType.Decimal); Cmd.Parameters.Add("Par3", FbDbType.VarChar); Cmd.Parameters.Add("Par4", FbDbType.TimeStamp); Cmd.CommandText = "UPDATE OR INSERT INTO TEST (FELD1, FELD2, FELD3, FELD4) VALUES (@Par1, @Par2, @Par3, @Par4);"; Cmd.Prepare(); const int max = 10000; progressBar1.Maximum = max; progressBar1.Value = 0; for (int i = 0; i < max; i++) { Cmd.Parameters["Par1"].Value = i; Cmd.Parameters["Par2"].Value = i; Cmd.Parameters["Par3"].Value = "Test"; Cmd.Parameters["Par4"].Value = DateTime.Now; Cmd.ExecuteNonQuery(); progressBar1.Value = i; label2.Text = i.ToString() + " / " + max.ToString(); Application.DoEvents(); } } Die Tabellendefinition sieht so aus:
Code:
Folgende Werte konnt ich mittels GetTickCount (sind das eig. Millisekunden?) ermitteln:
CREATE TABLE TEST (
FELD1 INTEGER NOT NULL, FELD2 DECIMAL, FELD3 VARCHAR(20), FELD4 TIMESTAMP, /* Schlüssel */ PRIMARY KEY (FELD1) );
Code:
Versuch Delphi C#
1 8515 68312 2 8297 55312 3 8703 54969 4 9984 53969 5 8671 54218 |
AW: Vergleich Firebird: C# vs. Delphi Win32
Das DoEvents muss in Winforms mehr machen, als in Delphi.
Hat da auch gar nix zu suchen. In Winforms kannst du den BackgroundWorker nehmen wenn du ohne viel Arbeit etwas im Hintergund erledigen willst. DoEvents ist nur dafür da um WinForms zu zwingen JETZT die MessageQueue abzuarbeiten, aber nicht um die UI bedienbar zu machen. So schnell wie in Delphi kann das aber nicht werden. Denn der firebird Client ist eine native DLL, und Calls in native DLLs müssen ihre Daten ständig von & zu .Net übersetzen. Das kostet einfach ein bissel was. Wenn du mehr zu Threading in C# wissen willst, schaue die die ![]() |
AW: Vergleich Firebird: C# vs. Delphi Win32
Oh, habe vergessen zu sagen, dass das DoEvents bzw. ProcessMessages erst später dazu kam, um die Anzeige zu aktualisieren. Es macht also keinen großen Unterschied ob mit oder ohne DoEvents.
Ist ja auch nur ein Beispielprogramm... Dass DoEvents gar nicht benötigt wird, wusste ich nicht. Tut einfach so als wäre es nich da ;-) |
AW: Vergleich Firebird: C# vs. Delphi Win32
Weil du Transaktionen erwähnt hast. War das eine explizit gestartete Transaktion, in der dann alles lief? Weil deine Beispiele sehen nach AutoCommit-Modus aus, d.h. nach jedem Query-Execute wird ein Commit(Retaining) gemacht.
|
AW: Vergleich Firebird: C# vs. Delphi Win32
Eine Transaktion die explizit gestartet wird:
Code:
private void button2_Click(object sender, EventArgs e)
{ FbConnection Con = new FbConnection(); FbConnectionStringBuilder ConStrBuilder = new FbConnectionStringBuilder(); // ConnectionString bauen ConStrBuilder.DataSource = "localhost"; ConStrBuilder.UserID = "SYSDBA"; ConStrBuilder.Password = "masterkey"; ConStrBuilder.Database = "F:\\Test.fdb"; // ConnectionString zuweisen Con.ConnectionString = ConStrBuilder.ConnectionString; Con.Open(); FbTransaction Trans = Con.BeginTransaction(); FbCommand Cmd = new FbCommand(); Cmd.Connection = Con; Cmd.Transaction = Trans; Cmd.Parameters.Add("Par1", FbDbType.Integer); Cmd.Parameters.Add("Par2", FbDbType.Decimal); Cmd.Parameters.Add("Par3", FbDbType.VarChar); Cmd.Parameters.Add("Par4", FbDbType.TimeStamp); Cmd.CommandText = "UPDATE OR INSERT INTO TEST (FELD1, FELD2, FELD3, FELD4) VALUES (@Par1, @Par2, @Par3, @Par4);"; Cmd.Prepare(); const int max = 5000; progressBar1.Maximum = max; progressBar1.Value = 0; for (int i = 0; i < max; i++) { Cmd.Parameters["Par1"].Value = i; Cmd.Parameters["Par2"].Value = i; Cmd.Parameters["Par3"].Value = "Test"; Cmd.Parameters["Par4"].Value = DateTime.Now; Cmd.ExecuteNonQuery(); progressBar1.Value = i; label2.Text = i.ToString() + " / " + max.ToString(); Application.DoEvents(); } Trans.Commit(); } |
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
Zitat:
|
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
|
AW: Vergleich Firebird: C# vs. Delphi Win32
Vergiss das mitzählen.
Nimm eine ![]() Erstmal eine komplett drumrum. Dann am besten zwei Stopwatches: Die eine ums komplette rum, die zweite stoppst Du in jedem Durchlauf, schreibst die elapsed time z.B. in eine List<TimeSpan>, resettest und startest sie neu. Dann hast Du auch ne Info wie lange ein insert dauert. Du kannst auch alternativ während die Stopwatch läuft ab und zu die elapsed time auslesen und z.B: auf die Debug- oder Trace-Klasse schreiben. Dann siehst Du am ehesten bei welchen calls die meiste Zeit verloren geht. Aber aufpassen: Wenn Du das im Debug-Build und im worst case noch mit attachtem debugger laufen lässt wird das dann *richtig* langsam. |
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
Morphies Beispiel-Code ist bei mir ohne Debugger ziemlich genau um den Faktor 8 schneller. (ohne string + string + string und DoEvents bei jedem Durchlauf sollte es vergleichbar schnell wie mit Delphi sein) |
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
Code:
ist eine einzige Konkatenation und erzeugt lediglich eine neue String-Variable. Das sollte verschmerzbar sein und nicht stören. Am besten packt er das ganze in einen Background-Thread (wurde schon angesprochen), die Label - Aktualisierung dann in eine jeweils neue anonyme Methode und schmeisst die dann zum Invoken auf den Mainthread. So hat die DB-Geschichte einen kompletten Kern für sich und das UI darf versuchen hinter den ganzen Aktualisierungen herzulaufen ;-)
label2.Text = i.ToString() + " / " + max.ToString();
|
AW: Vergleich Firebird: C# vs. Delphi Win32
wie gesagt, das war nur eine Testanwendung ;) Die eigentliche Anwendung ist natürlich sauberer...
Zu euren Vorschlägen: Mit Strg + F5 läuft das ganze schon wesentlich schneller! Das war anscheinend das Kernproblem... :-) Jetzt bin ich mit 10000 Datensätze bei ziemlich genau 4 Sekunden. Ich denke das reicht soweit. Delphi braucht bei entsprechendem Code nun ca. 2,2 Sekunden. Vielen Dank :) |
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
|
AW: Vergleich Firebird: C# vs. Delphi Win32
Hast du da zufällig einen Tipp wie ich das machen könnte?
Klar, irgendwann wird das in einen Thread ausgelagert, aber von der Ausführungszeit sollte das doch noch keinen Unterschied machen... Die Schleife sieht nun so aus:
Code:
wüsste nicht, was ich da noch optimieren könnte...
const int max = 10000; // < Anzahl
for (int i = 0; i < max; i++) { // Werte den Parametern zuweisen Cmd.Parameters["Par1"].Value = i; Cmd.Parameters["Par2"].Value = i; Cmd.Parameters["Par3"].Value = "Test"; Cmd.Parameters["Par4"].Value = DateTime.Now; // Command durchführen Cmd.ExecuteNonQuery(); // Nach jeweils 3000 Datensätz die Transaktion "committen" if ((i % 3000) == 0) Trans.CommitRetaining(); } |
AW: Vergleich Firebird: C# vs. Delphi Win32
Zitat:
Der Indexer-Zugriff auf den Parameter muss nicht innerhalb der Schleife passieren, weil das jedesmal ein Loopup ist. Das reicht wenn man den ein einziges mal macht, und nicht 40.000 mal :)
Code:
Alternativ kannst Du Dir die Referenzen auf die Parameter auch gleich beim Erzeugen der Parameter merken, dann sparst Du Dir die 4 Lookups zu Beginn auch nochmal, da wär allerdings wirklich mikrooptimierung.
var param1 = Cmd.Parameters["Par1"];
var param2 = Cmd.Parameters["Par2"]; var param3 = Cmd.Parameters["Par3"]; var param4 = Cmd.Parameters["Par4"]; const int max = 10000; // < Anzahl for (int i = 0; i < max; i++) { // Werte den Parametern zuweisen param1.Value = i; param2.Value = i; param3.Value = "Test"; param4.Value = DateTime.Now; // Command durchführen Cmd.ExecuteNonQuery(); // Nach jeweils 3000 Datensätz die Transaktion "committen" if ((i % 3000) == 0) Trans.CommitRetaining(); } |
AW: Vergleich Firebird: C# vs. Delphi Win32
@Morphie: Wird nicht mehr so viel bringen, aber anstatt CommitRetaining ein normales Commit machen. Somit ist es aus Sicht von Firebird keine langlaufende Transaktion und die OIT/OAT kann nachziehen.
|
AW: Vergleich Firebird: C# vs. Delphi Win32
Danke =)
Beide Tipps zusammen bringen ca. 100ms bei 10.000 DS. So lasse ich es nun erstmal denke ich... |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:36 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz