2009-07-14 9 views
6

Il seguente codice è estremamente lento per le tabelle di qualsiasi dimensione significativa. (100, 1000, ecc.) Il colpevole sta istanziando i miei oggetti con new T(). Si noti che questo non è il mio codice finalizzato, ne ho solo suddiviso alcune parti per rendere più semplice il profilo. L'istanziazione e l'inizializzazione avverranno insieme una volta che il codice sarà tornato in forma.Come posso velocizzare la creazione di istanze di una vasta raccolta di oggetti?

C'è un modo per velocizzare questo? Probabilmente sto dimenticando qualcosa di veramente semplice, o forse sono disossato. Spero che il primo.

public static IList<T> ToList<T>(this DataTable table) where T : Model, new() 
{ 
    T[] entities = new T[table.Rows.Count]; 

    // THIS LOOP IS VERY VERY SLOW 
    for (int i = 0; i < table.Rows.Count; i++) 
     entities[i] = new T(); 

    // THIS LOOP IS FAST 
    for (int i = 0; i < table.Rows.Count; i++) 
     entities[i].Init(table, table.Rows[i]); 

    return new List<T>(entities); 
} 

modifica per ulteriori informazioni:

Il costruttore di un dato ModelType sarà simile a questa:

public ModelType() 
{ 
    _modelInfo = new ModelTypeInfo(); 
} 

Il costruttore di un dato ModelTypeInfo sarà sufficiente impostare qualche stringa e string [] valori e quel solo lavoro di classe è di fornire i valori impostati.

di modifica per ancora più informazioni:

Dal momento che sembra essere un tema caldo, qui è quello che il mio metodo assomiglia per reali prima di scoppiare la costruzione oggetto e l'inizializzazione:

public static IList<T> ToList<T>(this DataTable table, ModelInfo modelInfo) where T : Model, new() 
{ 
    var tempRepository = new Repository<T>(modelInfo); 

    var list = new List<T>(); 
    foreach (DataRow row in table.Rows) 
     list.Add(tempRepository.FromData(table, row)); 

    return list; 
} 
+0

fuori tema: Perché non basta creare un '' lista , in primo luogo, invece di un 'T []' e poi trasformarla in una lista? Inoltre, perché 'for' invece di' foreach'? – Svish

+0

In realtà, questo * è * quello che faccio. Ho appena infranto il codice in questo modulo a scopo dimostrativo. Sai, per mostrare dove il codice è lento, usando il minimo di magia possibile. –

risposta

13

Sotto le coperte, new T() genera una chiamata a System.Activator.CreateInstance<T>(), che è (riflessivo) lento:

L_0012: ldc.i4.0 
L_0013: stloc.1 
L_0014: br.s L_0026 
L_0016: ldloc.0 
L_0017: ldloc.1 
L_0018: call !!0 [mscorlib]System.Activator::CreateInstance<!!T>() 
L_001d: stelem.any !!T 
L_0022: ldloc.1 
L_0023: ldc.i4.1 
L_0024: add 
L_0025: stloc.1 

Si potrebbe prendere in considerazione la possibilità di passare in un delegato di costruzione.

+1

+1. Non lo sapevo e probabilmente risponde alla domanda. –

+0

Oppure, sarebbe possibile identificare il costruttore corretto solo una volta, piuttosto che n volte? Penso che sarebbe abbastanza buono senza rovinare ogni chiamata a questo metodo. –

+0

CreateDelegate richiede un MethodInfo che non corrisponde a ConstructorInfo. Nella migliore delle ipotesi, dovresti emettere un metodo wrapper per il costruttore e avvolgerlo in un delegato per evitare il colpo di riflessione all'interno del ciclo. –

3

Il il titolo della tua domanda suggerisce che questo ha a che fare con il fatto che il metodo è generico. Sta allocando lo stesso numero di oggetti senza generici più velocemente? In caso contrario, deve essere a che fare con qualsiasi lavoro in corso nel tuo costruttore. Puoi pubblicare il codice costruttore?

