反思有多慢


70

我最近创建了一个接口层,以区分DataAccessProvider和我们的业务逻辑层。通过这种方法,我们可以随时通过更改Web / App.Config中的值来更改对DataAccessProvider的选择。(如果需要,可以提供更多详细信息)。

无论如何,为了做到这一点,我们使用反射来完成我们可以使用的DataProvider类。

/// <summary>
/// The constructor will create a new provider with the use of reflection.
/// If the assembly could not be loaded an AssemblyNotFoundException will be thrown.
/// </summary>
public DataAccessProviderFactory()
{
    string providerName = ConfigurationManager.AppSettings["DataProvider"];
    string providerFactoryName = ConfigurationManager.AppSettings["DataProviderFactory"];
    try
    {
        activeProvider = Assembly.Load(providerName);
        activeDataProviderFactory = (IDataProviderFactory)activeProvider.CreateInstance(providerFactoryName);
    }
    catch
    {
        throw new AssemblyNotFoundException();
    }
}

但是现在我想知道反射的速度有多慢?


4
创建测试工具来对此进行基准测试肯定很简单吗?
marijne

2
如果工厂是单例,那么Assembly.Load仅被调用一次?
CVertex

Answers:


80

在大多数情况下:足够快。例如,如果使用它来创建DAL包装对象,则通过反射创建对象所花费的时间与其连接到网络所需的时间相比将是微不足道的。因此,优化它会浪费时间。

如果您在紧密循环中使用反射,则有一些技巧可以改善它:

  • 泛型(使用包装器where T : new()MakeGenericType
  • Delegate.CreateDelegate (对于类型化的委托;不适用于构造函数)
  • Reflection.Emit -铁杆
  • Expression(如Delegate.CreateDelegate,但更灵活,并且适用于构造函数)

但是出于您的目的,这CreateInstance是完全可以的。坚持下去,并保持简单。


编辑:关于相对性能的观点仍然存在,而最重要的是“对其进行衡量”,我应该澄清以上几点。有时候...确实很重要。首先测量。但是,如果你发现它太慢了,你可能想看看像FastMember,它完成了所有的Reflection.Emit代码在后台悄悄,给你一个很好的方便的API; 例如:

var accessor = TypeAccessor.Create(type);
List<object> results = new List<object>();
foreach(var row in rows) {
    object obj = accessor.CreateNew();
    foreach(var col in cols) {
        accessor[obj, col.Name] = col.Value;
    }
    results.Add(obj);
}

这很简单,但是很快。在特定的示例中,我提到了DAL包装器-如果您要进行大量操作,请考虑使用dapper之类的东西,它再次Reflection.Emit在后台执行所有代码,从而为您提供最快但易于使用的API:

int id = 12345;
var orders = connection.Query<Order>(
    "select top 10 * from Orders where CustomerId = @id order by Id desc",
    new { id }).ToList();

2
如果有人希望看到反射发出的工作方式来访问字段(这不是太复杂)请参阅:sharpanalytics.blogspot.de/2012/08/...
丹尼尔Bişar

@Marc:我一直在使用反射来获取方法,当前方法的类名来记录try-catch中的错误。基本上是为了避免在记录错误时对功能名称进行硬编码。我需要担心吗?
Sangram Nandkhile 2015年

1
@Sangram可能不是,不是
Marc Gravell

19

与非反射代码相比,其速度较慢。重要的不是它是否缓慢,而在于它是否重要。例如,如果您在Web环境中使用反射实例化对象,而预期的并发性可以上升到10K,那么它将很慢。

无论如何,最好不要事先关注性能。如果结果很慢,则可以在正确设计事物的情况下始终加快它们的速度,以便将来可能需要优化的零件得以本地化。

如果需要加快速度,可以查看此著名文章:

动态...但速度很快:三只猴子,狼的故事以及DynamicMethod和ILGenerator类


9

6
不要震惊。测量的最长时间为22秒,进行一百万次迭代。最坏的情况是每次通话22微秒。除非您要创建大量的这些对象,否则确实没什么大不了的。当然,如果你正在创建这些对象数量巨大,那么它可能是一个大问题,但马克指出它仍然会通过数据库连接和查询时间被淹没。除非您知道它对性能至关重要,否则请不要对“慢速的x倍”文章感到惊讶。
itowlson

我同意,尽管速度较慢,但​​对于大多数应用程序而言,性能损失不会超过使用反射的好处。
鲁宾·斯坦斯

5

反思并不慢。通过反射调用方法的速度比正常方法慢大约3倍。如果您只执行一次或在非关键情况下执行此操作,则没有问题。如果在时间紧迫的方法中使用它1万次,我将考虑更改实现。


如果这是真的,我真的很喜欢这句话。“通过反射调用方法比正常方法慢大约三倍。” 你有参考吗?
乔纳森·莱因哈特

1
如果我的帖子大约3岁,我无法记住从那里获得此信息的信息。
Enyra 2012年

我将FieldInfo和PropertyInfo GetValue SetValue的数据访问层转换为已编译的表达式。读取30,000行包含40列的时间从4秒减少到1秒。但是,在并排比较中,设置和获取值时,反射比编译表达式慢200到250倍。
讨厌2014年

5

我以为我会做一个快速测试来证明慢反射与不慢反射相比。

反思

  • 通过遍历每个对象的属性并进行匹配来实例化58个对象
  • 总时间:52254纳秒

    while (reader.Read()) {
        string[] columns = reader.CurrentRecord;
        CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry();
        IEnumerable<PropertyInfo> rawPayFileAttributes = typeof(CdsRawPayfileEntry).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(CustomIndexAttribute)));
        foreach (var property in rawPayFileAttributes) {
            int propertyIndex = ((CustomIndexAttribute)property.GetCustomAttribute(typeof(CustomIndexAttribute))).Index;
            if (propertyIndex < columns.Length)
                property.SetValue(toReturn, columns[propertyIndex]);
            else
                break;
        }
    }
    

