• Version 1.7.1
Show / Hide Table of Contents

Benchmark Sample

This sample shows how to measure latency and use the warm-up feature.

Using OpenOnload® Network Stack

OpenOnload is part of Solarflare’s suite of network acceleration technologies.

To achieve better results, use the latency profile. For example:

./run-under-onload.sh 54 192.168.28.1 20054

TCP Loopback Acceleration

The TCP loopback acceleration is turned off by default. It is configured via the environment variables EF_TCP_CLIENT_LOOPBACK and EF_TCP_SERVER_LOOPBACK.

Activate

export EF_TCP_CLIENT_LOOPBACK=1
export EF_TCP_SERVER_LOOPBACK=1

Verify

echo $EF_TCP_CLIENT_LOOPBACK
echo $EF_TCP_SERVER_LOOPBACK

Source code


using Benchmark.Latency;
using OnixS.Cme.ILink3.Testing;
using OnixS.Cme.ILink3;
using OnixS.SimpleBinaryEncoding;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Threading;
using System;
using Microsoft.CodeAnalysis;
using System.Security.Cryptography;

namespace Benchmark
{
    internal static class Benchmark
    {
        static SessionTimeMarks[] marks;
        static bool active;
        static int sendCounter = 0;
        static int recvCounter = 0;

        static readonly ManualResetEventSlim ready = new(true);

        static void FillSettings(SessionSettings settings, bool useEmulator)
        {
            settings.LicenseStore = "../../../../../license|../../../../license|.";
            settings.TradingSystemVersion = "1.1.0";
            settings.TradingSystemName = "Trading System";
            settings.TradingSystemVendor = "OnixS";

            if (useEmulator)
            {
                settings.SessionId = "XXX";
                settings.FirmId = "001";
                settings.AccessKey = "dGVzdHRlc3R0ZXN0dA==";
                settings.SecretKey = "dGVzdHRlc3R0ZXN0dGVzdHRlc3R0";
                settings.KeepAliveInterval = 5000;
                settings.ReconnectAttempts = 0;
            }
            else
            {
                settings.SessionId = ConfigurationManager.AppSettings["SessionId"];
                settings.FirmId = ConfigurationManager.AppSettings["FirmId"];
                settings.AccessKey = ConfigurationManager.AppSettings["AccessKey"];
                settings.SecretKey = ConfigurationManager.AppSettings["SecretKey"];
            }
        }


        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("\n\tUsage: Benchmark [MarketSegmentId] [Host] [Port] (numberOfMessages) (sendPeriodUsec) (warmupPeriodUsec)\n");

            const int MainThreadAffinity = 1;
            const int ReceivingThreadAffinity = 2;
            const int SendingThreadAffinity = 3;
            const int EmulatorThreadAffinity = 4;

            bool useEmulator = false;

            int marketSegmentId = 99;
            string host = "127.0.0.1";
            int port = 49152;

#if DEBUG
            int numberOfMessages = 1000;
            Console.WriteLine("\n\n\nWARNING: Don't use the Debug build for benchmarking. The debug version can run 10-100 times slower!!!\n\n\n");
            return;
#else
            int numberOfMessages = 20000;
#endif

            var sendPeriodUsec = 100;
            var warmupPeriodUsec = 10;

            if (args.Length < 3)
            {
                useEmulator = true;
                Console.WriteLine("WARNING: Using Gateway Emulator!");
            }
            else
            {
                marketSegmentId = int.Parse(args[0]);
                host = args[1];
                port = int.Parse(args[2]);

                numberOfMessages = args.Length > 3 ? int.Parse(args[3]) : numberOfMessages;
                sendPeriodUsec = args.Length > 4 ? int.Parse(args[4]) : sendPeriodUsec;
                warmupPeriodUsec = args.Length > 5 ? int.Parse(args[5]) : warmupPeriodUsec;
            }

            Console.WriteLine($"\nSettings:\n\tnumberOfMessages={numberOfMessages}\n\tsendPeriodUsec={sendPeriodUsec}\n\twarmupPeriodUsec={warmupPeriodUsec}"
                + $"\n\tmainThreadAffinity={MainThreadAffinity}\n\treceivingThreadAffinity={ReceivingThreadAffinity}\n\tsendingThreadAffinity={SendingThreadAffinity}\n\temulatorThreadAffinity={EmulatorThreadAffinity}\n");

            try
            {
                Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"\nWARNING: unable to the set real-time process priority: {ex.Message}.\n To set the real-time priority run this benchmark ");
                Console.WriteLine(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "under administrator." : "with root access (e.g. using 'sudo').");
            }

            ThisThread.SetAffinity(new int[] { MainThreadAffinity });

            marks = new SessionTimeMarks[numberOfMessages];

