Как оптимизировать этот метод - PullRequest
3 голосов
/ 09 июля 2010
 private static void ConvertToUpper(object entity, Hashtable visited)
    {
        if (entity != null && !visited.ContainsKey(entity))
        {
            visited.Add(entity, entity);

            foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties())
            {
                if (!propertyInfo.CanRead || !propertyInfo.CanWrite)
                    continue;

                object propertyValue = propertyInfo.GetValue(entity, null);

                Type propertyType;
                if ((propertyType = propertyInfo.PropertyType) == typeof(string))
                {
                    if (propertyValue != null && !propertyInfo.Name.Contains("password"))
                    {
                        propertyInfo.SetValue(entity, ((string)propertyValue).ToUpper(), null);
                    }
                    continue;
                }

                if (!propertyType.IsValueType)
                {
                    IEnumerable enumerable;
                    if ((enumerable = propertyValue as IEnumerable) != null)
                    {
                        foreach (object value in enumerable)
                        {
                            ConvertToUpper(value, visited);
                        }
                    }
                    else
                    {
                        ConvertToUpper(propertyValue, visited);
                    }
                }
            }
        }
    }

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

Спасибо за любую помощь

Ответы [ 6 ]

2 голосов
/ 10 июля 2010

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

1) Используется динамическая генерация кода.

2) Использует кэш на основе типов для сгенерированных динамических делегатов.

public class VisitorManager : HashSet<object>
{
  delegate void Visitor(VisitorManager manager, object entity);

  Dictionary<Type, Visitor> _visitors = new Dictionary<Type, Visitor>();

  void ConvertToUpperEnum(IEnumerable entity)
  {
    // TODO: this can be parallelized, but then we should thread-safe lock the cache 
    foreach (var obj in entity)
      ConvertToUpper(obj);
  }

  public void ConvertToUpper(object entity)
  {
    if (entity != null && !Contains(entity))
    {
      Add(entity);

      var visitor = GetCachedVisitor(entity.GetType());

      if (visitor != null)
        visitor(this, entity);
    }
  }

  Type _lastType;
  Visitor _lastVisitor;

  Visitor GetCachedVisitor(Type type)
  {
    if (type == _lastType)
      return _lastVisitor;

    _lastType = type;

    return _lastVisitor = GetVisitor(type);
  }

  Visitor GetVisitor(Type type)
  {
    Visitor result;

    if (!_visitors.TryGetValue(type, out result))
      _visitors[type] = result = BuildVisitor(type);

    return result;
  }

  static MethodInfo _toUpper = typeof(string).GetMethod("ToUpper", new Type[0]);
  static MethodInfo _convertToUpper = typeof(VisitorManager).GetMethod("ConvertToUpper", BindingFlags.Instance | BindingFlags.Public);
  static MethodInfo _convertToUpperEnum = typeof(VisitorManager).GetMethod("ConvertToUpperEnum", BindingFlags.Instance | BindingFlags.NonPublic);

  Visitor BuildVisitor(Type type)
  {
    var visitorManager = Expression.Parameter(typeof(VisitorManager), "manager");
    var entityParam = Expression.Parameter(typeof(object), "entity");

    var entityVar = Expression.Variable(type, "e");
    var cast = Expression.Assign(entityVar, Expression.Convert(entityParam, type));  // T e = (T)entity;

    var statements = new List<Expression>() { cast };

    foreach (var prop in type.GetProperties())
    {
      // if cannot read or cannot write - ignore property
      if (!prop.CanRead || !prop.CanWrite) continue;

      var propType = prop.PropertyType;

      // if property is value type - ignore property
      if (propType.IsValueType) continue;

      var isString = propType == typeof(string);

      // if string type but no password in property name - ignore property
      if (isString && !prop.Name.Contains("password"))
        continue;

      #region e.Prop

      var propAccess = Expression.Property(entityVar, prop); // e.Prop

      #endregion

      #region T value = e.Prop

      var value = Expression.Variable(propType, "value");
      var assignValue = Expression.Assign(value, propAccess);

      #endregion

      if (isString)
      {
        #region if (value != null) e.Prop = value.ToUpper();

        var ifThen = Expression.IfThen(Expression.NotEqual(value, Expression.Constant(null, typeof(string))),
           Expression.Assign(propAccess, Expression.Call(value, _toUpper)));

        #endregion

        statements.Add(Expression.Block(new[] { value }, assignValue, ifThen));
      }
      else
      {
        #region var i = value as IEnumerable;

        var enumerable = Expression.Variable(typeof(IEnumerable), "i");

        var assignEnum = Expression.Assign(enumerable, Expression.TypeAs(value, enumerable.Type));

        #endregion

        #region if (i != null) manager.ConvertToUpperEnum(i); else manager.ConvertToUpper(value);

        var ifThenElse = Expression.IfThenElse(Expression.NotEqual(enumerable, Expression.Constant(null)),
         Expression.Call(visitorManager, _convertToUpperEnum, enumerable),
         Expression.Call(visitorManager, _convertToUpper, value));

        #endregion

        statements.Add(Expression.Block(new[] { value, enumerable }, assignValue, assignEnum, ifThenElse));
      }
    }

    // no blocks 
    if (statements.Count <= 1)
      return null;

    return Expression.Lambda<Visitor>(Expression.Block(new[] { entityVar }, statements), visitorManager, entityParam).Compile();
  }
}
1 голос
/ 10 июля 2010

