2009-07-14 2 views
6

Приведенный ниже код чрезвычайно медленный для таблиц любого значительного размера. (100, 1000 и т. Д.). Преступник создает объекты с объектами new T(). Обратите внимание, что это не мой окончательный код, я только что сломал его части, чтобы получить более простой профиль. Игнорирование и инициализация происходят вместе, как только я реорганизую код обратно в форму.Как ускорить создание экземпляра большой коллекции объектов?

Есть ли способ ускорить это? Я, наверное, забываю что-то действительно простое, или, может быть, я костяк. Надеюсь, первый.

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); 
} 

редактировать для получения дополнительной информации:

Конструктор любого данного ModelType будет выглядеть следующим образом:

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

Конструктор любого данного ModelTypeInfo будет просто установить некоторые строки и строку [] значение , и это единственное задание класса - это предоставление значений.

редактировать даже для получения дополнительной информации:

Поскольку это, кажется, горячая тема, вот что мой метод выглядит для вещественных чисел, прежде чем вспыхивают строительство объекта и инициализации:

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

по теме: Почему вы не просто создать '' List в первую очередь, а не в 'T []', а затем превратить его в список? Кроме того, почему 'for' вместо' foreach'? – Svish

+0

В действительности, это * есть *, что я делаю. Я просто сломал код в этой форме для демонстрационных целей. Вы знаете, чтобы показать, где код медленный, используя как можно меньше магии. –

ответ

13

Под одеялом, new T() генерирует вызов System.Activator.CreateInstance<T>(), который (рефлекторно) медленно:

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 

Возможно, вы захотите рассмотреть возможность передачи в делегат строительства.

+1

+1. Не знал этого и, вероятно, ответил на вопрос. –

+0

Или, можно ли определить правильный конструктор только один раз, а не n раз? Я думаю, что это было бы достаточно хорошо, без уклонения от каждого вызова этого метода. –

+0

CreateDelegate требует MethodInfo, который не соответствует ConstructorInfo. В лучшем случае вам нужно будет испустить метод-оболочку для конструктора и обернуть его в делегат, чтобы избежать отражения внутри цикла. –

3

название вашего вопроса предполагает, что это связано с тем, что метод является общим. Выделяет ли такое же количество объектов без генериков? Если нет, это должно быть связано с тем, что происходит в вашем конструкторе. Можете ли вы опубликовать код конструктора?

EDITED Вот то, что я написал некоторое время назад к конструкторам кэша в DynamicMethod, который очень быстро:

В своем классе:

delegate T ConstructorDelegate(); 

Метод тела:

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

Исправлена ​​ошибка, которая была менее конкретной. –

+0

Может быть, использовать Func вместо объявления имени ConstructorDelegate? –

+0

Это было бы лучше. Фрагмент немного старый - написанный, прежде чем я действительно начал использовать Func и Action с C# 3. –

0

Вы тестируете выпускную сборку?
Это tables.loop.count простое свойство, и можете ли вы сравнить его с выводом из цикла?
Какова стоимость создания экземпляра T?
Создает ли T множество мелких объектов, чтобы вы столкнулись с несколькими сборками мусора?

2

Вам действительно нужен список, или IEnumerable будет достаточно хорошим? Если да, то вы могли бы сделать ленивую/отложенное создание ваших объектов:

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; 
    } 
} 

К сожалению, это все еще может быть медленным, потому что большую часть времени, скорее всего, провели строительство объекта, но это может позволить вам отложить это загрузите достаточно долго, чтобы приложение отображалось быстрее или до тех пор, пока вы не сможете полностью отфильтровать некоторые объекты.

Кроме того, вы могли бы подумать о реализации этого с помощью Factory -like картина:

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

Исходный код был более заводским, и существует способ генерации T на основе DataRow. Ключевое слово доходности, о котором я не знал! Спасибо, что показали это мне. К сожалению, мне нужен IList. –

+0

Вы можете просто вызвать .ToList() по результатам. –

+0

Правильно, я, должно быть, забыл. Дурак я. ;) В конце концов, я все равно внедрил ваш метод IEnumerable и очистил ToList(), чтобы скомпоновать на ToEnumerable(). В любом случае, мне это понадобится. –

0

Чтобы показать на примере, этот метод в C#:

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

компилируется в этом MSIL код (от рефлектора):

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

Чтобы не вдаваться в слишком много внутренностей, есть несколько шаги здесь, проверяется несколько условий, необходимость инициализировать поля данных и т. д., и, наконец, требуется вызвать активатору может. Все это для создания экземпляра объекта общего типа. И да, это используется вместо прямого вызова конструктора типа всегда.

0

Даже если вам нужно использовать список, зачем сначала создавать массив?

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

Это временный код, иллюстрирующий, где происходит медлительность. Читайте этот вопрос более внимательно. –

3

Проблема заключается в том, что выражение new T() фактически использует отражение за кулисами. (Он вызывает Activator.CreateInstance) Поэтому каждый вызов на это займет время.


Одним из решений было бы ограничить T для реализации ICloneable. Затем вы можете написать new T() один раз и клонировать его в цикле. Очевидно, вы можете сделать это только в том случае, если у вас есть полный контроль над моделью.


Другим вариантом было бы сделать метод взять создателя делегата, например:

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(); 

    //... 
} 

Вы бы тогда называть это так:

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

Поскольку он используется в параметр, вам не нужно будет указывать общий тип явно при вызове метода.


Наименее навязчивым методом было бы использовать выражения LINQ для создания собственных методов создателя.

EDIT: Как это:

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

Я просто собирался это предложить. Я использую аналогичный метод для построения treenodes. – leppie

+0

Держу пари, что он добирается до следующего узкого места (я могу поспорить, потому что я как раз собирался добавить почти такой же код в свой ответ). –

0

Для тех, кто работает в этот вопрос позже, этот блог был чрезвычайно полезным для меня: http://blogs.msdn.com/haibo_luo/archive/2005/11/17/494009.aspx

Вот изменения, которые я в конечном итоге сделать, чтобы мой «заводский» метод.(На самом деле не собственно завода, но служит цели)

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; 
    } 
}