            try
            {
                var settings = new SessionSettings();
                FillSettings(settings, useEmulator);

                Task emulatorTask = null;
                Gateway emulator = null;
                if (useEmulator)
                {
                    emulator = new Gateway(port, settings);
                    emulator.SendReceiveSpinTimeout = TimeSpan.FromMilliseconds(10);
                    emulatorTask = Task.Run(() =>
                    {
                        ThisThread.SetAffinity(new int[] { EmulatorThreadAffinity });

                        emulator.AcceptSession();

                        var report = emulator.Encoder().Wrap(TemplateId.ExecutionReportTradeOutright);

                        emulator.WaitUntilTerminate(TimeSpan.FromMinutes(3), (IMessage message) =>
                            {
                                if (message.TemplateID == TemplateId.NewOrderSingle)
                                {
                                    report.SetUnsignedLong(Tag.UUID, emulator.Uuid);
                                    report.SetUnsignedInteger(Tag.SeqNum, message.GetUnsignedInteger(Tag.SeqNum));

                                    emulator.Send(report);
                                }
                            });
                    });
                }

                using (Session session = new (settings, marketSegmentId, SessionStorageType.MemoryBasedStorage))
                {
                    session.Warning += (object sender, SessionWarningEventArgs e) => Console.WriteLine("WARNING: " + e.Description);
                    session.Error += (object sender, SessionErrorEventArgs e) => Console.WriteLine("ERROR: " + e.Description);

                    session.InboundApplicationMessage += Session_InboundApplicationMessage;
                    session.BytesReceived += Session_OnBytesReceived;
                    session.MessageSending += Session_OnSendingMessage;
                    session.ReceivingThreadAffinity = new[] { ReceivingThreadAffinity };
                    session.SendingThreadAffinity = new[] { SendingThreadAffinity };

                    session.ReceiveSpinningTimeout = 10000;
                    session.ReuseEventArguments = true;
                    session.ReuseInboundMessage = true;

                    session.Reset();
                    session.Connect(host, port);

                    var order = CreateOrder(session.CreateEncoder());

                    const int MaxNumberOfWarmupMessages = 20000;
                    int numberOfWarmupMessages = Math.Min(MaxNumberOfWarmupMessages, numberOfMessages);

                    Console.WriteLine("Warm-up to avoid JIT compilation issues and make the first calls fast..");
                    Measure(session, order, numberOfWarmupMessages, sendPeriodUsec, warmupPeriodUsec);

                    Console.WriteLine("Measure..");
                    Measure(session, order, numberOfMessages, sendPeriodUsec, warmupPeriodUsec);

                    session.Disconnect();
                }

                ReportResults("Latency", marks, numberOfMessages);

                Console.WriteLine("Done.");

                if (useEmulator && emulatorTask != null)
                {
                    if(emulatorTask.Wait(TimeSpan.FromMinutes(1)) && emulator != null)
                        emulator.Dispose();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex.ToString());
                Console.WriteLine("Press Enter to exit...");
                Console.ReadLine();
            }

            NLog.LogManager.Shutdown();
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        private static void Measure(Session session, IMessage order, int numberOfMessages, int sendPeriodUsec, int warmupPeriodUsec)
        {
            ResetSessionTimeMarks();

            active = true;
            recvCounter = 0;

            const string ShortClientId = "ClientID";
            const string LongClientId = "ClientIDClientIDClientIDClientID";

            for (sendCounter = 0; sendCounter < numberOfMessages; ++sendCounter)
            {
                ready.Wait();
                ready.Reset();

                order.SetString(Tag.ClOrdID, sendCounter % 2 == 0 ? ShortClientId : LongClientId);

                marks[sendCounter].SendStart = TimestampHelper.Ticks;

                session.Send(order);

                marks[sendCounter].OverallSendFinish = TimestampHelper.Ticks;

                if (warmupPeriodUsec > 0)
                {
                    var start = TimestampHelper.Ticks;

                    do
                    {
                        SpinWait(warmupPeriodUsec);
                        session.WarmUp(order);
                    } while (TimestampHelper.ElapsedMicroseconds(start) < sendPeriodUsec);
                }
                else if (sendPeriodUsec > 0)
                {
                    SpinWait(sendPeriodUsec);
                }
            }

            ready.Wait();
            active = false;
        }

        private static void ResetSessionTimeMarks()
        {
            sendCounter = 0;
            recvCounter = 0;

            for (var i = 0; i < marks.Length; ++i)
            {
                marks[i].Reset();
            }
        }

        private static void ProcessAndReportDurations(string durationName, List<double> durations)
        {
            durations.Sort();

            Console.WriteLine(
                    "\t{0,-22} min: {1,-8:F} median: {2,-8:F} 99%: {3,-8:F}",
                    durationName,
                    durations[0],
                    durations[durations.Count / 2],
                    durations[Convert.ToInt32(Math.Ceiling(durations.Count * 99 / 100.0)) - 1]
                );
        }