Хотя ваш вопрос касается производительности кода, существует другая проблема, которую другие, похоже, упускают: ремонтопригодность.

Хотя вы можете подумать, что это не так важно, как проблема с производительностью, наличие кода, более удобочитаемого и обслуживаемого, облегчит решение проблем.

Вот пример того, как ваш код может выглядеть после нескольких рефакторингов:

class HierarchyUpperCaseConverter
{
    private HashSet<object> visited = new HashSet<object>();

    public static void ConvertToUpper(object entity)
    {
        new HierarchyUpperCaseConverter_v1().ProcessEntity(entity);
    }

    private void ProcessEntity(object entity)
    {
        // Don't process null references.
        if (entity == null)
        {
            return;
        }

        // Prevent processing types that already have been processed.
        if (this.visited.Contains(entity))
        {
            return;
        }

        this.visited.Add(entity);

        this.ProcessEntity(entity);
    }

    private void ProcessEntity(object entity)
    {
        var properties = 
            this.GetProcessableProperties(entity.GetType());

        foreach (var property in properties)
        {
            this.ProcessEntityProperty(entity, property);
        }
    }

    private IEnumerable<PropertyInfo> GetProcessableProperties(Type type)
    {
        var properties =
            from property in type.GetProperties()
            where property.CanRead && property.CanWrite
            where !property.PropertyType.IsValueType
            where !(property.Name.Contains("password") &&
                property.PropertyType == typeof(string))
            select property;

        return properties;
    }

    private void ProcessEntityProperty(object entity, PropertyInfo property)
    {
        object value = property.GetValue(entity, null);

        if (value != null)
        {
            if (value is IEnumerable)
            {
                this.ProcessCollectionProperty(value as IEnumerable);
            }
            else if (value is string)
            {
                this.ProcessStringProperty(entity, property, (string)value);
            }
            else
            {
                this.AlterHierarchyToUpper(value);
            }
        }
    }

    private void ProcessCollectionProperty(IEnumerable value)
    {
        foreach (object item in (IEnumerable)value)
        {
            // Make a recursive call.
            this.AlterHierarchyToUpper(item);
        }
    }

    private void ProcessStringProperty(object entity, PropertyInfo property, string value)
    {
        string upperCaseValue = ConvertToUpperCase(value);

        property.SetValue(entity, upperCaseValue, null);
    }

    private string ConvertToUpperCase(string value)
    {
        // TODO: ToUpper is culture sensitive.
        // Shouldn't we use ToUpperInvariant?
        return value.ToUpper();
    }
}

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

class A
{
    public object Value { get; set; }
}

var a = new A() { Value = "Hello" };

Возможно, это именно то, что вы хотели, но строка "Hello" не преобразуется в "HELLO" в вашем коде.

Еще одна вещь, которую я хотел бы отметить, это то, что хотя я пытался сделать ваш код более читабельным, мой рефакторинг кажется примерно на 20% быстрее.