CURA Ecco qualcosa che ho scritto poco fa ai costruttori di cache in un DynamicMethod, che è molto veloce:

Nella classe:

delegate T ConstructorDelegate(); 

Il corpo del metodo:

DynamicMethod method = new DynamicMethod(string.Empty, typeof(T), null, 
    MethodBase.GetCurrentMethod().DeclaringType.Module); 
ILGenerator il = method.GetILGenerator(); 
il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); 
il.Emit(OpCodes.Ret); 
var constructor = (ConstructorDelegate)method.CreateDelegate(typeof(ConstructorDelegate)); 
+0

Corretto il titolo per essere meno specifico. –

+0

Forse usare Func invece di dichiarare un ConstructorDelegate con nome? –

+0

Sarebbe meglio. Lo snippet è un po 'vecchio - scritto prima di iniziare a utilizzare Func e Action con C# 3. –

0

Stai testando una versione di rilascio?
È tables.loop.count una proprietà semplice e puoi confrontare per sollevarla dal ciclo?
Qual è il costo dell'istanziazione di T?
La creazione di T alloca un sacco di piccoli oggetti, in modo da eseguire alcune raccolte di dati inutili?

2

Hai davvero bisogno di una lista, o un IEnumerable dovrebbe essere abbastanza buono? Se è così, si potrebbe fare di creazione pigro/differita degli oggetti:

public static IEnumerable<T> ToEnumerable<T>(this DataTable table) where T : Model, new() 
{ 
    foreach (DataRow row in table.Rows) 
    { 
     T entity = new T(); 
     entity.Init(table, row); 

     yield return entity; 
    } 
} 

Purtroppo questo è ancora probabile che sia lento, perché la maggior parte del tempo è probabile speso la costruzione dell'oggetto, ma potrebbe consentire di rinviare questo carica abbastanza a lungo per rendere l'app più veloce o fino a quando non sei in grado di filtrare completamente alcuni oggetti.

Inoltre, si potrebbe pensare di implementare questo utilizzando una fabbrica -come modello:

public static IEnumerable<T> ToEnumerable<T>(this DataTable table, Func<DataRow, T> TFactory) 
{ 
    foreach (DataRow row in table.Rows) 
    { 
     yield return TFactory(row); 
    } 
} 
+0

Il codice originale era più simile a quello di fabbrica e esiste un metodo per generare un T basato su un DataRow. La parola chiave yield che non conoscevo! Grazie per avermelo mostrato. Purtroppo ho bisogno di un IList. –

+0

Puoi semplicemente chiamare .ToList() sui risultati. –

+0

Giusto, devo aver dimenticato. Silly me. ;) Alla fine ho implementato il tuo metodo IEnumerable e ho pulito ToList() per piggyback su ToEnumerable(). Probabilmente ne avrò bisogno, comunque. –

0

Per mostrare con l'esempio, questo metodo in C#:

public T Method<T>() where T : new() 
{ 
    return new T(); 
} 

è compilato a questo codice MSIL (da Reflector):

.method public hidebysig instance !!T Method<.ctor T>() cil managed 
{ 
.maxstack 2 
.locals init (
    [0] !!T CS$1$0000, 
    [1] !!T CS$0$0001) 
L_0000: nop 
L_0001: ldloca.s CS$0$0001 
L_0003: initobj !!T 
L_0009: ldloc.1 
L_000a: box !!T 
L_000f: brfalse.s L_001c 
L_0011: ldloca.s CS$0$0001 
L_0013: initobj !!T 
L_0019: ldloc.1 
L_001a: br.s L_0021 
L_001c: call !!0 [mscorlib]System.Activator::CreateInstance<!!T>() 
L_0021: stloc.0 
L_0022: br.s L_0024 
L_0024: ldloc.0 
L_0025: ret 
} 

Per non andare troppo interni, ci sono diversi passi qui, diverse condizioni da controllare, la necessità di inizializzare i campi di dati, ecc, e infine una chiamata all'attivatore potrebbe essere richiesto. Tutto questo per istanziare un oggetto di tipo generico. E sì, questo è usato al posto di una chiamata diretta al costruttore del tipo sempre.

