10、cglib、jdk動的エージェント呼び出しパフォーマンステスト
18673 ワード
説明
ここではcglibとjdkダイナミックエージェントを比較します.結局、工業界ではjava assistまたはasmを使用して呼び出し速度を比較するのは奇妙です(速度jdkダイナミックエージェントの作成は速いに違いありません).
バージョン:cglib 2.2.2 jdk 1.8.0_77
主に以下の文章を参考にしていますが、それぞれの言い方が異なり、おかしいです.http://www.cnblogs.com/haiq/p/4304615.htmlhttp://adolphor.com/blog/2016/12/14/java-proxy-performance.html
最初の結論はjdk 7以降jdkダイナミックエージェントがcglibより速いことです.2番目の結論はcglibがjdkダイナミックエージェントより速いことです.実験してみました.2つのコードは、私のwin 7にあります.
じっけんきかいはいち
結果は一致し、最初の結論も正しいし、2番目の結論も正しい.リアルタイムはそうですか.
いいえ、2つのcglib実装のソースコードを参照すると、少し異なります.違いは
じっけんコードひかく
これは前のdemoのような書き方で、setSuperclassやinvokeSuperという方法が使われていることがわかります.
区别が见えない!!!この方式はsetSuperClassやinvokeSuperではなくsetInterfacesやinvokeである実装クラス、実装クラスのfastclass(前節ではinvokeとinvokeSuperの違いを分析し、invokeは「別のobj」に送らなければならない.そうしないと死循環を招く)
じっけんけっかいせき
なぜ2つのコードの結果の差がそんなに大きいのかcglibの速いコードは、invoke、すなわち前節のソースコード分析におけるf 1(すなわち実装クラスのfastclass)を用いて、対応するclass decompileは以下の通りである.
このfastClassのinvoke関数は非常に簡単で、正常な状況は1つしかありません.それはcase 0です.では、なぜinvokeSuperとsetSuperClassの実現方式が遅いのでしょうか.invokeSuperはf 2、つまりenhanceのfastClassを使っています.この関数はたくさんあります.invokeSuperでf 2を呼び出すinvokeを少し感じて、26種類のcaseを処理します.
だからcglibの2つの実現方式、invokeSuper+setSuperClassは永遠にinvoke+setInterfacesより遅いです同じ情況の下で、後者の生成する関数はもっと多くて、後者のswitchの判断するcaseももっとnet.sf.cglib.proxy.MethodProxy#invoke比net.sf.cglib.proxy.MethodProxy#invokeSuper快
では、cglib invoke+setInterfaces方式とjdkダイナミックエージェントは、上記の例を通じて、cglibが実装クラスの関数の個数と関係があることを見ることができます.結局、switchの各index
カスタム実験
Java benchmark、すなわちjmhパッケージを使用してテスト
CountServicesは、これらのインタフェースa 0、test 1()などのCountServiceImplを提供しています.これらのインタフェースは、何もしていません.主に関数呼び出しの時間を比較します.
じっけんけっか
インタフェースは29の関数を提供して、実験は比較します
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
7.385 ± 0.749
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
6.164 ± 0.711
ops/s
cglibはjdkダイナミックエージェントより少し速い
インタフェースは19個の関数を保持します
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
15.126 ± 0.730
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
9.790 ± 1.008
ops/s
cglibはjdkダイナミックエージェントの1.5倍の速度です
インタフェースは49個の関数を保持し、平均呼び出し
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
4.987 ± 0.859
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
3.826 ± 0.259
ops/s
インタフェースは49の関数を保持し、リストの最後の関数のみを呼び出します.
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
193.406 ± 6.209
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
347.192 ± 26.464
ops/s
このときjavaは反射を利用してcglibより毎回switchが後ろcaseより速い
結論
1.同様の場合、cglibの2つの実現方式は、invokeSuper+setSuperClassがinvoke+setInterfacesよりも永遠に遅い.cglib invoke+setInterfacesメソッドの数が少ない場合、関数の平均呼び出しの場合、jdkProxyよりも速く、関数の増加に伴い、優位性はますます明らかになっていない.あるレベルに達すると、jdkダイナミックエージェントよりも遅いに違いない.cglib invoke+setInterfacesは、jdkダイナミックエージェントよりも特定の関数(switchで後ろのcase)を呼び出すのが遅い
考える
cglibのボトルネックはnet.を呼び出すことである.sf.cglib.reflect.FastClass#invoke(int,java.lang.Object,java.lang.Object[])ではswitchのcaseが必要です:n個の関数があれば、一つ一つ比較しなければなりません.複雑度O(n)ここにkey->behaviorのマッピングがあればいいのですが、現在はありません.ここでasmで二分検索を書くことができれば、cglibはずっと速くなり、O(log 2 n)になり、時間的に飛躍するが、このfastclassは醜く見える.(現在の最新3.2.5のバージョンもこのブロックを変更していません)
refer
http://www.cnblogs.com/haiq/p/4304615.htmlhttp://adolphor.com/blog/2016/12/14/java-proxy-performance.htmlhttps://zeroturnaround.com/rebellabs/testing-the-performance-of-4-java-runtime-code-generators-cglib-javassist-jdk-proxy-byte-buddy/https://github.com/cglib/cglib/releases/tag/RELEASE_3_2_5http://mvnrepository.com/artifact/cglib/cglib/3.2.5
転記先:https://www.jianshu.com/p/1aaacf92e2cd
ここではcglibとjdkダイナミックエージェントを比較します.結局、工業界ではjava assistまたはasmを使用して呼び出し速度を比較するのは奇妙です(速度jdkダイナミックエージェントの作成は速いに違いありません).
バージョン:cglib 2.2.2 jdk 1.8.0_77
主に以下の文章を参考にしていますが、それぞれの言い方が異なり、おかしいです.http://www.cnblogs.com/haiq/p/4304615.htmlhttp://adolphor.com/blog/2016/12/14/java-proxy-performance.html
最初の結論はjdk 7以降jdkダイナミックエージェントがcglibより速いことです.2番目の結論はcglibがjdkダイナミックエージェントより速いことです.実験してみました.2つのコードは、私のwin 7にあります.
じっけんきかいはいち
結果は一致し、最初の結論も正しいし、2番目の結論も正しい.リアルタイムはそうですか.
いいえ、2つのcglib実装のソースコードを参照すると、少し異なります.違いは
じっけんコードひかく
// cglib
Test cglibProxy = CglibProxyTest.newProxyInstance(TestImpl.class);
public static Test newProxyInstance(Class targetInstanceClazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetInstanceClazz);
enhancer.setCallback(new CglibProxyTest());
return (Test) enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
これは前のdemoのような書き方で、setSuperclassやinvokeSuperという方法が使われていることがわかります.
// cglib
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{CountService.class});
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
CountService delegate = new CountServiceImpl();
CountService cglibProxy = createCglibDynamicProxy(delegate);
区别が见えない!!!この方式はsetSuperClassやinvokeSuperではなくsetInterfacesやinvokeである実装クラス、実装クラスのfastclass(前節ではinvokeとinvokeSuperの違いを分析し、invokeは「別のobj」に送らなければならない.そうしないと死循環を招く)
じっけんけっかいせき
なぜ2つのコードの結果の差がそんなに大きいのかcglibの速いコードは、invoke、すなわち前節のソースコード分析におけるf 1(すなわち実装クラスのfastclass)を用いて、対応するclass decompileは以下の通りである.
//cglib f1,
package cglib;
import cglib.CglibLearn;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;
public class CglibLearn$service$$FastClassByCGLIB$$eebe5c1
extends FastClass {
public CglibLearn$service$$FastClassByCGLIB$$eebe5c1(Class class_) {
super(class_);
}
public int getIndex(Signature signature) {
String string = signature.toString();
switch (string.hashCode()) {
case -909388886: {
if (!string.equals("say()V")) break;
return 0;
}
}
return -1;
}
public int getIndex(String string, Class[] arrclass) {
String string2 = string;
Class[] arrclass2 = arrclass;
switch (string2.hashCode()) {
case 113643: {
if (!string2.equals("say")) break;
arrclass2 = arrclass2;
switch (arrclass2.length) {
case 0: {
return 0;
}
}
break;
}
default: {
break;
}
}
return -1;
}
public int getIndex(Class[] arrclass) {
Class[] arrclass2 = arrclass;
Class[] arrclass3 = arrclass2;
arrclass2.length;
return -1;
}
public Object invoke(int n, Object object, Object[] arrobject) throws InvocationTargetException {
try {
switch (n) {
case 0: {
((CglibLearn.service)object).say();
return null;
}
}
}
catch (Throwable v0) {
throw new InvocationTargetException(v0);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public Object newInstance(int n, Object[] arrobject) throws InvocationTargetException {
CglibLearn.service service2;
CglibLearn.service service3 = service2;
CglibLearn.service service4 = service2;
int n2 = n;
try {
}
catch (Throwable v4) {
throw new InvocationTargetException(v4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public int getMaxIndex() {
return 0;
}
}
このfastClassのinvoke関数は非常に簡単で、正常な状況は1つしかありません.それはcase 0です.では、なぜinvokeSuperとsetSuperClassの実現方式が遅いのでしょうか.invokeSuperはf 2、つまりenhanceのfastClassを使っています.この関数はたくさんあります.invokeSuperでf 2を呼び出すinvokeを少し感じて、26種類のcaseを処理します.
public Object invoke(int n, Object object, Object[] arrobject) throws InvocationTargetException {
CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c = (CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c)object;
try {
switch (n) {
case 0: {
return new Boolean(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.equals(arrobject[0]));
}
case 1: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.toString();
}
case 2: {
return new Integer(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.hashCode());
}
case 3: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Class[])arrobject[0], (Object[])arrobject[1], (Callback[])arrobject[2]);
}
case 4: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Callback[])arrobject[0]);
}
case 5: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.newInstance((Callback)arrobject[0]);
}
case 6: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.setCallbacks((Callback[])arrobject[0]);
return null;
}
case 7: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.setCallback(((Number)arrobject[0]).intValue(), (Callback)arrobject[1]);
return null;
}
case 8: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.say();
return null;
}
case 9: {
CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$SET_THREAD_CALLBACKS((Callback[])arrobject[0]);
return null;
}
case 10: {
CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$SET_STATIC_CALLBACKS((Callback[])arrobject[0]);
return null;
}
case 11: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getCallback(((Number)arrobject[0]).intValue());
}
case 12: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getCallbacks();
}
case 13: {
return CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$findMethodProxy((Signature)arrobject[0]);
}
case 14: {
CglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$STATICHOOK1();
return null;
}
case 15: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$finalize$0();
return null;
}
case 16: {
return new Boolean(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$equals$1(arrobject[0]));
}
case 17: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$toString$2();
}
case 18: {
return new Integer(cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$hashCode$3());
}
case 19: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$clone$4();
}
case 20: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.CGLIB$say$5();
return null;
}
case 21: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait();
return null;
}
case 22: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait(((Number)arrobject[0]).longValue(), ((Number)arrobject[1]).intValue());
return null;
}
case 23: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.wait(((Number)arrobject[0]).longValue());
return null;
}
case 24: {
return cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.getClass();
}
case 25: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.notify();
return null;
}
case 26: {
cglibLearn$service$$EnhancerByCGLIB$$e9bfe24c.notifyAll();
return null;
}
}
}
catch (Throwable v1) {
throw new InvocationTargetException(v1);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
だからcglibの2つの実現方式、invokeSuper+setSuperClassは永遠にinvoke+setInterfacesより遅いです同じ情況の下で、後者の生成する関数はもっと多くて、後者のswitchの判断するcaseももっとnet.sf.cglib.proxy.MethodProxy#invoke比net.sf.cglib.proxy.MethodProxy#invokeSuper快
では、cglib invoke+setInterfaces方式とjdkダイナミックエージェントは、上記の例を通じて、cglibが実装クラスの関数の個数と関係があることを見ることができます.結局、switchの各index
カスタム実験
Java benchmark、すなわちjmhパッケージを使用してテスト
package cglibtest;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
public class DynamicProxyPerformanceTest {
static CountService jdkProxy;
static CountService cglibProxy;
static {
try {
init();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void init() throws Exception {
CountService delegate = new CountServiceImpl();
long time = System.currentTimeMillis();
jdkProxy = createJdkDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JDK Proxy: " + time + " ms");
time = System.currentTimeMillis();
cglibProxy = createCglibDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create CGLIB Proxy: " + time + " ms");
}
public static void main(String[] args) throws Exception {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".//");
try {
init();
} catch (Exception e) {
e.printStackTrace();
}
Options opt = new OptionsBuilder()
.include(DynamicProxyPerformanceTest.class.getSimpleName())
.forks(1)
.warmupIterations(3)
.measurementIterations(5)
.build();
new Runner(opt).run();
}
@Benchmark
public void cglibProxy() {
int count = 1000000;
for (int i = 0; i < count; i++) {
cglibProxy.count();
cglibProxy.test1();
cglibProxy.test2();
cglibProxy.test3();
cglibProxy.test4();
cglibProxy.test5();
cglibProxy.test6();
cglibProxy.test7();
cglibProxy.test8();
cglibProxy.test9();
cglibProxy.t0();
cglibProxy.t1();
cglibProxy.t2();
cglibProxy.t3();
cglibProxy.t4();
cglibProxy.t5();
cglibProxy.t6();
cglibProxy.t7();
cglibProxy.t8();
cglibProxy.t9();
cglibProxy.a1();
cglibProxy.a2();
cglibProxy.a3();
cglibProxy.a4();
cglibProxy.a5();
cglibProxy.a6();
cglibProxy.a7();
cglibProxy.a8();
cglibProxy.a9();
}
}
@Benchmark
public void testjdkProxy() {
int count = 1000000;
for (int i = 0; i < count; i++) {
jdkProxy.count();
jdkProxy.test1();
jdkProxy.test2();
jdkProxy.test3();
jdkProxy.test4();
jdkProxy.test5();
jdkProxy.test6();
jdkProxy.test7();
jdkProxy.test8();
jdkProxy.test9();
jdkProxy.t0();
jdkProxy.t1();
jdkProxy.t2();
jdkProxy.t3();
jdkProxy.t4();
jdkProxy.t5();
jdkProxy.t6();
jdkProxy.t7();
jdkProxy.t8();
jdkProxy.t9();
jdkProxy.a1();
jdkProxy.a2();
jdkProxy.a3();
jdkProxy.a4();
jdkProxy.a5();
jdkProxy.a6();
jdkProxy.a7();
jdkProxy.a8();
jdkProxy.a9();
}
}
private static CountService createJdkDynamicProxy(
final CountService delegate) {
CountService jdkProxy = (CountService) Proxy
.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[] { CountService.class },
new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
return method.invoke(delegate, objects);
}
}
private static CountService createCglibDynamicProxy(
final CountService delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
// enhancer.setSuperclass(CountServiceImpl.class);
enhancer.setInterfaces(new Class[] { CountService.class });
CountService cglibProxy = (CountService) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
return methodProxy.invoke(delegate, objects);
}
}
}
CountServicesは、これらのインタフェースa 0、test 1()などのCountServiceImplを提供しています.これらのインタフェースは、何もしていません.主に関数呼び出しの時間を比較します.
じっけんけっか
インタフェースは29の関数を提供して、実験は比較します
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
7.385 ± 0.749
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
6.164 ± 0.711
ops/s
cglibはjdkダイナミックエージェントより少し速い
インタフェースは19個の関数を保持します
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
15.126 ± 0.730
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
9.790 ± 1.008
ops/s
cglibはjdkダイナミックエージェントの1.5倍の速度です
インタフェースは49個の関数を保持し、平均呼び出し
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
4.987 ± 0.859
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
3.826 ± 0.259
ops/s
インタフェースは49の関数を保持し、リストの最後の関数のみを呼び出します.
Benchmark
Mode
Cnt
Score
Units
DynamicProxyPerformanceTest.cglibProxy
thrpt
5
193.406 ± 6.209
ops/s
DynamicProxyPerformanceTest.testjdkProxy
thrpt
5
347.192 ± 26.464
ops/s
このときjavaは反射を利用してcglibより毎回switchが後ろcaseより速い
結論
1.同様の場合、cglibの2つの実現方式は、invokeSuper+setSuperClassがinvoke+setInterfacesよりも永遠に遅い.cglib invoke+setInterfacesメソッドの数が少ない場合、関数の平均呼び出しの場合、jdkProxyよりも速く、関数の増加に伴い、優位性はますます明らかになっていない.あるレベルに達すると、jdkダイナミックエージェントよりも遅いに違いない.cglib invoke+setInterfacesは、jdkダイナミックエージェントよりも特定の関数(switchで後ろのcase)を呼び出すのが遅い
考える
cglibのボトルネックはnet.を呼び出すことである.sf.cglib.reflect.FastClass#invoke(int,java.lang.Object,java.lang.Object[])ではswitchのcaseが必要です:n個の関数があれば、一つ一つ比較しなければなりません.複雑度O(n)ここにkey->behaviorのマッピングがあればいいのですが、現在はありません.ここでasmで二分検索を書くことができれば、cglibはずっと速くなり、O(log 2 n)になり、時間的に飛躍するが、このfastclassは醜く見える.(現在の最新3.2.5のバージョンもこのブロックを変更していません)
refer
http://www.cnblogs.com/haiq/p/4304615.htmlhttp://adolphor.com/blog/2016/12/14/java-proxy-performance.htmlhttps://zeroturnaround.com/rebellabs/testing-the-performance-of-4-java-runtime-code-generators-cglib-javassist-jdk-proxy-byte-buddy/https://github.com/cglib/cglib/releases/tag/RELEASE_3_2_5http://mvnrepository.com/artifact/cglib/cglib/3.2.5
転記先:https://www.jianshu.com/p/1aaacf92e2cd