2012-09-02 3 views
7

Ricevo costantemente i risultati mostrati nel grafico sottostante. Mi chiedo se c'è qualcosa che posso fare per aumentare la prevedibilità. Non sto utilizzando SqlBulkCopy poiché ho bisogno di sfruttare le funzionalità di convalida di EFv5.Perché EF5 produce questi picchi nei tempi di SaveChanges()?

Sarebbe bello se qualcuno potesse verificare/confutare le mie conclusioni. Il mio obiettivo è sbarazzarsi di entrambi i tipi di punte. Sto fornendo il codice sorgente qui sotto per renderlo veloce per te. Tutto ciò di cui hai bisogno è un progetto di libreria di classi in VS, con riferimenti a EFv5 e NUnit, entrambi disponibili attraverso NuGet. Basta incollare questo codice in Class1, modificare la stringa di connessione ed eseguirlo. Puoi usare lo script sql qui sotto per ricreare la tabella.

Sto usando .Net 4.5, EF 5, NUnit 2.6.1, eseguendo il codice in modalità di rilascio, senza debugger collegato. Il db è SqlServer 2008 R2. Eseguo il test con NUnit.exe in modalità a 64 bit, che mostra "Net 4.0" come versione del framework.

EFv5 simple entity insert. 1000 batches of 100 entities

L'asse X è il numero di lotto (1000 lotti in totale), e l'asse Y è millisecondi. Puoi vedere che il primo batch impiega circa 30 secondi, il che è previsto dal momento che dbContext è "freddo". Ogni batch salva 100 entità.

Si prega di notare che questa domanda è alla ricerca di un po 'di informazioni mancanti in this answer, che è il jitter in EF salva.

Ecco il codice che sto utilizzando:

La tabella:

CREATE TABLE [dbo].[Entity1](
    [Id] [int] IDENTITY(1,1) NOT NULL, 
    [IntField] [int] NOT NULL, 
    [StrField] [nvarchar](50) NOT NULL, 
    [DateField] [datetimeoffset](7) NOT NULL, 
CONSTRAINT [PK_Entity1] PRIMARY KEY CLUSTERED 
(
    [Id] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
    ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

Le classi:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Diagnostics; 
using NUnit.Framework; 

namespace ClassLibrary1 
{ 
    public class Entity1 
    { 
     public int Id { get; protected set; } 
     public int IntField { get; set; } 
     public string StrField { get; set; } 
     public DateTimeOffset DateField { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public MyContext(string connStr) : base(connStr) { } 
     public virtual DbSet<Entity1> Entities { get { return base.Set<Entity1>(); } } 
    } 

    [TestFixture] 
    public class Class1 
    { 
     [Test] 
     public void EfPerf() 
     { 
      var entities = buildEntities(100000); 
      int batchSize = 100; 
      var batchTimes = new List<Stopwatch>(); 

      for (int i = 0; i < entities.Length; i += batchSize) 
      { 
       var sw = Stopwatch.StartNew(); 
       using (var ctx = buildCtx()) 
       { 
        for (int j = i; j < i + batchSize; j++) 
         ctx.Entities.Add(entities[j]); 
        ctx.SaveChanges(); 
       } 
       sw.Stop(); 
       batchTimes.Add(sw); 
      } 

      batchTimes.ForEach(sw => Console.Out.WriteLine("Elapsed ms: " + 
       sw.ElapsedMilliseconds)); 
     } 

     private MyContext buildCtx() 
     { 
      var cs = "Data Source=your db server;" + 
        "Initial Catalog=your db;" + 
        "Persist Security Info=True;" + 
        "User ID=your user;" + 
        "Password=your pwd"; 
      var ctx = new MyContext(cs); 
      //ctx.Configuration.ProxyCreationEnabled = false; 
      return ctx; 
     } 

     private Entity1[] buildEntities(int count) 
     { 
      var entities = new Entity1[count]; 
      for (int i = 0; i < count; i++) 
       entities[i] = new Entity1 { IntField = i, StrField = "str" + i, 
        DateField = DateTimeOffset.UtcNow }; 
      return entities; 
     } 
    } 
} 
+0

Puoi esagerare il problema aumentando la dimensione del batch? Questo potrebbe consentire di rompere il debugger. È inoltre possibile avviare un timer dopo ogni batch che chiama Debugger.Break dopo 8s nel batch. – usr

+0

Eseguire anche SQL Profiler per verificare se i picchi si verificano a livello SQL. Guarda la colonna della durata. – usr

+1

Sono intenzionalmente non collegare il debugger in modo da poter essere sicuro che non sia un fattore. Inoltre, ho eseguito Sql Profiler e tutti gli inserti hanno una durata pari a "0", e solo uno in poche dozzine richiede più della CPU "0" (e solo circa "15" in quel caso). – esegura

risposta

1

Ho la sensazione che si esegue in un problema con il blocco critico in il tuo DB. EF inserisce tutte le chiamate saveChanges() in una transazione. Per impostazione predefinita, le transazioni in SQL Server vengono eseguite come Read Committed che è altamente restrittivo. Forse puoi provare a cambiare il livello di isolamento in questo modo:

using (var scope = new TransactionScope(TransactionScopeOption.Required, new 
2: TransactionOptions { IsolationLevel= IsolationLevel.Snapshot })) 
3: { 
4: // do something with EF here 
5: scope.Complete(); 
6: }