0

Anche se è necessario utilizzare un elenco, perché creare prima l'array?

public static IList<T> ToList<T>(this DataTable table) where T : Model, new() 
{ 
    var list = new List<T>(); 
    foreach (DataRow dr in table.Rows) { 
     T entity = new T(); 
     entity.Init(table, dr); 
     list.Add(entity); 
    } 
    return list; 
} 
+0

Questo è un codice temporaneo per illustrare dove si verifica la lentezza. Leggi la domanda più da vicino per favore. –

3

Il problema è che l'espressione new T() in realtà utilizza la riflessione dietro le quinte. (Chiama Activator.CreateInstance) Pertanto, ogni chiamata ad esso richiederà tempo.


Una soluzione sarebbe quella di vincolare T per implementare ICloneable. Quindi, è possibile scrivere new T() una volta e clonarlo nel ciclo. Ovviamente, puoi farlo solo se hai il pieno controllo del modello.


Un'altra opzione potrebbe essere quella di rendere il metodo prende un delegato creatore, in questo modo:

public static IList<T> ToList<T>(this DataTable table, Func<T> creator) where T : Model { 
    T[] entities = new T[table.Rows.Count]; 
    for (int i = 0; i < table.Rows.Count; i++) 
     entities[i] = creator(); 

    //... 
} 

Si potrebbe quindi chiamare in questo modo:

table.ToList(() => new MyModelType()); 

Perché è utilizzato in un parametro, non è necessario specificare il tipo generico esplicitamente quando si chiama il metodo.


Il metodo meno intrusivo sarebbe utilizzare le espressioni LINQ per creare i propri metodi di creazione.

EDIT: Ti piace questa:

static class CreatorFactory<T> where T : new() { 
    public static readonly Func<T> Method = 
     Expression.Lambda<Func<T>>(Expression.New(typeof(T)).Compile(); 
} 

public static IList<T> ToList<T>(this DataTable table) where T : Model { 
    var entities = table.Rows.Select(r => CreatorFactory<T>.Method()).ToList(); 

    for (int i = 0; i < table.Rows.Count; i++) 
     entities[i].Init(table, table.Rows[i]); 

    return entities; 
} 
+0

Stavo per suggerire questo. Io uso un metodo simile per la costruzione di treenodes. – leppie

+0

Scommetto che questo lo porta direttamente al collo di bottiglia successivo (posso scommettere perché stavo per aggiungere praticamente lo stesso codice alla mia risposta). –

0

Per chiunque incorrere in questo problema in seguito, questo post del blog è stato estremamente utile per me: http://blogs.msdn.com/haibo_luo/archive/2005/11/17/494009.aspx

Qui ci sono i cambiamenti che ho finito per fare a il mio metodo "fabbrica".(Non proprio una fabbrica corretta, ma serve allo scopo)

public class Repository<T> : IRepository<T> where T : Model, new() 
{ 
    // ... 

    private delegate T CtorDelegate(); 
    private CtorDelegate _constructor = null; 
    private CtorDelegate Constructor 
    { 
     get 
     { 
      if (_constructor == null) 
      { 
       Type type = typeof(T); 
       DynamicMethod dm = new DynamicMethod(type.Name + "Constructor", type, new Type[] { }, typeof(Repository<T>).Module); 
       ILGenerator ilgen = dm.GetILGenerator(); 
       ilgen.Emit(OpCodes.Nop); 
       ilgen.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); 
       ilgen.Emit(OpCodes.Ret); 
       _constructor = (CtorDelegate)dm.CreateDelegate(typeof(CtorDelegate)); 
      } 
      return _constructor; 
     } 
    } 

    public T FromData(DataTable table, DataRow row) 
    { 
     T model = Constructor(); // was previously = new T(); 
     model.Init(table, row); 
     return model; 
    } 
} 
Problemi correlati