Compact 3.5
Der folgende Linq code braucht 20 Sek. Manuelles insert dauert hingegen nur 5 Sek. Mache ich etwas falsch?
Db.Transaction = DbCon.BeginTransaction();
for (i = 0; i < 10000; i++)
{
Test p = new Test();
p.Test = i;
p.Id = i;
Db.Tests.InsertOnSubmit(p);
}
Db.SubmitChanges();
Db.Transaction.Commit();
hallo,
Wie sieht denn im Vergleich dazu der Code für den manuellen Insert aus?
Ich glaube, dass hier 10000 Sql Statements abgesetzt werden - was sagt denn der Trace vom LINQ bzw. der SQL Profiler dazu?
fg
hannes
Für den Verlust an Performance gewinnst du die Objektorientierung und Compilezeitsicherheit.
Nach meiner Theorie gilt die Regel "Was an Kraft gewonnen wird, geht an Weg verloren" und umgekehrt - aus der Physik (Hebelgschichten) auch in der Informatik.
loop:
btst #6,$bfe001
bne.s loop
rts
Hallo zusammen
@Hannes
Ich denke nicht, das hier 10000 Statements abgesetzt werden.
@0815Coder
Da stimme ich zu 🙂
Gruss Peter
--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011
hallo,
@Peter: stimmt, wenn der LINQ Provider schlau ist, werden keine 10000 Statements abgesetzt, weil ja "SubmitChanges()" nach der Schleife ist, dann wirds wohl ein Batch Insert sein - hoffentlich. 😁
=> werds bei Gelegenheit selbst mal testen.
fg
hannes
Hallo Hannes
@Peter: stimmt, wenn der LINQ Provider schlau ist, werden keine 10000 Statements abgesetzt, weil ja "SubmitChanges()" nach der Schleife ist, dann wirds wohl ein Batch Insert sein - hoffentlich. 😁
Jup: InsertOnSubmit, darum 🙂
Gruss Peter
--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011
Leider werden keine Bulk-Funktionen unterstützt. Allerdings gibt es ein Open-Source-Projekt, welches versucht diesen Umstand zu beseitigen:
http://code2code.net/DB_Linq/
http://code2code.net/DB_Linq/LINQ_and_SQL_Bulk_Insert.html
Standard LINQ ist wohl eher nicht die richtige Technolgie, große Datenmengen auf Tabellenebene zu bewegen.
hallo,
ich hab mir gerade ein kleines Testrpogramm geschrieben:
class Program
{
static void Main(string[] args)
{
LinqPerformanceTest();
AdoNetPerformaceTest();
Console.ReadLine();
}
public static void LinqPerformanceTest()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
DataClasses1DataContext ctx = new DataClasses1DataContext();
//ctx.Log = Console.Out;
for (int i = 0; i < 10000; i++)
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.PersonNr = i.ToString();
p.Name = "Name " + i.ToString();
ctx.Persons.InsertOnSubmit(p);
}
ctx.SubmitChanges();
watch.Stop();
Console.WriteLine("LINQ insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
public static void AdoNetPerformaceTest()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
SqlConnection connection = new SqlConnection(@"Data Source=POLARIS\SQLSERVER2005;Initial Catalog=LinqDB;Integrated Security=True");
connection.Open();
SqlCommand command = new SqlCommand();
command.Connection = connection;
for (int i = 0; i < 10000; i++)
{
string sqlCommand = string.Format("Insert into Person (Id, PersonNr, Name) values ('{0}', '{1}', '{2}')",
Guid.NewGuid(), i, "ADO-Name " + i.ToString());
command.CommandText = sqlCommand;
command.ExecuteNonQuery();
}
watch.Stop();
Console.WriteLine("ADO.NET insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
}
Was ich dabei gestegestellt habe:
Zeiten:
Linq: 5635ms
pure ADO.net: 6392ms
Sql Server läuft auf lokalem Rechner.
fg
hannes
Das eigentlich teure ist ja der Round-Trip. Das Connection-Pooling hält eh den Socket offen und ob man die Bulk-Syntax nutzt oder einzelne Commands absetzt ist vermutlich nicht entscheidend. Wenn man aber jedesmal die ID zurücklesen muss bzw. Konflikte prüfen, wirds halt teuer. Und das ist natürlich der Regelfall für eine Anwendung. Nichtsdestotrotz wäre es natürlich schön, wenn man trotzdem eine Bulk-Funktion hätte, auch wenn sie nur für seltene Anwendungsfälle taugt. Ich bin ziemlich sicher, dass wird in einer 2.0 ebenso kommen, wie es bei ADO.NET auch der Fall war.
@HannesB:
und solltest du, wie es eigentlich bestpractice ist, vor der schleife eine Transaction
öffnen, und danach committen ( oder ein rollback machen) wirst du
diese Zeiten drastisch runter gehen sehen.
hallo,
ok, ich hab den Code abgeändert:
class Program
{
static void Main(string[] args)
{
LinqPerformanceTest();
AdoNetPerformaceTest();
Console.ReadLine();
}
public static void LinqPerformanceTest()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
using (TransactionScope tx = new TransactionScope())
{
DataClasses1DataContext ctx = new DataClasses1DataContext();
//ctx.Log = Console.Out;
for (int i = 0; i < 10000; i++)
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.PersonNr = i.ToString();
p.Name = "Name " + i.ToString();
ctx.Persons.InsertOnSubmit(p);
}
ctx.SubmitChanges();
tx.Complete();
}
watch.Stop();
Console.WriteLine("LINQ insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
public static void AdoNetPerformaceTest()
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
using (TransactionScope tx = new TransactionScope())
{
SqlConnection connection = new SqlConnection(@"Data Source=POLARIS\SQLSERVER2005;Initial Catalog=LinqDB;Integrated Security=True");
connection.Open();
SqlCommand command = new SqlCommand();
command.Connection = connection;
for (int i = 0; i < 10000; i++)
{
string sqlCommand = string.Format("Insert into Person (Id, PersonNr, Name) values ('{0}', '{1}', '{2}')",
Guid.NewGuid(), i, "ADO-Name " + i.ToString());
command.CommandText = sqlCommand;
command.ExecuteNonQuery();
}
tx.Complete();
}
watch.Stop();
Console.WriteLine("ADO.NET insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
}
Ergebnisse:
LINQ: 5848ms
pure ADO.Net: 2690ms
fg
hannes
Interessanterweise wirds hier
http://blogs.msdn.com/ricom/archive/2007/07/05/dlinq-linq-to-sql-performance-part-4.aspx
http://blogs.msdn.com/ricom/archive/2007/07/05/dlinq-linq-to-sql-performance-part-5.aspx
behauptet, dass DLINQ bei Insert und Upate 3,5 mal schneller sei, als die ADO.NET-Version.
Dabei fasst der sogar noch 10 Inserts zusammen. Schon ulkig.
Ziehe doch nochmal den StopWatch-Context enger. Also nach dem Erzeugen des DataContextes bzw. nach dem Öffnen der Connection. Wie sehen dann die Zeiten aus? Und tauschen mal die Ausführungen von DLINQ und ADO.NET. Bei deiner momentanen Implementierung kann es nämlich sein, dass DLINQ die Kosten für den Verbindungsaufbau zugeschlagen wird und ADO.NET auf die offene Verbindung des Pools läuft.
hallo,
ok, noch eine kleine erweiterung des programmes (ich hoffe, das wird jetzt nicht bald zu lange fürs forum)
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 3; i++)
{
AdoNetPerformaceTest(i);
LinqPerformanceTest(i);
Console.WriteLine("***************************" + Environment.NewLine);
}
Console.ReadLine();
}
public static void LinqPerformanceTest(int run)
{
Console.WriteLine("*** Run: [{0}]", run);
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
using (TransactionScope tx = new TransactionScope())
{
Console.WriteLine("*** LINQ ***");
Console.WriteLine("LINQ insert operation elapsed 1: [{0}] miliseconds", watch.ElapsedMilliseconds);
DataClasses1DataContext ctx = new DataClasses1DataContext();
Console.WriteLine("LINQ insert operation elapsed 2: [{0}] miliseconds", watch.ElapsedMilliseconds);
//ctx.Log = Console.Out;
for (int i = 0; i < 10000; i++)
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.PersonNr = i.ToString();
p.Name = "Name " + i.ToString();
ctx.Persons.InsertOnSubmit(p);
}
Console.WriteLine("LINQ insert operation elapsed 3: [{0}] miliseconds", watch.ElapsedMilliseconds);
ctx.SubmitChanges();
Console.WriteLine("LINQ insert operation elapsed 4: [{0}] miliseconds", watch.ElapsedMilliseconds);
tx.Complete();
Console.WriteLine("LINQ insert operation elapsed 5: [{0}] miliseconds", watch.ElapsedMilliseconds);
}
watch.Stop();
Console.WriteLine("LINQ insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
public static void AdoNetPerformaceTest(int run)
{
Console.WriteLine("*** ADO.NET ***");
Console.WriteLine("*** Run: [{0}]", run);
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
using (TransactionScope tx = new TransactionScope())
{
Console.WriteLine("ADO.NET insert operation elapsed 1: [{0}] miliseconds", watch.ElapsedMilliseconds);
SqlConnection connection = new SqlConnection(@"Data Source=POLARIS\SQLSERVER2005;Initial Catalog=LinqDB;Integrated Security=True");
connection.Open();
Console.WriteLine("ADO.NET insert operation elapsed 2: [{0}] miliseconds", watch.ElapsedMilliseconds);
SqlCommand command = new SqlCommand();
command.Connection = connection;
for (int i = 0; i < 10000; i++)
{
string sqlCommand = string.Format("Insert into Person (Id, PersonNr, Name) values ('{0}', '{1}', '{2}')",
Guid.NewGuid(), i, "ADO-Name " + i.ToString());
command.CommandText = sqlCommand;
command.ExecuteNonQuery();
}
Console.WriteLine("ADO.NET insert operation elapsed 3: [{0}] miliseconds", watch.ElapsedMilliseconds);
tx.Complete();
Console.WriteLine("ADO.NET insert operation elapsed 4: [{0}] miliseconds", watch.ElapsedMilliseconds);
}
watch.Stop();
Console.WriteLine("ADO.NET insert operation took [{0}] miliseconds", watch.ElapsedMilliseconds);
}
}
Konsolenausgabe:
*** ADO.NET ***
*** Run: [0]
ADO.NET insert operation elapsed 1: [49] miliseconds
ADO.NET insert operation elapsed 2: [152] miliseconds
ADO.NET insert operation elapsed 3: [2715] miliseconds
ADO.NET insert operation elapsed 4: [2715] miliseconds
ADO.NET insert operation took [2721] miliseconds
*** Run: [0]
*** LINQ ***
LINQ insert operation elapsed 1: [0] miliseconds
LINQ insert operation elapsed 2: [40] miliseconds
LINQ insert operation elapsed 3: [213] miliseconds
LINQ insert operation elapsed 4: [5278] miliseconds
LINQ insert operation elapsed 5: [5279] miliseconds
LINQ insert operation took [5292] miliseconds
***************************
*** ADO.NET ***
*** Run: [1]
ADO.NET insert operation elapsed 1: [0] miliseconds
ADO.NET insert operation elapsed 2: [0] miliseconds
ADO.NET insert operation elapsed 3: [3051] miliseconds
ADO.NET insert operation elapsed 4: [3051] miliseconds
ADO.NET insert operation took [3056] miliseconds
*** Run: [1]
*** LINQ ***
LINQ insert operation elapsed 1: [0] miliseconds
LINQ insert operation elapsed 2: [0] miliseconds
LINQ insert operation elapsed 3: [165] miliseconds
LINQ insert operation elapsed 4: [5291] miliseconds
LINQ insert operation elapsed 5: [5291] miliseconds
LINQ insert operation took [5299] miliseconds
***************************
*** ADO.NET ***
*** Run: [2]
ADO.NET insert operation elapsed 1: [0] miliseconds
ADO.NET insert operation elapsed 2: [0] miliseconds
ADO.NET insert operation elapsed 3: [2764] miliseconds
ADO.NET insert operation elapsed 4: [2764] miliseconds
ADO.NET insert operation took [2770] miliseconds
*** Run: [2]
*** LINQ ***
LINQ insert operation elapsed 1: [0] miliseconds
LINQ insert operation elapsed 2: [0] miliseconds
LINQ insert operation elapsed 3: [149] miliseconds
LINQ insert operation elapsed 4: [5359] miliseconds
LINQ insert operation elapsed 5: [5359] miliseconds
LINQ insert operation took [5370] miliseconds
***************************
=> Zeitliches Verhalten ändert sich also nicht.
fg
hannes
Dann isses so. Mir erscheinen die Zeiten aus diesem Artikel auch nicht plausibel, zumindest was Insert und Update angehen.
LINQ verwendet doch intern auch ADO, also wie soll das schneller sein?
loop:
btst #6,$bfe001
bne.s loop
rts
Imho hält Linq selbst intern bereits eine Transaction, die mit SubmitChanges() committed wird.
Genau genommen merkt sich Linq halt die Änderungen in den Tables oder was weiß ich wo und bei SubmitChanges() rollt die Sache erst richtig los.
Geht was schief, rollts zurück.
Ich weiß nicht ob das an der Performance was ändert, unnötig ist der Transaction-Block jedoch trotzdem.