CoralReactor UDP Roundtrip Benchmark (compared to regular Java NIO)

In this article we show CoralReactor roundtrip performance numbers for UDP packets and compare them with a regular Java NIO implementation.

Environment

Machine: Intel i7-3770K quad-core (4 x 3.50GHz) Ubuntu box over-clocked to 4.50Ghz

OS: Ubuntu Server 12.10 (Kernel: 3.5.7-03050732-generic)

Java: Oracle JVM 1.8.0_45-b14 (Hotspot: 25.45-b02 mixed mode)

Benchmark mechanics

  • UDP server listens to packets and echoes them back
  • UDP client starts timer, sends a packet, waits for the echo back, receives the echo, stops the timer and calculates the roundtrip latency
  • UDP client only sends next packet after it gets the echo back from server
  • Message size is set to 256 bytes
  • UDP server runs on its own JVM
  • UDP client runs on its own JVM
  • Client and server run on the same machine
  • Client and server talk to each other over loopback / localhost / 127.0.0.1
  • 500k messages are sent first to warmup
  • 1 million messages are sent to benchmark after warmup
  • UDP server reactor thread is pinned to its own isolated CPU core
  • UDP client reactor thread is pinned to its own isolated CPU core

Results

Iterations: 1,000,000 messages
Message size: 256 bytes
Avg Time: 3.684 micros
GC Activity: none
StDev: 246.11 nanos
Min Time: 3.236 micros
Max Time: 97.65 micros
75% = [avg: 3.609 micros, stdev: 46.01 nanos, max: 3.696 micros]
90% = [avg: 3.639 micros, stdev: 89.11 nanos, max: 3.98 micros]
99% = [avg: 3.675 micros, stdev: 141.06 nanos, max: 4.1 micros]
99.9% = [avg: 3.68 micros, stdev: 155.8 nanos, max: 5.569 micros]
99.99% = [avg: 3.683 micros, stdev: 178.03 nanos, max: 7.827 micros]
99.999% = [avg: 3.683 micros, stdev: 187.27 nanos, max: 16.128 micros]

Command-line

UDP server:

java -server -verbose:gc -Xbootclasspath/p:/home/soliveira/workspace/CoralReactor-boot-jdk8/target/coralreactor-boot-jdk8.jar -cp target/coralreactor-all.jar -DensureBootstrap=true -DnioReactorProcToBind=2 -DlogColors=true com.coralblocks.coralreactor.client.bench.roundtrip.PongUdpServer

UDP client:

java -server -verbose:gc -Xbootclasspath/p:/home/soliveira/workspace/CoralReactor-boot-jdk8/target/coralreactor-boot-jdk8.jar -cp ../CoralBits/target/coralbits.jar:target/coralreactor-all.jar -DensureBootstrap=true -DnioReactorProcToBind=3 -DdetailedBenchmarker=true -DbenchStdev=true -DlogColors=true com.coralblocks.coralreactor.client.bench.roundtrip.PingUdpClient

Screenshot

(Click on the image to enlarge)
coralreactor-udp-roundtrip

Regular Java NIO

We can also run the same benchmark code but this time using the standard Java NIO classes that come with the JDK and our demo version of CoralReactor without our low-latency tricks (standard Java NIO implementation).

Results

Iterations: 1,000,000 messages
Message size: 256 bytes
Avg Time: 36.331 micros
GC Activity: 3 (198 millis of latency)
StDev: 55372.91 nanos
Min Time: 11.717 micros
Max Time: 43.906 millis
75% = [avg: 35.639 micros, stdev: 2791.42 nanos, max: 36.782 micros]
90% = [avg: 35.865 micros, stdev: 2598.23 nanos, max: 37.275 micros]
99% = [avg: 36.063 micros, stdev: 2579.18 nanos, max: 43.647 micros]
99.9% = [avg: 36.184 micros, stdev: 2883.59 nanos, max: 57.404 micros]
99.99% = [avg: 36.206 micros, stdev: 2968.02 nanos, max: 65.672 micros]
99.999% = [avg: 36.219 micros, stdev: 4208.87 nanos, max: 2.118 millis]

Screenshot

(Click on the image to enlarge)
coralreactor-udp-roundtrip-nio

