Skip to content
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

Interceptor optimization #1993

Open
brucelwl opened this issue Jul 26, 2020 · 9 comments
Open

Interceptor optimization #1993

brucelwl opened this issue Jul 26, 2020 · 9 comments

Comments

@brucelwl
Copy link

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:

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

})
public class MyInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class);

    private Properties properties;

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("invoke");
        return invocation.proceed();
    }
}
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

})
public class MyInterceptor2 implements Interceptor {
    
}

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

@brucelwl
Copy link
Author

brucelwl commented Aug 8, 2020

@harawata Can you reply to my issue?

@harawata
Copy link
Member

harawata commented Aug 8, 2020

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?

@brucelwl
Copy link
Author

brucelwl commented Aug 8, 2020

@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
image

Proxy 1 time vs Proxy 5 times
image

Proxy 1 time vs Proxy 8 times
image

@brucelwl
Copy link
Author

brucelwl commented Aug 8, 2020

@harawata
see PR https://github.com/mybatis/mybatis-3/pull/2001/files please
This PR has two main purposes:

  1. The first goal is performance improvement
  2. The second goal is to make the generated proxy class structure clearer

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

@harawata
Copy link
Member

harawata commented Aug 8, 2020

Thank you, @brucelwl .

I will look into it, but it may take some time as I don't have much spare time lately.

@brucelwl
Copy link
Author

brucelwl commented Aug 9, 2020

@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

@harawata
Copy link
Member

@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.

@brucelwl
Copy link
Author

@harawata The performance problem is only relative. I just want to have an optimized solution

@wushp
Copy link

wushp commented Oct 26, 2020

@harawata ありがとう!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants