方法参考缓存在Java 8中是个好主意吗? [英] Is method reference caching a good idea in Java 8?

查看:106
本文介绍了方法参考缓存在Java 8中是个好主意吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑我有以下代码:

class Foo {

   Y func(X x) {...} 

   void doSomethingWithAFunc(Function<X,Y> f){...}

   void hotFunction(){
        doSomethingWithAFunc(this::func);
   }

}

假设 hotFunction 经常被调用。然后建议缓存 this :: func ,也许是这样:

Suppose that hotFunction is called very often. Would it then be advisable to cache this::func, maybe like this:

class Foo {
     Function<X,Y> f = this::func;
     ...
     void hotFunction(){
        doSomethingWithAFunc(f);
     }
}

就我对java方法引用的理解而言,使用方法引用时,虚拟机会创建匿名类的对象。因此,缓存引用将仅创建该对象一次,而第一种方法在每个函数调用上创建它。这是正确的吗?

As far as my understanding of java method references goes, the Virtual Machine creates an object of an anonymous class when a method reference is used. Thus, caching the reference would create that object only once while the first approach creates it on each function call. Is this correct?

是否应缓存出现在代码中热点位置的方法引用,或者VM是否能够对此进行优化并使缓存变得多余?是否存在关于此的一般最佳实践,或者这种高度VM实现是否特定于此类缓存是否有用?

Should method references that appear at hot positions in the code be cached or is the VM able to optimize this and make the caching superfluous? Is there a general best practice about this or is this highly VM-implemenation specific whether such caching is of any use?

推荐答案

您必须区分频繁执行相同的调用站点,无状态lambda或有状态lambda,以及频繁使用方法 - 引用到同一方法(通过不同的电话会议)。

You have to make a distinction between frequent executions of the same call-site, for stateless lambda or stateful lambdas, and frequent uses of a method-reference to the same method (by different call-sites).

请看以下示例:

    Runnable r1=null;
    for(int i=0; i<2; i++) {
        Runnable r2=System::gc;
        if(r1==null) r1=r2;
        else System.out.println(r1==r2? "shared": "unshared");
    }

这里,同一个呼叫站点被执行两次,产生一个无状态lambda当前的实现将打印shared

Here, the same call-site is executed two times, producing a stateless lambda and the current implementation will print "shared".

Runnable r1=null;
for(int i=0; i<2; i++) {
  Runnable r2=Runtime.getRuntime()::gc;
  if(r1==null) r1=r2;
  else {
    System.out.println(r1==r2? "shared": "unshared");
    System.out.println(
        r1.getClass()==r2.getClass()? "shared class": "unshared class");
  }
}

在第二个例子中,同一个呼叫站点是执行两次,生成一个lambda,其中包含对 Runtime 实例的引用,当前实现将打印unshared但是shared class

In this second example, the same call-site is executed two times, producing a lambda containing a reference to a Runtime instance and the current implementation will print "unshared" but "shared class".

Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
    r1.getClass()==r2.getClass()? "shared class": "unshared class");

相比之下,在最后一个例子中是两个不同的呼叫站点,产生等效的方法参考但是 1.8.0_05 它将打印unshared非共享类

In contrast, in the last example are two different call-sites producing an equivalent method reference but as of 1.8.0_05 it will print "unshared" and "unshared class".

对于每个lambda表达式或方法引用,编译器将发出 invokedynamic 指向JRE提供的类中的引导方法 LambdaMetafactory 以及生成所需lambda实现类所需的静态参数。它留给实际的JRE meta工厂产生的东西,但它是 invokedynamic 指令的一个指定行为,用于记住并重新使用 CallSite 在第一次调用时创建的实例。

For each lambda expression or method reference the compiler will emit an invokedynamic instruction that refers to a JRE provided bootstrap method in the class LambdaMetafactory and the static arguments necessary to produce the desired lambda implementation class. It is left to the actual JRE what the meta factory produces but it is a specified behavior of the invokedynamic instruction to remember and re-use the CallSite instance created on the first invocation.

当前的JRE产生 ConstantCallSite 包含 MethodHandle 为无状态lambda提供一个常量对象(并且没有任何可以想象的理由以不同的方式做到这一点)。对 static 方法的方法引用始终是无状态的。所以对于无状态lambdas和单个调用站点,答案必须是:不要缓存,JVM会这样做,如果没有,它必须有强烈的理由让你不应该抵消。

The current JRE produces a ConstantCallSite containing a MethodHandle to a constant object for stateless lambdas (and there’s no imaginable reason to do it differently). And method references to static method are always stateless. So for stateless lambdas and single call-sites the answer must be: don’t cache, the JVM will do and if it doesn’t, it must have strong reasons that you shouldn’t counteract.

对于有参数的lambda和 this :: func 是一个lambda,它引用了 this 实例,事情有点不同。允许JRE缓存它们,但这意味着在实际参数值和生成的lambda之间保留某种 Map ,这可能比创建简单的结构化lambda实例更昂贵再次。当前的JRE不会缓存具有状态的lambda实例。

For lambdas having parameters, and this::func is a lambda that has a reference to the this instance, things are a bit different. The JRE is allowed to cache them but this would imply maintaining some sort of Map between actual parameter values and the resulting lambda which could be more costly than just creating that simple structured lambda instance again. The current JRE does not cache lambda instances having a state.

但这并不意味着每次都会创建lambda类。它只是意味着已解析的调用站点将像普通对象构造一样,实例化在第一次调用时生成的lambda类。

But this does not mean that the lambda class is created every time. It just means that the resolved call-site will behave like an ordinary object construction instantiating the lambda class that has been generated on the first invocation.

类似的东西适用于方法引用到不同呼叫站点创建的相同目标方法。允许JRE在它们之间共享一个lambda实例,但在当前版本中却没有,很可能是因为不清楚缓存维护是否会得到回报。在这里,即使生成的类也可能不同。

Similar things apply to method references to the same target method created by different call-sites. The JRE is allowed to share a single lambda instance between them but in the current version it doesn’t, most probably because it is not clear whether the cache maintenance will pay off. Here, even the generated classes might differ.

因此,在您的示例中进行缓存可能会使您的程序执行与。但不一定更有效率。缓存对象并不总是比临时对象更有效。除非你真的测量由lambda创建引起的性能影响,否则你不应该添加任何缓存。

So caching like in your example might have your program do different things than without. But not necessarily more efficient. A cached object is not always more efficient than a temporary object. Unless you really measure a performance impact caused by a lambda creation, you should not add any caching.

我认为,只有一些特殊情况下缓存可能有用:

I think, there are only some special cases where caching might be useful:


  • 我们谈论的是很多不同的呼叫网站,指的是相同的方法

  • lambda在构造函数/类初始化中创建,因为稍后在use-site上将同时调用多个线程



    • 遭受第一次调用的较低性能

    • we are talking about lots of different call-sites referring to the same method
    • the lambda is created in the constructor/class initialize because later on the use-site will
      • be called by multiple threads concurrently
      • suffer from the lower performance of the first invocation

      这篇关于方法参考缓存在Java 8中是个好主意吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