LibHFTDocumentationLow-latency architecture

Technical reference · reviewed 26 October 2026

Low-latency FIX engine architecture

Low-latency FIX engine architecture in LibHFT starts with explicit session ownership and reusable message storage. Across C++, Java, .NET and Rust, these choices keep the work in the trading path visible: framing, validation, application callbacks, recovery, persistence and network I/O.

Session execution

Keep sequence state with its owner.

A FIX connection carries ordered state: the next inbound and outbound sequence numbers, pending writes, heartbeat deadlines and the replay window. LibHFT serializes mutations through the session’s owner. Foreign-thread requests use the provider’s supported command boundary; they do not write directly to its socket or framing buffer.

This reduces coordination on the send path and makes recovery decisions follow the same ordering as application sends. A deployment can have many sessions and threads; single-owner describes a session’s mutable state, rather than a promise that the whole process runs on one thread.

FlavorEngine and execution boundaryIntegration responsibility
C++The native session serve path owns receive/send, sequencing, timers and store access.Drive the selected runner consistently and keep foreign producers off owner-only state.
Java PureThe JVM engine owns the session. Worker and application-driven loop modes have different I/O and timer arrangements.Use the selected mode’s progression and command APIs; avoid mixing manual progression with a worker loop.
Java/JNIThe C++ engine owns the native session; the Java API exchanges commands and reusable event views across the JNI boundary.Respect the command/event producer and consumer roles and consume each view before reuse.
.NET PureThe C# engine’s RunOnce() progresses commands, peers, reads, pending writes and deadlines; Run() supplies its loop.Call owner-only methods from the owner. Foreign producers submit through TryEnqueueApplication.
.NET NativeThe C# API calls the C++ provider through the versioned hftnet C ABI and P/Invoke.Keep native resources and managed callback handles alive for the full period native code can reference them.
RustThe independent engine exposes caller-owned session state; its runners make I/O progression explicit.Keep the session, store and application with the owning loop and respect borrowed-buffer lifetimes.

Callbacks participate in this execution path. Blocking on a database, synchronous logging or waiting for a downstream queue can delay the next session pass. When handing business work to another thread, copy the required values into application-owned storage and define what happens when its queue fills.

Memory and message lifetime

Reuse storage; make retained data explicit.

Receive buffers, message views and outbound builders let the application process FIX fields without constructing a string or object for every tag. Size buffers for the intended message and repeating-group shapes during setup, then warm the paths you will use before measuring.

A view is a borrowed interpretation of bytes. It is useful while its backing storage is valid; it is not an immutable message archive. Before the engine advances or recycles that storage, consume the values synchronously or copy the subset needed later. This rule matters at Java event/poller boundaries, managed spans, native pointers and Rust borrows alike.

Example: receiving an execution report and retaining its order state

Conceptual callback flow; these names describe operations rather than a language-specific API:

on_execution_report(borrowed_view):
    order_id = read_order_id(borrowed_view)
    status = read_order_status(borrowed_view)
    update_owned_order_state(order_id, status)
    # Copy any variable-length values that must survive this callback.
    # A full downstream queue must produce an explicit application outcome.
    return

Keep a reference to owned order state, rather than storing the borrowed view for a later task. If full wire retention is required, use a configured audit/store path or a bounded application-owned copy and include its cost in the measurement.

Buffer reuse can remove repeated allocation from a prepared path. Initialization, capacity growth, first-use batching, dictionary loading, telemetry, auditing and the application still have their own resource costs. Measure the warmed workload and its allocation profile with the enabled features.

JVM and .NET integration

Control allocation at both sides of the boundary.

Java Pure and .NET Pure keep engine execution in their managed runtime. Reusable buffers and views reduce short-lived message objects; converting every field to a string, capturing closures, formatting logs or growing collections can add allocation back in the application.

Java/JNI and .NET Native move session work into the C++ core while retaining a managed application. They also introduce a native boundary: commands, event polling, buffer ownership and callback lifetime are part of the integration. A native core does not remove garbage collection from the containing JVM or .NET process.

Warm compilation and runtime initialization before collecting latency samples. Keep the runtime version, heap/GC settings, native library build and application callbacks fixed between comparisons. Inspect allocation and pause data alongside p50 and tail latency; a collector setting alone does not establish a latency bound.

Wire correctness

Separate framing from dictionary checks.

FIX framing identifies complete messages and checks wire integrity, including BodyLength and CheckSum. Dictionary validation adds message rules: required fields, value types, enums and repeating groups. Loading a QuickFIX-format XML dictionary happens outside the steady-state message exchange; enabled validation still performs work on each relevant message.

Keep the validation profile matched when comparing engines. A parser-only loop and a dictionary-validated session answer different questions. Use a custom dictionary when your counterparty requires custom fields or groups, and test invalid messages as well as the normal path.

Configuration fragment: enable a FIX 4.4 dictionary

Merge this fragment into the matching package’s working session configuration. Replace the dictionary path with your local FIX 4.4 XML file; keep the connection identities and endpoints from your configuration.

[DEFAULT]
HeartBtInt=30
UseDataDictionary=Y
DataDictionary=/absolute/path/FIX44.xml
SocketNoDelay=Y

SocketNoDelay controls TCP’s small-write delay where supported; it does not enable kernel bypass. For FIXT/application-version dictionaries and the protocol components beyond tag-value FIX, use the protocol coverage reference.

Recovery and durability

Measure the store policy you intend to operate.

Sequence tracking, replay and gap filling belong to the session path. The configured store determines which messages and sequence checkpoints survive a process or machine failure. Memory storage, buffered file writes and a synchronous durability policy have different latency and recovery properties.

Keep the provider, capacity, write/sync policy and replay behavior with a benchmark result. A successful send is not by itself proof that the record has reached durable storage. Exercise store failures and restart/replay with the same policy you intend to deploy, then follow the session recovery and high-availability guides.

Transport and measurement

Keep network acceleration and engine work visible.

Ordinary TCP is the baseline. AMD Solarflare Onload and NVIDIA/Mellanox VMA interpose a user-space stack on a socket path; TCPDirect and SocketXtreme use dedicated integration paths. Raw ef_vi and ibverbs receive paths have a different scope from a complete FIX TCP session. The kernel-bypass guide details the hardware, SDK and deployment distinctions.

CPU affinity, polling policy, NIC placement and competing work affect scheduling and tails. TLS, stores, dictionary checks and application callbacks must stay visible in the chosen profile. Select a transport that matches the NIC and provider build, then measure it under the expected arrival rate.

Codec microbenchmarks isolate parse/extract/build work. Full-session tests include the selected send/receive, session and response workflow. Open-loop response-time tests can also include the delay from the intended send slot; their numbers should be read with that clock boundary. See the measurement methodology, benchmarks by flavor and codec microbenchmarks for diagrams, percentiles and recorded conditions.