The ISMFIX Engine is a Java implementation of the FIX Trading Community™'s FIX Protocol designed for ultra low latency applications. The fix engine requires zero external dependencies to function.
The engine code above the nio socket channel layer produces zero garbage. This includes:
Formatters; parsers; FIX-state loggers (Logging Sequence numbers and inbound messages when required); and message parsing, formatting, reading and writing.
The FIX engine employs Java's nio layer which will produce a small amount of garbage. The FIX engine has a spin mode which can be employed to prevent a portion of this. When running the FIX engine with a kernel bypass card, the nio layer garbage concern is eliminated.
The engine provides the client level with local caches of reusable objects, e.g. buffers, messages, fields, etc.
Many reusable objects are explicitly obtained from and returned to the cache by the client layer.
The raw received message can be detached from the engine's control, consumed by the user in any thread, then returned
to the object's cache using a thread-safe reclaim method.
Sending a messages is synchronous. You know immediately if the message was sent or not.
Streaming a FIX message into an outgoing byte buffer is fast.
The values are sorted as they are added to the message. Adding tag value pairs close to the sorted order minimizes the cost of this pre-sort.
The formatters use a byte buffer writer to format directly into the outgoing byte buffer. There is no need to copy the formatted value.
Sending a batch of messages when a single event occurs is faster by pre-staging the outgoing messages you intend to send.
A pre-staged outgoing message is serialized into a byte buffer with a few fields, e.g. price, quantity, etc., written with dummy values. As part of the pre-staging effort, we bind byte buffer writers
to update these dummy fields directly into the byte buffer. When you are ready to send, write the final values and immediately send the byte buffer.
We ensure the byte count remains the same by using our number formatter's ability to pad numbers and define a double's precision.
Raw messages are delivered to the client level to parse, (deserialize), and process. There are three levels of message parsing available:
Level-1 (Lookup): Lookup a tag value is a sequential search over the byte array to locate the start and stop indexes of the desired tag value pair.
When you need only a few tag values and know the approximate byte location within the message, this is the fastest. If you can guarantee the exact location, there is none faster.
This lookup technique is heavily used by the session-level message handling logic.
Prefer a stream processing approach with minimal overhead for Lookup speed? Use the static multi-integer-array that represents the message with each tag value pair being a row that marks both the tag and value's start and stop index positions within the message.
Level-2 (FiXMessage with Fields): Moving away from the minimalist parsing approach to support heavier processing requirements on the message, create a FixMessage with Fields for each tag value pair. Validation of the tags is not performed, i.e., each tag value in the received messages will have a Field. Fields are zero copy objects that employ byte buffer readers to mark the start and end indexes of the tags and values. FixMessages provide:
quick checks for "has any Data Field" or "has any Group Field."
properly parsed Data fields to correctly mark the binary data boundaries.
quick Field lookups.
Fields that cache values you parse for repeated use.
Level-3 (FixMessage with group structure): For intensive group level processing, validate the Fields in the Level-2 parsed message, then add the Group and Group-Segment structure to the message, and finally, move the Fields into the group-segments as required. This level requires all user defined fields to be declared.
The ISMFIX Engine has timing examples you can run for each of the above micro benchmarks.
The majority of the processing within the engine is performed without Atomics and without locks (synchronize). Data is staged for a thread to work without the contention of another thread using Atomics. Locks (synchronize) are required in two areas: to wake a blocked thread (when in blocking mode) and to call the nio socket layer functions to read, write or select. Lock contention is summarized:
The nio socket level reader and writer methods: Moving to a kernel bypass will eliminate this contention.
The nio socket selector: The reader thread can be deployed in a loop-mode which bypasses the selector; however, the reader will occasionally wakeup a connector thread when a Connection closes. Moving to a kernel bypass will eliminate this contention.
The engine moves data between a wire reader thread and a client handler thread. The reader will wake the client thread when it is blocking. The client thread can be placed in a loop mode to bypass the contention.
When you have cancel-on-disconnect agreements in place with all brokers, this feature takes you out of the market very quickly.
A StandOff to Active method will restart the connections that are still in their scheduled window of operation.