Source Code

Client code:

package com.coralblocks.coralreactor.client.bench.roundtrip;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;

import com.coralblocks.coralbits.bench.Benchmarker;
import com.coralblocks.coralreactor.client.AbstractUdpClient;
import com.coralblocks.coralreactor.client.Client;
import com.coralblocks.coralreactor.nio.NioReactor;
import com.coralblocks.coralreactor.util.Configuration;
import com.coralblocks.coralreactor.util.MapConfiguration;

public class PingUdpClient extends AbstractUdpClient {
	
	private final SocketAddress sendAddress;
	private final ByteBuffer msgToSend = ByteBuffer.allocateDirect(1024);
	private final Benchmarker bench;
	private final int messagesToWarmup;
	private final int messagesToBenchmark;
	private final int totalMessagesToReceive;
	private int messagesReceived;

	public PingUdpClient(NioReactor nio, String host, int port, Configuration config) {
	    
		super(nio, host, port, config);
	    
	    this.messagesToWarmup = config.getInt("messagesToWarmup");
	    this.messagesToBenchmark = config.getInt("messagesToBenchmark");
	    this.totalMessagesToReceive = messagesToWarmup + messagesToBenchmark;
	    
	    String sendHost = config.getString("sendHost");
	    int sendPort = config.getInt("sendPort");
	    
	    this.sendAddress = new InetSocketAddress(sendHost, sendPort);
	    
	    int msgSize = config.getInt("msgSize", 256); // if not specified, use 256 (default value)
	    
	    // initialize the message to send...
	    msgToSend.clear();
		for(int i = 0; i < msgSize; i++) {
			msgToSend.put((byte) i);
		}
		msgToSend.flip();
		
		this.bench = Benchmarker.create(messagesToWarmup);
    }
	
	@Override
	protected void handleOpened() {
		messagesReceived = 0;
		sendPacket(); // send first packet to start ping-pong benchmark...
	}

	private final void sendPacket() {
		bench.mark();
		msgToSend.position(0);
		send(sendAddress, msgToSend);
	}
	
	@Override
	protected void handleMessage(InetSocketAddress from, ByteBuffer packet) {
		bench.measure();
		if (++messagesReceived == totalMessagesToReceive) {
			close();
			bench.printResults();
		} else {
			sendPacket();
		}
	}
	
	public static void main(String[] args) {
		
		NioReactor nio = NioReactor.create();
		
		MapConfiguration config = new MapConfiguration();
		config.add("sendHost", "localhost");
		config.add("sendPort", 55555);
		config.add("msgSize", 256);
		config.add("messagesToWarmup", 500000);
		config.add("messagesToBenchmark", 1000000);
		
		Client ping = new PingUdpClient(nio, "localhost", 55556, config);
		ping.open();
		
		nio.start();
	}
}

Server code:

package com.coralblocks.coralreactor.client.bench.roundtrip;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;

import com.coralblocks.coralreactor.client.AbstractUdpClient;
import com.coralblocks.coralreactor.client.Client;
import com.coralblocks.coralreactor.nio.NioReactor;
import com.coralblocks.coralreactor.util.Configuration;
import com.coralblocks.coralreactor.util.MapConfiguration;

public class PongUdpServer extends AbstractUdpClient {
	
	private final SocketAddress replyAddress;
	
	public PongUdpServer(NioReactor nio, String host, int port, Configuration config) {
	    
		super(nio, host, port, config);
	    
	    String replyHost = config.getString("replyHost");
	    int replyPort = config.getInt("replyPort");
	    
	    this.replyAddress = new InetSocketAddress(replyHost, replyPort);
    }
	
	@Override
	protected void handleMessage(InetSocketAddress from, ByteBuffer packet) {
		this.send(replyAddress, packet);
	}
	
	public static void main(String[] args) {
		
		NioReactor nio = NioReactor.create();
		
		MapConfiguration config = new MapConfiguration();
		config.add("replyHost", "localhost");
		config.add("replyPort", 55556); // reply to port 55556
		
		Client pong = new PongUdpServer(nio, "localhost", 55555, config); // listen to port 55555
		pong.open();
		
		nio.start();
	}
}