0%

CGLIB 和 JDK 动态代理性能测试

背景

问题描述

Spring Framework 提供了两种动态代理的实现,CGLIB 和 JDK 动态代理,并且提供了 proxy-target-class。

两者实现方式上并不相同,初步看来 CGLIB 性能上应该有优势,但是没有实际数据作为支撑。

从 Spring Boot 2.0 开始,默认的 AOP 策略已经更换为 CGLIB,即 proxy-target-class = true。

可以先看结论再看分析

参考来源

主要参考了如下两篇文章的数据:

两者的结论完全相反

后续主要参考了:

提供了一定思路,但是结论并不相同

情况分析

CGLIB 基于类与接口的区别

关键代码

1
2
3
4
5
6
7
8
9
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.isInterface()) {
this.setInterfaces(new Class[]{superclass});
} else if (superclass != null && superclass.equals(Object.class)) {
this.superclass = null;
} else {
this.superclass = superclass;
}
}

基于类和接口生成的代理类,均继承自 FastClass,其中前者生成的代码 switch 和 case 数量均较多,可能是两种方式下性能差距的原因之一。

CGLIB 与 Method 数量的关系

CGLIB 的 invoke 基于 switch 实现,Method 数量较多的情况下,性能下降严重。

JDK 动态代理的实现

实现相对简单,代理类实现了被代理接口,并生成了 java.lang.reflect.Method。

实验内容

实验环境

  • OS: 64bit Mac OS X 10.13.4
  • CPU: Intel Core i7-4770HQ @ 2.20GHz
  • RAM: 16GB
  • JDK: Oracle JDK 10.0.1
  • CGLIB: Spring 5.0.5.RELEASE 内嵌
  • JMH: 1.20

实验数据

方式 分数
cglibBigClassEndProxy 339.777
cglibBigClassProxy 331.552
cglibBigEndProxy 358.999
cglibBigProxy 359.476
cglibSmallClassProxy 578.817
cglibSmallProxy 618.582
jdkBigEndProxy 432.813
jdkBigProxy 434.224
jdkSmallProxy 432.592

说明

  • Big:含有 50 个 Method
  • Small:含有 5 个 Method
  • End:使用最后的 Method,否则使用首个
  • Class:使用作为被代理的内容

实验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.example.demo;

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 org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

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 {

private static final int TEST_COUNT = 50_0000;
private static NopBigService jdkBigProxy;
private static NopBigService cglibBigProxy;
private static NopBigService cglibBigClassProxy;
private static NopSmallService jdkSmallProxy;
private static NopSmallService cglibSmallProxy;
private static NopSmallService cglibSmallClassProxy;

private static void init() {

NopBigService nopBigService = new NopBigServiceImpl();

NopSmallService nopSmallService = new NopSmallServiceImpl();
jdkBigProxy = createJdkBigDynamicProxy(nopBigService);
cglibBigProxy = createCglibBigDynamicProxy(nopBigService);
cglibBigClassProxy = createCglibBigClassDynamicProxy(nopBigService);
jdkSmallProxy = createJdkSmallDynamicProxy(nopSmallService);
cglibSmallProxy = createCglibSmallDynamicProxy(nopSmallService);
cglibSmallClassProxy = createCglibSmallClassDynamicProxy(nopSmallService);

}

static {
init();
}

public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(DynamicProxyPerformanceTest.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.measurementIterations(1)
.build();
new Runner(opt).run();
}

@Benchmark
public void jdkBigProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
jdkBigProxy.m1();
}
}

@Benchmark
public void jdkBigEndProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
jdkBigProxy.m49();
}
}

@Benchmark
public void jdkSmallProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
jdkSmallProxy.m1();
}
}

@Benchmark
public void cglibBigProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibBigProxy.m1();
}
}

@Benchmark
public void cglibBigEndProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibBigProxy.m49();
}
}

@Benchmark
public void cglibSmallProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibSmallProxy.m1();
}
}

@Benchmark
public void cglibBigClassProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibBigClassProxy.m1();
}
}


@Benchmark
public void cglibBigClassEndProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibBigClassProxy.m49();
}
}

@Benchmark
public void cglibSmallClassProxy() {
for (int i = 0; i < TEST_COUNT; i++) {
cglibSmallClassProxy.m1();
}
}


private static NopBigService createJdkBigDynamicProxy(
final NopBigService delegate) {
return (NopBigService) Proxy
.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{NopBigService.class},
new JdkHandler(delegate));
}

private static NopSmallService createJdkSmallDynamicProxy(
final NopSmallService delegate) {
return (NopSmallService) Proxy
.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{NopSmallService.class},
new JdkHandler(delegate));
}

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 NopBigService createCglibBigDynamicProxy(
final NopBigService delegate) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setSuperclass(NopBigService.class);
return (NopBigService) enhancer.create();
}

private static NopSmallService createCglibSmallDynamicProxy(
final NopSmallService delegate) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setSuperclass(NopSmallService.class);
return (NopSmallService) enhancer.create();
}

private static NopBigService createCglibBigClassDynamicProxy(
final NopBigService delegate) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setSuperclass(NopBigServiceImpl.class);
return (NopBigService) enhancer.create();
}

private static NopSmallService createCglibSmallClassDynamicProxy(
final NopSmallService delegate) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setSuperclass(NopSmallServiceImpl.class);
return (NopSmallService) enhancer.create();
}

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);
}
}
}

其他

对于 CGLIB 和 JDK 动态代理,均可将生成的代理类保存,便于查看,具体方法可以自行搜索。

结论

  1. CGLIB 对类进行代理比接口慢,随着 Method 数量上升差距逐渐减少
  2. CGLIB 随着 Method 数量上升,性能出现比较严重的下降
  3. CGLIB 被调用 Method 的位置对于性能无影响(可能与相关优化有关)
  4. JDK 动态代理性能不受 Method 数量影响
  5. CGLIB 的性能在 Method 较少时优于 JDK 动态代理,较多时相反