如何使用声明方式而不是LINQ-to-Objects来提高性能?

时间:2016-03-14 13:51:30

标签: c# linq

让我们看一下LINQ-to-Objects。这段代码引入了多少低效率?

public object GetObjectToSerialize(object value, Type targetType)
{
    var type = value.GetType();
    PropertyInfo[] setToNullProperties;

    if (!_typeToPropertyMap.TryGetValue(type, out setToNullProperties))
    {
         var allPropeties = type.GetProperties();
         var passwordProperties = allPropeties
                                  .Where(p => p.PropertyType == typeof(string))
                                  .Where(p => p.Name.Contains("Password")).ToArray();

         var passwordWithoutEncryptedAttribute = passwordProperties
             .Where(p => !p.GetCustomAttributes(typeof(EncryptedConfigurationItemAttribute), false).Any());

          if (passwordWithoutEncryptedAttribute.Any())
          {
              throw new InvalidOperationException();
          }

          var propertiesWithEncryptedAttribute = allPropeties.Where(p => p.GetCustomAttributes(typeof(EncryptedConfigurationItemAttribute), false).Any());
          setToNullProperties = passwordProperties.Union(propertiesWithEncryptedAttribute).ToArray();

          _typeToPropertyMap[type] = setToNullProperties;
     }

     foreach (var property in setToNullProperties)
     {
           property.SetValue(value, null, null);
     }

     return value;
}

我想知道很多.Where .toArray .Union

例如,ToArray方法不知道输出的大小,因此必须进行多次分配。如果我们有int s而不是PropertyInfo的数组,它将从4个元素开始,然后根据需要保持加倍和复制元素。我们最终可能会有过多的存储空间。例如,如果我们最终得到33,000个元素,我们就会浪费128KB动态存储空间(32,000 X 4-byte ints)。

当我发现passwordProperties时,有两个委托对象分配,两个用于调用Enumerable.Where。这些委托指向可能有两个不同的闭包对象,每个闭包对象都捕获了封闭变量。这些闭包对象是新类的实例,它们在二进制文件和运行时都占用了非常重要的空间。 (当然,参数现在存储在两个地方,必须复制到闭包对象,然后每次访问它们时都必须产生额外的间接。)很可能,Where运算符将会分配新的IEnumerable个对象。

如何通过使用声明式方式改进代码(因为随着编译器和运行时享受新的优化,它会变得更快)?

1 个答案:

答案 0 :(得分:4)

if中的代码对每种类型执行一次。我没有看到任何问题。即使它比必要的慢2倍,它仍然足够快,并且执行得很少(免责声明:我写了那段代码:-))

但是你问过如何加速代码......你几乎可以删除所有LINQ:

public object GetObjectToSerialize(object value, Type targetType) 
{
    var type = value.GetType();
    PropertyInfo[] setToNullProperties;

    if (!_typeToPropertyMap.TryGetValue(type, out setToNullProperties)) 
    {
        PropertyInfo[] allProperties = type.GetProperties();

        var setToNullProperties2 = new List<PropertyInfo>(allProperties);

        foreach (PropertyInfo property in allProperties) 
        {
            bool isEncrypted = property.GetCustomAttributes(typeof(EncryptedConfigurationItemAttribute), false).Any();
            bool isPasswordProperty = false;

            if (!isEncrypted) 
            {
                isPasswordProperty = property.PropertyType == typeof(string) && property.Name.Contains("Password");

                if (isPasswordProperty) {
                    throw new InvalidOperationException();
                }
            }

            if (isEncrypted || isPasswordProperty) {
                setToNullProperties2.Add(property);
            }
        }

        _typeToPropertyMap[type] = setToNullProperties = setToNullProperties2.ToArray();
    }

    foreach (var property in setToNullProperties) 
    {
        property.SetValue(value, null, null);
    }

    return value;
}

真正的加速是在Expression树生成器中转换代码,因此您将删除重复反射(SetValue部分)。问题是生成Expression树并编译它非常慢,所以除非你在同一GetObjectToSerialize上使用Type一百次,否则你将没有加速。