-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Interceptor optimization #1993
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@harawata Can you reply to my issue? |
Hello @brucelwl , I'm sorry, but I am not so familiar with this topic. |
@harawata environment: 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();
}
} |
@harawata
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 |
Just curious, how many plugins do you use in your project? Anyway, I need some time to understand the current design before answering your question. |
@harawata The performance problem is only relative. I just want to have an optimized solution |
@harawata ありがとう! |
I finally took some time to look into it and my conclusion is : the number of dynamic proxies does not have significant impact on performance. Here is the JMH project I used. There are three methods.
Please try the benchmark yourself and see if you reach the same conclusion. Just to be sure, I ran the same benchmarks against PR #2001 (master branch merged locally).
I also ran the benchmarks against PR #3396 .
[1] This is just an experiment, but after applying the following patch to the master, the throughput of
diff --git a/src/main/java/org/apache/ibatis/plugin/Plugin.java b/src/main/java/org/apache/ibatis/plugin/Plugin.java
index feae8724c8..8da6166095 100644
--- a/src/main/java/org/apache/ibatis/plugin/Plugin.java
+++ b/src/main/java/org/apache/ibatis/plugin/Plugin.java
@@ -41,12 +41,6 @@ public class Plugin implements InvocationHandler {
}
public static Object wrap(Object target, Interceptor interceptor) {
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
- Class<?> type = target.getClass();
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
- if (interfaces.length > 0) {
- return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
- }
return target;
}
|
@harawata Although this optimization does not approach the performance of zero interceptors, even with a small amount of optimization, it is still a worthwhile thing to do, and it has not made things worse than before |
Interceptor optimization mybatis#1993
You raised two points .
To be frank, I don't think PR #2001 makes the structure clearer and the performance "gain" is trivial even with five interceptors (I have no idea how many plugins you guys use, but five seems like "a lot"), so I don't see any advantage of the design change. |
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
The text was updated successfully, but these errors were encountered: