Skip to content

Repository files navigation

License: MIT

fix-engine-benchmarks

This repository contains benchmarks of C++ FIX protocol engines. The goal is to measure performance under conditions that are as close to production as possible. Therefore, all benchmarks are executed with protocol validations enabled, ensuring the reported results reflect realistic production deployments rather than artificially optimised benchmark configurations. All benchmark messages include repeating groups containing two entries.

In addition to the summary tables below, each engine's benchmark directory ( fix_client_benchmarks/{engine}/ and fix_server_benchmarks/{engine}/ ) contains a results.txt with the raw console output from every mode tested, including configurations not reflected in the tables below.

Environment

Server : 2 x Intel Xeon Gold 6134 (2x8 physical cores @3.2 GHz), 256 GB DDR4, RHEL9.4 & GCC 11.4.1

NIC : Solarflare 8000 Series SFN8522 PLUS, 10GBE

Compiler: GCC 11.4.1

Compilation flags: -DNDEBUG -O3 -march=native -mtune=native

CPU isolation for pinning TX/RX threads: BOOT_IMAGE=/vmlinuz-5.14.0-427.42.1.el9_4.x86_64 root=/dev/mapper/rhel00-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=/dev/mapper/rhel00-swap rd.lvm.lv=rhel00/root rd.lvm.lv=rhel00/swap isolcpus=2,3,10,11 nohz_full=2,3,10,11 rcu_nocbs=2,3,10,11 mitigations=off rhgb quiet

Other tunings : Disabled hyperthreading, maximised CPU frequency, interleaved RAM access

Hwloc/lstopo output of the benchmark server is as below :

Benchmark Topology

FIX client latency benchmarks

FIX Client benchmarks measure application-to-wire latency, including message encoding and enqueueing the encoded messages to the NIC for 1 million messages.

Timestamps in the benchmarks below are recorded from within the application. They are all CPU based (RDTSCP).

Message : 8=FIXT.1.1|9=218|35=D|34=2| 49=CLIENT1|52=20251231-18:21:36.457245600| 56=EXECUTOR|50=SNDR_SUB|57=SRVR_SUB| 11=1|55=NOKIA.HE| 54=1|38=10|44=10000|40=2|59=0| 453=2| 448=PARTY1|447=D|452=1| 448=PARTY2|447=D|452=3| 60=20251231-18:21:36.457245600| 10=221|

All benchmarks were performed on the same network infrastructure and executed using Solarflare Onload 8.1.3.40, except for the engines using TCPDirect support.

FIX engine P50 P75 P90 P95 P99
Quickfix 17 7341 nanoseconds 7571 nanoseconds 7809 nanoseconds 7893 nanoseconds 9323 nanoseconds
FIX8 1.0.4 1666 nanoseconds 1755 nanoseconds 3180 nanoseconds 5047 nanoseconds 6155 nanoseconds
llfix 1.0.7 573 nanoseconds 798 nanoseconds 1044 nanoseconds 1588 nanoseconds 2663 nanoseconds
llfix 1.0.7 TCPDirect 493 nanoseconds 536 nanoseconds 685 nanoseconds 1437 nanoseconds 2629 nanoseconds

Note 1: llfix and Quickfix do not expose a prebuilt-order / pre-encoded-message API. Where an engine does provide this capability (Fix8), the reported numbers correspond to the equivalent full-construction mode for a fair comparison. The additional prebuilt-order-mode results are available in each engine's folder's 'results.txt' file.

FIX server throughput benchmarks

In this benchmark, the FIX server and FIX client applications run on the same host and communicate through the loopback interface. This setup minimises the impact of external networking factors, such as network latency and packet loss, allowing the benchmark to focus primarily on the server-side FIX receive path.

A single FIX client session is connected to the server and sends 1 million 35=D (New Order Single) messages.

Message : 8=FIXT.1.1|9=188|35=D|34=2| 49=CLIENT1|52=20251231-17:42:03.736004873| 56=EXECUTOR|11=1|55=BMWG.DE| 54=1|38=1|44=5|40=2|59=0| 453=2| 448=PARTY1|447=D|452=1| 448=PARTY2|447=D|452=3| 60=20251231-17:42:03.736004873| 10=077|

Throughput is measured using the following code snippet in each benchmark:

class FixServerUnderBenchmark
{
    private:
        std::atomic<uint32_t> m_execution_id = 0;
        std::atomic<uint64_t> m_first_message_timestamp_nanoseconds = 0;
        std::atomic<uint64_t> m_one_millionth_message_timestamp_nanoseconds = 0;
    public:
        void new_order_handler(...)
        {
            m_execution_id++;

            if(m_execution_id == 1)
            {
                m_first_message_timestamp_nanoseconds = Time::nanoseconds_monotonic();
            }
            else if(m_execution_id == 1'000'000)
            {
                m_one_millionth_message_timestamp_nanoseconds = Time::nanoseconds_monotonic();
                const uint64_t elapsed_ns = m_one_millionth_message_timestamp_nanoseconds - m_first_message_timestamp_nanoseconds;
                const double throughput = 1'000'000.0 * 1'000'000'000.0 / static_cast<double>(elapsed_ns);
                Console::print_colour(ConsoleColour::FG_YELLOW, "Throughput: " + std::to_string(throughput) + " msgs/sec\n");
            }
        }
        
        ...
FIX Engine Throughput
FIX8 1.0.4 193013.688163 msgs/sec
Quickfix 17 197700.072732 msgs/sec
llfix 1.0.7 231060.031247 msgs/sec

Running the benchmarks

Each benchmark directory contains its own build instructions.

The endpoints directory contains the endpoint applications used by the benchmarks:

  • 'server' : used as the FIX server endpoint for FIX client benchmarks.
  • 'clients' : used as the FIX client endpoint for FIX server benchmarks.

To analyse FIX messages from the endpoint perspective, you can use the deserialiser executable (built for RHEL) provided in the endpoints directory:

Option Description
-i Input serialisation path
-o Output file
-e Exclude timestamps from output
-t Use tag names instead of numbers

To build the deserialiser tool from its source, visit https://github.com/CorewareLtd/llfix#serialisations--deserialiser-tool.

About

Production-oriented benchmarks comparing the latency and throughput of C++ FIX engines

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages