【问题标题】:java, reflection, performance, etcjava、反射、性能等
【发布时间】:2011-07-13 00:00:04
【问题描述】:

所以我知道这个话题以前已经做过了,例如Java Reflection Performance,但我的特殊问题是,似乎许多流行的库都是通过注释和反射实现的(例如 Gson、Jackson、Jaxb 实现、休眠搜索)。许多(如果不是全部)库提供了良好(或出色)的性能,即使它们使用反射。我的问题是,他们是怎么做到的?是不是有什么“窍门”要知道,还是单纯的使用直接反射,对性能的担忧被夸大了?

编辑: 例如,当我们写: MyObject obj = new Gson().fromJson(someInputStream, MyObject.class);

我可以理解库如何在内部缓存 Field 对象,但在我看来,它每次都需要反射地实例化对象,并且需要根据解析的值(反射地)调用每个字段的 setter来自 json。 或者有什么方法可以仅在启动时支付(全部)反射成本?

我确实注意到 Gson/Jackson 等具有相对较大的启动成本,并且在那之后速度非常快。所以很明显我想知道,如果我写一个做一些模糊相似的库,有没有我需要知道的技巧?因为在每次通话中,您似乎都无法摆脱一定程度的反思。

【问题讨论】:

标签: java reflection


【解决方案1】:

一般没有技巧。大多数基于反射的操作都是在application startup 上执行的,因此不会影响启动后的运行时性能。一个完美的例子就是整个 hibernate-annotation API。

有时,注释的存在需要影响应用程序的整个生命周期。这通常使用dynamic proxies(或例如,代理具体类时cglib proxies)或基于初始反射读数的拦截器进行配置。

【讨论】:

  • Sjoberg:请参阅我对上述问题的编辑。也许您已经用您对动态代理(我假设为 cglib)的评论回答了我的问题,但我不知道它究竟是如何工作的?也许您可以详细说明或链接到现有说明?
  • @Kevin,动态代理只是 像常规代理(仅运行时) - 它拦截方法调用并对其进行处理。例如 springs @Transactional 注释创建一个代理,该代理围绕目标方法创建一个事务。
  • Sjoberg:好的,所以这并不能解释像 Jackson 或 Gson 这样的库如何避免重复的运行时成本(例如,在我上面的示例中,Gson 需要每次实例化一个新对象,然后调用一个 setter (反思)对于它在一些 json 文档中找到的每个属性)。其他序列化类型库也将面临同样的问题。
  • @Kevin,将 xml 转换为对象根本不需要反射。即便如此,与通过网络发送流量相比,反射的成本也非常很小。再说一次我的第一个习语,通常没有技巧,因为反射不是那么慢。在某些情况下会使用字节码修改(例如,hibernate 使用cglib 来延迟加载属性)。
  • @Kevin,例如见Benchmarking cost of dynamic proxies。他声称使用代理的速度大约慢了 1.6 倍;比几乎为零慢 1.6 倍仍然几乎为零。 cglib 可能同样高效,并且允许您注入所需的字节码作为常规反射的补充。
【解决方案2】:

诀窍是在“配置时”使用反射,而不是在“运行时”。

使用反射检查需要的任何内容,然后将这些信息存储(在内存中)以供运行时使用。

【讨论】:

    【解决方案3】:

    只有原始查找是广泛的,一旦您获得了有关类及其方法所需的所有信息,就没有太大区别了。

    由于一个业务层应该运行很长时间,因此启动速度会慢一些,但之后不会影响性能。

    【讨论】:

      【解决方案4】:

      与使用由编译器放置在应用程序中的数据相比,反射速度较慢,但​​与从数据库中提供相同数据相比,反射速度较快。

      只要应用程序通过反射获得的信息被按需检索并存储在本地缓存中(或以初始化对象的形式),这样检索就成为应用程序生命周期中的一次性事件无需担心反射会成为性能瓶颈。

      【讨论】:

        【解决方案5】:

        成本高的是方法查找,但方法调用一次非常相似。

        因此,一旦您找到要调用的方法,您只需保留对它的引用,连续调用的工作方式类似。

        当然,在某些情况下,您希望每毫秒减少一次。

        虽然你应该beaware of micro benchmarks,但你可以试试这个只是为了得到一个粗略的想法:

        import java.lang.reflect.*;
        class ReflectionOrNot { 
            public void run() { 
                try { 
                    Thread.currentThread().sleep( 0 );
                } catch( InterruptedException ie ){}
            }
        
            public static void main( String ... args ) throws Exception { 
        
                ReflectionOrNot ron = new ReflectionOrNot();
                int max = 1000000;
        
                long start = System.currentTimeMillis();
                for( int i = 0 ; i < max ; i++ ) { 
                    ron.run();
                }
                System.out.println( "Direct access took: " + ( System.currentTimeMillis() - start ) );
        
        
                Method m = ReflectionOrNot.class.getDeclaredMethod("run");
                start = System.currentTimeMillis();
                for( int i = 0 ; i < max ; i++ ) { 
                    m.invoke( ron );
                }
                System.out.println( "Reflection    Took: " + ( System.currentTimeMillis() - start ) );
        
        
                start = System.currentTimeMillis();
                for( int i = 0 ; i < max ; i++ ) { 
                     m = ReflectionOrNot.class.getDeclaredMethod("run");
                    m.invoke( ron );
                }
                System.out.println( "Lookup + Reflect  : " + ( System.currentTimeMillis() - start ) );
        
        
            }
        }
        

        用不同的方法调用 100 万次给了我:

        C:\Users\oreyes\java>java ReflectionOrNot
        Direct access took: 422
        Reflection    Took: 1156
        Lookup + Reflect  : 3016
        
        C:\Users\oreyes\java>java ReflectionOrNot
        Direct access took: 422
        Reflection    Took: 1125
        Lookup + Reflect  : 2750
        
        C:\Users\oreyes\java>java ReflectionOrNot
        Direct access took: 485
        Reflection    Took: 1203
        Lookup + Reflect  : 2797
        

        【讨论】:

        • Class#getDeclaredMethod(s) 复制它的结果,不必要地减慢它使用结果的执行时间只读跨度>
        【解决方案6】:

        我在 Windows 笔记本电脑上使用 Java 8 创建了一个包含一百万次迭代和十个填充随机数的字段的基准测试。结果如下:

        • 直接访问:941 ns par 对象(参考时间)
        • 朴素内省:每个对象 4613 ns (+ 390 %)
        • 自省 缓存在 HashMap 中的字段:1376 ns par object (+ 46 %)
        • 使用缓存在局部变量中的字段进行自省:每 1105 ns 对象(+ 17 %)

        如果需要,我可以发布代码。

        对我来说,如果您将所有类和字段查找排除在循环之外,那么自省成本可以忽略不计。

        问候,

        【讨论】:

          猜你喜欢
          • 2010-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-30
          • 1970-01-01
          • 2018-05-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多