netty / netty

Netty project - an event-driven asynchronous network application framework

Home Page:http://netty.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MqttEncoder optimisation proposal

Munoon opened this issue · comments

Current MqttEncoder make a lot of class casts, which can be improved with the methods, that returns concreete type.

For example, this code:

static ByteBuf doEncode(ChannelHandlerContext ctx,
MqttMessage message) {
switch (message.fixedHeader().messageType()) {
case CONNECT:
return encodeConnectMessage(ctx, (MqttConnectMessage) message);

May be refactored as follow:

switch (message.fixedHeader().messageType()) {
    case CONNECT:
        return encodeConnectMessage(ctx, message.asConnectMessage());

Where default implementation of asConnectMessage method throws ClassCastException while MqttConnectMessage overrides this method and return this.

A simple benchmark shows, that such refactoring sligtly improves the performance:

Benchmark                                  Mode  Cnt       Score       Error  Units
MqttEncoderBench.doEncode                 thrpt   20  407587,038 ± 21522,217  ops/s
MqttEncoderBench.doEncodeWithTypeMethods  thrpt   20  419308,953 ± 10095,722  ops/s
Benchmark code
package io.netty.handler.codec.mqtt;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.microbench.channel.EmbeddedChannelHandlerContext;
import io.netty.microbench.util.AbstractMicrobenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

@State(Scope.Benchmark)
public class MqttEncoderBench extends AbstractMicrobenchmark {
    MqttMessage[] messages;
    ChannelHandlerContext context;

    @Setup
    public void initData() {
        messages = new MqttMessage[] {
                MqttMessageBuilders.connect().clientId("testClientId").build(),
                MqttMessageBuilders.connAck().returnCode(MqttConnectReturnCode.CONNECTION_ACCEPTED).build(),
                MqttMessageBuilders.subscribe().messageId(1).addSubscription(MqttQoS.AT_MOST_ONCE, "/test").build(),
                MqttMessageBuilders.subAck().packetId(1).addGrantedQos(MqttQoS.AT_MOST_ONCE).build(),
                MqttMessageBuilders.publish().topicName("/test").payload(Unpooled.EMPTY_BUFFER).qos(MqttQoS.AT_LEAST_ONCE).build(),
                MqttMessageBuilders.disconnect().build(),
                MqttMessage.PINGREQ,
                MqttMessage.PINGRESP
        };

        ByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT;
        ChannelHandler handler = new MqttDecoder();
        EmbeddedChannel channel = new EmbeddedChannel();
        context = new EmbeddedChannelHandlerContext(alloc, handler, channel) {
            @Override
            protected void handleException(Throwable t) {
                t.printStackTrace();
            }
        };
    }

    @Benchmark
    public void doEncode(Blackhole blackhole) {
        for (MqttMessage message : messages) {
            ByteBuf byteBuf = MqttEncoder.doEncode(context, message);
            byteBuf.release();
            blackhole.consume(byteBuf);
        }
    }

    @Benchmark
    public void doEncodeWithTypeMethods(Blackhole blackhole) {
        for (MqttMessage message : messages) {
            ByteBuf byteBuf = MqttEncoder.doEncodeTypeMethod(context, message);
            byteBuf.release();
            blackhole.consume(byteBuf);
        }
    }
}

If this proposal look good for you, I'd be happy to create a pull request.

I think what we have now is more clean and also the perf win is not huge.