메소드 참조 캐싱이 Java 8에서 좋은 아이디어입니까?
다음과 같은 코드가 있다고 생각하십시오.
class Foo {
Y func(X x) {...}
void doSomethingWithAFunc(Function<X,Y> f){...}
void hotFunction(){
doSomethingWithAFunc(this::func);
}
}
hotFunction
매우 자주 호출 된다고 가정합니다 . 그러면 다음 this::func
과 같이 캐시하는 것이 좋습니다 .
class Foo {
Function<X,Y> f = this::func;
...
void hotFunction(){
doSomethingWithAFunc(f);
}
}
Java 메서드 참조에 대한 제가 이해하는 한, 가상 머신은 메서드 참조가 사용될 때 익명 클래스의 객체를 생성합니다. 따라서 참조를 캐싱하면 해당 객체가 한 번만 생성되고 첫 번째 접근 방식은 각 함수 호출에서 객체를 생성합니다. 이 올바른지?
코드의 핫 포지션에 나타나는 메서드 참조를 캐시해야합니까, 아니면 VM이이를 최적화하고 캐시를 불필요하게 만들 수 있습니까? 이것에 대한 일반적인 모범 사례가 있습니까? 아니면 이러한 캐싱이 사용되는지 여부에 관계없이 VM 구현이 매우 구체적입니까?
상태 비 저장 람다 또는 상태 저장 람다에 대해 동일한 call-site 의 빈번한 실행과 동일한 메서드 에 대한 메서드 참조 의 빈번한 사용 (다른 호출 사이트에 의한)을 구분해야합니다.
다음 예를보십시오.
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");
}
여기에서 동일한 호출 사이트가 두 번 실행되어 상태 비 저장 람다를 생성하고 현재 구현은 "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");
}
}
두 번째 예에서는, 동일한 통화 사이트는 참조 함유 람다하여 두 번 실행 Runtime
인스턴스 인쇄 될 현재 구현 "unshared"
하지만이 "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"
그리고 "unshared class"
.
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.
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.
매개 변수 this::func
가 있고 this
인스턴스에 대한 참조가있는 람다의 경우 상황이 약간 다릅니다. JRE는 그것들을 캐싱 할 수 있지만 이것은 Map
실제 매개 변수 값과 결과 람다 사이에 일종의 유지를 의미 하므로 단순한 구조화 된 람다 인스턴스를 다시 만드는 것보다 더 많은 비용이들 수 있습니다. 현재 JRE는 상태가있는 람다 인스턴스를 캐시하지 않습니다.
그러나 이것이 람다 클래스가 매번 생성된다는 것을 의미하지는 않습니다. 이는 해결 된 호출 사이트가 첫 번째 호출에서 생성 된 람다 클래스를 인스턴스화하는 일반 개체 구성처럼 동작한다는 것을 의미합니다.
다른 호출 사이트에서 만든 동일한 대상 메서드에 대한 메서드 참조에도 유사한 사항이 적용됩니다. JRE는 이들간에 단일 람다 인스턴스를 공유 할 수 있지만 현재 버전에서는 그렇지 않습니다. 아마도 캐시 유지 관리가 효과가 있는지 여부가 명확하지 않기 때문일 것입니다. 여기에서는 생성 된 클래스도 다를 수 있습니다.
따라서 예제와 같은 캐싱은 프로그램이없는 것과 다른 일을 할 수 있습니다. 그러나 반드시 더 효율적인 것은 아닙니다. 캐시 된 개체가 항상 임시 개체보다 더 효율적인 것은 아닙니다. 람다 생성으로 인한 성능 영향을 실제로 측정하지 않는 한 캐싱을 추가해서는 안됩니다.
캐싱이 유용 할 수있는 몇 가지 특별한 경우가 있다고 생각합니다.
- 우리는 동일한 방법을 참조하는 다양한 호출 사이트에 대해 이야기하고 있습니다.
- 람다는 생성자 / 클래스 초기화에서 생성됩니다. 나중에 사용 사이트에서
- 동시에 여러 스레드에서 호출
- 첫 번째 호출의 성능이 저하됨
언어 사양을 이해하는 한 관찰 가능한 동작을 변경하더라도 이러한 종류의 최적화를 허용합니다. 섹션 JSL8 §15.13.3 에서 다음 인용문을 참조하십시오 .
§15.13.3 메서드 참조의 런타임 평가
런타임에 메서드 참조 식의 평가는 정상적인 완료 가 개체에 대한 참조를 생성 하는 한 클래스 인스턴스 생성 식의 평가와 유사 합니다. [..]
[..] Either a new instance of a class with the properties below is allocated and initialized, or an existing instance of a class with the properties below is referenced.
A simple test shows, that method references for static methods (can) result in the same reference for each evaluation. The following program prints three lines, of which the first two are identical:
public class Demo {
public static void main(String... args) {
foobar();
foobar();
System.out.println((Runnable) Demo::foobar);
}
public static void foobar() {
System.out.println((Runnable) Demo::foobar);
}
}
I can't reproduce the same effect for non-static functions. However, I haven't found anything in the language specification, that inhibits this optimization.
So, as long as there is no performance analysis to determine the value of this manual optimization, I strongly advise against it. The caching affects the readability of the code, and it's unclear whether it has any value. Premature optimization is the root of all evil.
One situation where it is a good ideal, unfortunately, is if the lambda is passed as a listener that you want to remove at some point in the future. The cached reference will be needed as passing another this::method reference will not be seen as the same object in the removal and the original won't be removed. For example:
public class Example
{
public void main( String[] args )
{
new SingleChangeListenerFail().listenForASingleChange();
SingleChangeListenerFail.observableValue.set( "Here be a change." );
SingleChangeListenerFail.observableValue.set( "Here be another change that you probably don't want." );
new SingleChangeListenerCorrect().listenForASingleChange();
SingleChangeListenerCorrect.observableValue.set( "Here be a change." );
SingleChangeListenerCorrect.observableValue.set( "Here be another change but you'll never know." );
}
static class SingleChangeListenerFail
{
static SimpleStringProperty observableValue = new SimpleStringProperty();
public void listenForASingleChange()
{
observableValue.addListener(this::changed);
}
private<T> void changed( ObservableValue<? extends T> observable, T oldValue, T newValue )
{
System.out.println( "New Value: " + newValue );
observableValue.removeListener(this::changed);
}
}
static class SingleChangeListenerCorrect
{
static SimpleStringProperty observableValue = new SimpleStringProperty();
ChangeListener<String> lambdaRef = this::changed;
public void listenForASingleChange()
{
observableValue.addListener(lambdaRef);
}
private<T> void changed( ObservableValue<? extends T> observable, T oldValue, T newValue )
{
System.out.println( "New Value: " + newValue );
observableValue.removeListener(lambdaRef);
}
}
}
Would have been nice to not need lambdaRef in this case.
참고URL : https://stackoverflow.com/questions/23983832/is-method-reference-caching-a-good-idea-in-java-8
'Program Tip' 카테고리의 다른 글
Heroku 서버 상태 이해 143 (0) | 2020.10.27 |
---|---|
std :: unique_ptr을 선언하는 방법과 그 용도는 무엇입니까? (0) | 2020.10.27 |
Android에서 Git을 사용하는 방법? (0) | 2020.10.27 |
IE8 브라우저 모드와 문서 모드 (0) | 2020.10.27 |
모바일 장치에서 웹 사이트를 디버깅하는 방법은 무엇입니까? (0) | 2020.10.27 |