![]() |
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();
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:51 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