        private static void ReportResults(string testName, SessionTimeMarks[] marksArray, int count)
        {
            var baseName = testName + '_';

            using StreamWriter receiveStream = new(baseName + "recv.csv", true)
                , sendStream = new(baseName + "send.csv", true)
                , overallSendStream = new(baseName + "overallsend.csv", true)
                , oneWayStream = new(baseName + "oneway.csv", true)
                , sendAndReceiveStream = new(baseName + "sendrecv.csv", true);

            var sendDurations = new List<double>();
            var overallSendDurations = new List<double>();
            var receiveDurations = new List<double>();
            var sendAndReceiveDurations = new List<double>();
            var oneWayDurations = new List<double>();

            const int MaxNumberStringLength = 26;
            var buff = new char[MaxNumberStringLength];

            ReadOnlySpan<char> Format(double value)
            {
                const string NumberFormat = "0.000";

                if (value.TryFormat(buff, out var length, NumberFormat))
                    return new ReadOnlySpan<char>(buff, 0, length);

                return value.ToString(NumberFormat);
            }

            for (var i = 0; i < count; ++i)
            {
                var m = marksArray[i];

                var receiveDuration = m.RecvSpan / TimestampHelper.TicksPerMicrosecond;
                receiveDurations.Add(receiveDuration);
                receiveStream.WriteLine(Format(receiveDuration));

                var sendDuration = m.SendSpan / TimestampHelper.TicksPerMicrosecond;
                sendDurations.Add(sendDuration);
                sendStream.WriteLine(Format(sendDuration));

                var overallSendDuration = m.OverallSendSpan / TimestampHelper.TicksPerMicrosecond;
                overallSendDurations.Add(overallSendDuration);
                overallSendStream.WriteLine(Format(overallSendDuration));

                var sendAndReceiveDuration = sendDuration + receiveDuration;
                sendAndReceiveDurations.Add(sendAndReceiveDuration);
                sendAndReceiveStream.WriteLine(Format(sendAndReceiveDuration));

                var oneWayDuration = m.OneWaySpan / TimestampHelper.TicksPerMicrosecond;
                oneWayDurations.Add(oneWayDuration);
                oneWayStream.WriteLine(Format(oneWayDuration));
            }

            Console.WriteLine("\n{0} (microseconds): ", testName);

            ProcessAndReportDurations("Receive", receiveDurations);
            ProcessAndReportDurations("Send", sendDurations);
            ProcessAndReportDurations("Send+Receive", sendAndReceiveDurations);
            ProcessAndReportDurations("Overall Send", overallSendDurations);
            ProcessAndReportDurations("One-Way (Round-Trip/2)", oneWayDurations);
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        private static void Session_OnBytesReceived(ReadOnlySpan<byte> args)
        {
            if (!active)
                return;

            marks[recvCounter].RecvStart = TimestampHelper.Ticks;
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        private static void Session_InboundApplicationMessage(object sender, InboundMessageEventArgs e)
        {
            if (!active)
                return;

            marks[recvCounter].RecvFinish = TimestampHelper.Ticks;

            ++recvCounter;

            ready.Set();
        }

        [MethodImpl(MethodImplOptions.AggressiveOptimization)]
        private static void Session_OnSendingMessage(ReadOnlySpan<byte> args)
        {
            if (!active)
                return;

            marks[sendCounter].SendFinish = TimestampHelper.Ticks;
        }

        private static IMessage CreateOrder(IEncoder encoder)
        {
            const int DefaultSecurityId = 5424; // Channel 54 - "CME Equity Options"
            const decimal DefaultPrice = -46190;
            const ulong PartyDetailsListReqId = 1;

            IMessage message = encoder.Wrap(TemplateId.NewOrderSingle);

            message
                .SetUnsignedLong(Tag.PartyDetailsListReqID, PartyDetailsListReqId)
                .SetByte(Tag.Side, (byte)Side.Buy)
                .SetString(Tag.SenderID, "GFP")
                .SetString(Tag.ClOrdID, "OrderId")
                .SetUnsignedLong(Tag.OrderRequestID, 1u)
                .SetString(Tag.Location, "UK")
                .SetChar(Tag.OrdType, (char)OrderType.Limit)
                .SetByte(Tag.TimeInForce, (byte)TimeInForce.Day)
                .SetInteger(Tag.SecurityID, DefaultSecurityId)
                .SetUnsignedInteger(Tag.OrderQty, 1)
                .SetDecimal(Tag.Price, DefaultPrice)
                .SetByte(Tag.ManualOrderIndicator, (byte)ManualOrdInd.Automated)
                .SetChar(Tag.ExecutionMode, (char)ExecMode.Aggressive)
                .SetByte(Tag.ExecInst, (byte)ExecInst.AON);

            return message;
        }

        private static void SpinWait(int microseconds)
        {
            var start = TimestampHelper.Ticks;

            while (TimestampHelper.ElapsedMicroseconds(start) < microseconds)
                Thread.SpinWait(10);
        }
    }
}

In this article
Back to top Copyright © Onix Solutions.
Generated by DocFX