Open brucelwl opened 4 years ago
@harawata Can you reply to my issue?
Hello @brucelwl ,
I'm sorry, but I am not so familiar with this topic. Could you post some data showing the 'certain performance loss', please?
@harawata I did a test. If an interface is proxy only once, the performance is much higher than that of proxy multiple times, and the performance increases in proportion to the number of agents
environment: windows 10 JDK:JDK 1.8.0_171, Java HotSpot(TM) 64-Bit Server VM, 25.171-b11 CPU: i5 pc memory:16G Other: all defaults Benchmarking tools:JMH
Benchmark code
public interface ExecutorHandler {
int getProxyCount();
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter);
}
public class ExecutorHandlerImpl implements ExecutorHandler {
private int count;
public ExecutorHandlerImpl(int count) {
this.count = count;
}
@Override
public int getProxyCount() {
return count;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return 10 * 20 / 5 * 2 + 1 + 15;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter) {
return null;
}
}
public class Plugin implements InvocationHandler {
private final Object target;
private Plugin(Object target) {
this.target = target;
}
public static Object wrap(Object target) {
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target));
}
return target;
}
private static Class<?>[] getAllInterfaces(Class<?> type) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
interfaces.addAll(Arrays.asList(type.getInterfaces()));
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
public class InterceptorChain {
public Object pluginAll(ExecutorHandler target) {
int proxyCount = target.getProxyCount();
for (int i = 0; i < proxyCount; i++) {
target = (ExecutorHandler) Plugin.wrap(target);
}
return target;
}
}
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 1)
@Threads(15)
@State(Scope.Benchmark)
@Measurement(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ProxyBenchmarkTest {
private static final Logger logger = LoggerFactory.getLogger(ProxyBenchmarkTest.class);
ExecutorHandler executorHandler;
ExecutorHandler executorHandler2;
@Setup
public void setup() throws Exception {
InterceptorChain interceptorChain = new InterceptorChain();
executorHandler = (ExecutorHandler) interceptorChain.pluginAll(new ExecutorHandlerImpl(1));
executorHandler2 = (ExecutorHandler) interceptorChain.pluginAll(new ExecutorHandlerImpl(15));
}
@Benchmark
public void jdkProxyN(Blackhole blackhole) throws Exception {
MappedStatement mappedStatement = new MappedStatement(
"select * from user info", 5,500,"c://aaa.mapper");
List<Object> query = executorHandler2.query(mappedStatement, 45);
blackhole.consume(query);
}
@Benchmark
public void jdkProxy1(Blackhole blackhole) throws Exception {
MappedStatement mappedStatement = new MappedStatement(
"select * from user info", 5,500,"c://aaa.mapper");
List<Object> query = executorHandler.query(mappedStatement, 45);
blackhole.consume(query);
}
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder().include(ProxyBenchmarkTest.class.getName())
//.output("benchmark/jedis-Throughput.log")
.forks(0)
.build();
new Runner(options).run();
}
}
Proxy 1 time vs Proxy 3 times
Proxy 1 time vs Proxy 5 times
Proxy 1 time vs Proxy 8 times
@harawata see PR https://github.com/mybatis/mybatis-3/pull/2001/files please This PR has two main purposes:
The current implementation code will make the proxy class nest another proxy. The more interceptors, the more nested the proxy class. But in fact, if only one proxy class is used, the class structure will be clearer
Thank you, @brucelwl .
I will look into it, but it may take some time as I don't have much spare time lately.
@harawata Another more optimized way is to use the decorator pattern to form an interceptor chain, so that there is no need to use a dynamic proxy to implement the interceptor, If you agree, I can submit a pr
@brucelwl ,
Just curious, how many plugins do you use in your project? I don't use plugins very often and have never had any performance issue myself.
Anyway, I need some time to understand the current design before answering your question. It would be easier for us to understand your proposals if you submit the change as a PR indeed, but please do understand there is no guarantee that your PRs will be merged.
@harawata The performance problem is only relative. I just want to have an optimized solution
@harawata ありがとう!
If there are multiple interceptors intercepting the same method, the same number of dynamic proxy classes will be created. However, the dynamic proxy has certain performance loss. I hope that it can be optimized to avoid the creation of multiple dynamic proxy classes. example:
This will cause two dynamic proxy classes to be created !!!
At present, Mybatis supports four types of interface interception
ParameterHandler
,ResultSetHandler
,StatementHandler
,Executor
Proposal 1: change to responsibility chain mode,developers can provide specific implementation classes of these interfaces to intercept corresponding methods
Proposal 2: Only a proxy class can be used to intercept interface methods,The advantage of this approach is that it does not change the existing interceptor implementation logic