После рефакторинга кода я попытался улучшить его производительность, но обнаружил, что его особенно сложно улучшить. В то время как другие пытаются распараллелить код, я должен предупредить об этом. Распараллелить код не так просто, как могут подумать другие. Между потоками происходит некоторая синхронизация (в форме «посещенной» коллекции). Не забывайте, что запись в коллекцию не является поточно-ориентированной. Использование поточно-ориентированной версии или ее блокировка может снова снизить производительность. Вам придется проверить это.

Я также обнаружил, что узким местом реальной производительности является все отражение (особенно чтение всех значений свойств). Единственный способ действительно ускорить это - жестко кодировать операции кода для каждого типа или, как другие предлагали облегченную генерацию кода. Однако это довольно сложно, и сомнительно, стоит ли оно того.

Я надеюсь, что вы найдете мои рефакторинги полезными и желаю вам удачи в улучшении производительности.

1 голос
/ 09 июля 2010

Есть пара неотложных вопросов:

  1. Существует повторная оценка информации о свойствах, которые, как я предполагаю, относятся к одним и тем же типам.

  2. Отражение сравнительно медленное.

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

Производительность проблемы 2. может быть повышена с помощью генерации кода IL и динамических методов. Я взял код из здесь , чтобы динамически (а также запоминать из пункта 1) реализовать сгенерированные и высокоэффективные вызовы для получения и установки значений свойств. В основном, IL-код генерируется динамически для вызова set и get для свойства и инкапсулируется в оболочку метода - это обходит все этапы отражения (и некоторые проверки безопасности ...). Где следующий код относится к «DynamicProperty», я использовал код из предыдущей ссылки.

Этот метод также можно распараллелить, как предлагают другие, просто убедитесь, что «посещенный» кеш и кеш вычисленных свойств синхронизированы.

private static readonly Dictionary<Type, List<ProperyInfoWrapper>> _typePropertyCache = new Dictionary<Type, List<ProperyInfoWrapper>>();

private class ProperyInfoWrapper
{
    public GenericSetter PropertySetter { get; set; }
    public GenericGetter PropertyGetter { get; set; }
    public bool IsString { get; set; }
    public bool IsEnumerable { get; set; }
}

private static void ConvertToUpper(object entity, Hashtable visited)
{
    if (entity != null && !visited.Contains(entity))
    {
        visited.Add(entity, entity);

        foreach (ProperyInfoWrapper wrapper in GetMatchingProperties(entity))
        {
            object propertyValue = wrapper.PropertyGetter(entity);

            if(propertyValue == null) continue;

            if (wrapper.IsString)
            {
                wrapper.PropertySetter(entity, (((string)propertyValue).ToUpper()));
                continue;
            }

            if (wrapper.IsEnumerable)
            {
                IEnumerable enumerable = (IEnumerable)propertyValue;

                foreach (object value in enumerable)
                {
                    ConvertToUpper(value, visited);
                }
            }
            else
            {
                ConvertToUpper(propertyValue, visited);
            }
        }
    }
}

private static IEnumerable<ProperyInfoWrapper> GetMatchingProperties(object entity)
{
    List<ProperyInfoWrapper> matchingProperties;

    if (!_typePropertyCache.TryGetValue(entity.GetType(), out matchingProperties))
    {
        matchingProperties = new List<ProperyInfoWrapper>();

        foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties())
        {
            if (!propertyInfo.CanRead || !propertyInfo.CanWrite)
                continue;

            if (propertyInfo.PropertyType == typeof(string))
            {
                if (!propertyInfo.Name.Contains("password"))
                {
                    ProperyInfoWrapper wrapper = new ProperyInfoWrapper
                    {
                        PropertySetter = DynamicProperty.CreateSetMethod(propertyInfo),
                        PropertyGetter = DynamicProperty.CreateGetMethod(propertyInfo),
                        IsString = true,
                        IsEnumerable = false
                    };

                    matchingProperties.Add(wrapper);
                    continue;
                }
            }

            if (!propertyInfo.PropertyType.IsValueType)
            {
                object propertyValue = propertyInfo.GetValue(entity, null);

                bool isEnumerable = (propertyValue as IEnumerable) != null;

                ProperyInfoWrapper wrapper = new ProperyInfoWrapper
                {
                    PropertySetter = DynamicProperty.CreateSetMethod(propertyInfo),
                    PropertyGetter = DynamicProperty.CreateGetMethod(propertyInfo),
                    IsString = false,
                    IsEnumerable = isEnumerable
                };

                matchingProperties.Add(wrapper);
            }
        }

        _typePropertyCache.Add(entity.GetType(), matchingProperties);
    }

    return matchingProperties;
}                
1 голос
/ 09 июля 2010

