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.
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 :
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.
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 |
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.
