Laden...

Linq Geschwindigkeit

Erstellt von can320 vor 16 Jahren Letzter Beitrag vor 16 Jahren 5.350 Views
C
can320 Themenstarter:in
151 Beiträge seit 2006
vor 16 Jahren
Linq Geschwindigkeit

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();
185 Beiträge seit 2005
vor 16 Jahren

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

0
767 Beiträge seit 2005
vor 16 Jahren

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

5.941 Beiträge seit 2005
vor 16 Jahren

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

185 Beiträge seit 2005
vor 16 Jahren

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

5.941 Beiträge seit 2005
vor 16 Jahren

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

S
8.746 Beiträge seit 2005
vor 16 Jahren

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.

185 Beiträge seit 2005
vor 16 Jahren

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:

  • beim "ctx.SubmitChanges();" werden 10000 einzelne SQL Statements abgesetzt, wie von svenson bereits angemerkt - schade, dass MS hier den linq-provider nicht weiter optimiert hat.
  • linq ist in meinem beispiel schneller wie pure ado.net (sqlCommand ist nur für Testzwekce mit stringoperation zusammengesetzt)

Zeiten:
Linq: 5635ms
pure ADO.net: 6392ms

Sql Server läuft auf lokalem Rechner.

fg
hannes

S
8.746 Beiträge seit 2005
vor 16 Jahren

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.

F
10.010 Beiträge seit 2004
vor 16 Jahren

@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.

185 Beiträge seit 2005
vor 16 Jahren

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

S
8.746 Beiträge seit 2005
vor 16 Jahren

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.

185 Beiträge seit 2005
vor 16 Jahren

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

S
8.746 Beiträge seit 2005
vor 16 Jahren

Dann isses so. Mir erscheinen die Zeiten aus diesem Artikel auch nicht plausibel, zumindest was Insert und Update angehen.

0
767 Beiträge seit 2005
vor 16 Jahren

LINQ verwendet doch intern auch ADO, also wie soll das schneller sein?

loop:
btst #6,$bfe001
bne.s loop
rts

B
114 Beiträge seit 2007
vor 16 Jahren

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.