То, что вы могли бы сделать, это иметь Dictionary с типом в качестве ключа и соответствующими свойствами в качестве значений.Тогда вам нужно будет только один раз выполнить поиск свойств, которые вас интересуют (по внешнему виду IEnumerable и string) - в конце концов, свойства, которые имеют типы, не изменятся (если только вывы делаете что-то классное Emit , но я не слишком знаком с этим)

Получив это, вы можете просто перебрать все свойства в Dictionary, используя тип объектов какключ.

Что-то вроде этого (я на самом деле не проверял, но он компилируется :))

</p> <pre><code> private static Dictionary<Type, List<PropertyInfo>> _properties = new Dictionary<Type, List<PropertyInfo>>(); private static void ExtractProperties(List<PropertyInfo> list, Type type) { if (type == null || type == typeof(object)) { return; // We've reached the top } // Modify which properties you want here // This is for Public, Protected, Private const BindingFlags PropertyFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; foreach (var property in type.GetProperties(PropertyFlags)) { if (!property.CanRead || !property.CanWrite) continue; if ((property.PropertyType == typeof(string)) || (property.PropertyType.GetInterface("IEnumerable") != null)) { if (!property.Name.Contains("password")) { list.Add(property); } } } // OPTIONAL: Navigate the base type ExtractProperties(list, type.BaseType); } private static void ConvertToUpper(object entity, Hashtable visited) { if (entity != null && !visited.ContainsKey(entity)) { visited.Add(entity, entity); List<PropertyInfo> properties; if (!_properties.TryGetValue(entity.GetType(), out properties)) { properties = new List<PropertyInfo>(); ExtractProperties(properties, entity.GetType()); _properties.Add(entity.GetType(), properties); } foreach (PropertyInfo propertyInfo in properties) { object propertyValue = propertyInfo.GetValue(entity, null); Type propertyType = propertyInfo.PropertyType; if (propertyType == typeof(string)) { propertyInfo.SetValue(entity, ((string)propertyValue).ToUpper(), null); } else // It's IEnumerable { foreach (object value in (IEnumerable)propertyValue) { ConvertToUpper(value, visited); } } } } }

1 голос
/ 09 июля 2010

Вот блог с кодом, который должен работать, чтобы применить предел максимальной глубины, о котором упоминал Брайан Гидеон, а также несколько параллельных вещей. Он не идеален и может быть немного уточнен, поскольку я разбил типы значений и свойства, не являющиеся значениями, на 2 запроса linq.

private static void ConvertToUpper(object entity, Hashtable visited, int depth)
        {
             if (entity == null || visited.ContainsKey(entity) || depth > MAX_DEPTH)
            {
                return;
            }

            visited.Add(entity, entity);

            var properties = from p in entity.GetType().GetProperties()
                                         where p.CanRead &&
                                                    p.CanWrite &&
                                                    p.PropertyType == typeof(string) &&
                                                    !p.Name.Contains("password") &&
                                                    p.GetValue(entity, null) != null
                                         select p;

            Parallel.ForEach(properties, (p) =>
            {
                p.SetValue(entity, ((string)p.GetValue(entity, null)).ToUpper(), null);
            });

            var valProperties = from p in entity.GetType().GetProperties()
                             where p.CanRead &&
                                        p.CanWrite &&
                                        !p.PropertyType.IsValueType &&
                                        !p.Name.Contains("password") &&
                                        p.GetValue(entity, null) != null 
                             select p;

            Parallel.ForEach(valProperties, (p) =>
            {
                if (p.GetValue(entity, null) as IEnumerable != null)
                {
                    foreach(var value in p.GetValue(entity, null) as IEnumerable)
                        ConvertToUpper(value, visted, depth +1);
                }
                else
                {
                    ConvertToUpper(p, visited, depth +1);
                }
            });
        }
1 голос
/ 09 июля 2010

Это выглядит довольно наклонно для меня.Единственное, о чем я могу думать, - это распараллелить это.Если я получу шанс, я попытаюсь что-то решить и отредактировать свой ответ.

Вот как можно ограничить глубину.

...