没有反思

  • 通过创建一个新对象实例化58个对象
  • 总时间:868纳秒

        while (reader2.Read()) {
            string[] columns = reader2.CurrentRecord;
            CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry() {
                ColumnZero = columns[0],
                ColumnOne = columns[1],
                ColumnTwo = columns[2],
                ColumnThree = columns[3],
                ColumnFour = columns[4],
                ColumnFive = columns[5],
                ColumnSix = columns[6],
                ColumnSeven = columns[7],
                ColumnEight = columns[8],
                ColumnNine = columns[9],
                ColumnTen = columns[10],
                ColumnEleven = columns[11],
                ColumnTwelve = columns[12],
                ColumnThirteen = columns[13],
                ColumnFourteen = columns[14],
                ColumnFifteen = columns[15],
                ColumnSixteen = columns[16],
                ColumnSeventeen = columns[17]
            };
        }
    

尽管这并不完全公平,因为反射还必须在通过反射创建新对象的基础上检索58 * 18次每个属性的特定属性,但这至少提供了一些视角。


3

除了遵循其他答案中给出的链接并确保您没有编写“病理上不好的”代码外,对我而言,对此的最佳答案是自己进行测试。

只有您知道瓶颈所在,反射代码将被用户使用多少次,反射代码是否会陷入紧闭循环等。您知道您的业务案例,将有多少用户访问您的网站以及对性能的要求是什么。

但是,鉴于您在此处显示的代码段,那么我的猜测是,反射的开销不会成为一个大问题。

VS.NET Web测试和性能测试功能应该使测量此代码的性能非常简单。

如果您不使用反射,您的代码将是什么样?它会有什么限制?如果删除反射代码,可能就无法忍受遇到的限制。可能值得尝试在没有反思的情况下设计此代码,以查看是否可行或是否需要替代方案。


0

在开始玩IoC之前,我一直在做类似的事情。我将使用Spring对象定义来指定数据提供程序-SQL,XML或Mocks!


Spring.net相当有能力在运行时更新依赖项。如果您更新配置文件并从工厂重新加载实例,则将获得对更新后实例的引用。(请注意,这不,如果你从app.config中加载的配置工作,只有当你使用一个单独的Spring XML文件。
雅各布
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.