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)
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)
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(); } }