Skip to content

Commit

Permalink
Import x9 library for blazing fast interthread message passing
Browse files Browse the repository at this point in the history
  • Loading branch information
ktf committed Sep 19, 2024
1 parent d15478c commit 11febb5
Show file tree
Hide file tree
Showing 16 changed files with 2,584 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Framework/Foundation/3rdparty/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ o2_add_library(Catch2
TARGETVARNAME targetName
PUBLIC_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/catch2)

o2_add_library(X9
SOURCES x9/x9.c
TARGETVARNAME targetName
PUBLIC_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/x9)

install(FILES ${CMAKE_CURRENT_LIST_DIR}/catch2/catch_amalgamated.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_CURRENT_LIST_DIR}/x9/x9.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
24 changes: 24 additions & 0 deletions Framework/Foundation/3rdparty/x9/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BSD 2-Clause License

Copyright (c) 2023, Diogo Flores

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
68 changes: 68 additions & 0 deletions Framework/Foundation/3rdparty/x9/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
X9
---

*Note: I am currently working on v2.0 which will bring further performance
gains and flexibility to the user at the (unfortunate) cost of breaking the
current API. I expect to release v2.0 by September/October 2024 and for it to be the
last major/API-breaking change to X9.*

X9 is a low level high performance message passing library, based on a
lock-free ring buffer implemented with atomic variables, for low latency
multithreading work.
It allows for multiple producers/consumers to concurrently access the same
underlying ring buffer and provides both spinning (busy loop) and non-blocking
read and write functions.

The library is based on three concepts:

- A message, which is a user defined struct.
- A `x9_inbox`, which is where messages are both written to and read from.
- A `x9_node`, which is an abstraction that unifies x9_inbox(es).

The library provides multiple functions to both read from and write to a
`x9_inbox`, as the right choice depends on the user needs.
Refer to _x9.h_, where all public functions are properly documented and their
use cases explained, and the examples folder for comprehensive examples of
different architectures.

Enabling `X9_DEBUG` at compile time will print to stdout the reason why the
functions `x9_inbox_is_valid` and `x9_node_is_valid` returned 'false' (if they
indeed returned 'false'), or why `x9_select_inbox_from_node` did not return a
valid `x9_inbox`.

To use the library just link with x9.c and include x9.h where necessary.

X9 is as generic, performant and intuitive as C allows, without forcing the
user to any sort of build system preprocessor hell, pseudo-C macro based
library, or worse.
It was originally written in the context of an high-frequency-trading system
that this author developed, and was made public in June of 2023.
It is released under the BSD-2-Clause license, with the purpose of serving
others and other programs.

Benchmarks
---

- Single producer and single consumer transmitting 100M messages via a single
`x9_inbox`.
- Run on Intel 11900k (cpu and ram untuned).
- _Msg size_ expressed in bytes, and _Inbox size_ in number of slots in the
ring buffer.
- _(See /profiling for how to run your own tests)_

```
Inbox size | Msg size | Time (secs) | Msgs/second
-------------------------------------------------
1024 | 16 | 4.00 | 25.01M
1024 | 32 | 4.03 | 24.82M
1024 | 64 | 4.17 | 23.99M
-------------------------------------------------
2048 | 16 | 3.90 | 25.63M
2048 | 32 | 3.96 | 25.26M
2048 | 64 | 4.09 | 24.43M
-------------------------------------------------
4096 | 16 | 3.86 | 25.88M
4096 | 32 | 3.92 | 25.50M
4096 | 64 | 4.05 | 24.67M
-------------------------------------------------
```
241 changes: 241 additions & 0 deletions Framework/Foundation/3rdparty/x9/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
Notes:
---

- Inbox N is always associated with a message of type N, hence to inbox 1
producers will write messages of type 1 and consumers will read messages of
type 1, and so on.

- Each producer/consumer runs in a unique thread.

- All x9_inbox sizes equal 4. This is not ideal for performance and should not
be used as a guideline. The reason why I use such a small buffer is because
I wanted to saturate the inbox and make it much more likely to trigger a data
race (which there is none).

- All examples/tests can be run by executing ./run_examples.sh

```
──────▷ write to
─ ─ ─ ▷ read from
```

Examples
---
```
x9_example_1.c:
One producer
One consumer
One message type
┌────────┐ ┏━━━━━━━━┓ ┌────────┐
│Producer│──────▷┃ inbox ┃◁ ─ ─ ─│Consumer│
└────────┘ ┗━━━━━━━━┛ └────────┘
This example showcases the simplest (multi-threading) pattern.
Data structures used:
- x9_inbox
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_write_to_inbox_spin
- x9_read_from_inbox_spin
- x9_free_inbox
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
```
-------------------------------------------------------------------------------
```
x9_example_2.c
Two producers.
One consumer and producer simultaneously.
One consumer.
┌────────┐ ┏━━━━━━━━┓ ┏━━━━━━━━┓
│Producer│─────▷┃ ┃ ┌────────┐ ┃ ┃
└────────┘ ┃ ┃ │Consumer│ ┃ ┃ ┌────────┐
┃inbox 1 ┃◁ ─ ─ │ and │─────▷┃inbox 2 ┃◁ ─ ─ │Consumer│
┌────────┐ ┃ ┃ │Producer│ ┃ ┃ └────────┘
│Producer│─────▷┃ ┃ └────────┘ ┃ ┃
└────────┘ ┗━━━━━━━━┛ ┗━━━━━━━━┛
This example showcase using multiple threads to write to the same inbox,
using multiple message types, the 'x9_node' abstraction, and
respective create/free and select functions.
Data structures used:
- x9_inbox
- x9_node
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_create_node
- x9_node_is_valid
- x9_select_inbox_from_node
- x9_write_to_inbox_spin
- x9_read_from_inbox_spin
- x9_free_node_and_attached_inboxes
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
```
-------------------------------------------------------------------------------
```
x9_example_3.c
Two producers and simultaneously consumers.
┌────────┐ ┏━━━━━━━━┓ ┌────────┐
│Producer│──────▷┃inbox 1 ┃◁ ─ ─ ─│Producer│
│ │ ┗━━━━━━━━┛ │ │
│ and │ │ and │
│ │ ┏━━━━━━━━┓ │ │
│Consumer│─ ─ ─ ▷┃inbox 2 ┃◁──────│Consumer│
└────────┘ ┗━━━━━━━━┛ └────────┘
This example showcases the use of: 'x9_write_to_inbox'
and 'x9_read_from_inbox', which, unlike 'x9_write_to_inbox_spin' and
'x9_read_from_inbox_spin' do not block until are able to write/read a msg.
Data structures used:
- x9_inbox
- x9_node
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_create_node
- x9_node_is_valid
- x9_select_inbox_from_node
- x9_write_to_inbox
- x9_read_from_inbox
- x9_free_node_and_attached_inboxes
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
```
-------------------------------------------------------------------------------
```
x9_example_4.c
One producer broadcasts the same message to three inboxes.
Three consumers read from each inbox.
One message type.
┏━━━━━━━━┓ ┌────────┐
┌──▷┃ inbox ┃◁─ ─ ─│Consumer│
│ ┗━━━━━━━━┛ └────────┘
┌────────┐ │ ┏━━━━━━━━┓ ┌────────┐
│Producer│───┼──▷┃ inbox ┃◁─ ─ ─│Consumer│
└────────┘ │ ┗━━━━━━━━┛ └────────┘
│ ┏━━━━━━━━┓ ┌────────┐
└──▷┃ inbox ┃◁─ ─ ─│Consumer│
┗━━━━━━━━┛ └────────┘
This example showcases the use of 'x9_broadcast_msg_to_all_node_inboxes'.
Data structures used:
- x9_inbox
- x9_node
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_create_node
- x9_node_is_valid
- x9_broadcast_msg_to_all_node_inboxes
- x9_read_from_inbox_spin
- x9_free_node_and_attached_inboxes
IMPORTANT:
- All inboxes must receive messages of the same type (or at least of the
same size) that its being broadcasted.
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
```
-------------------------------------------------------------------------------
```
x9_example_5.c
Three producers.
Three consumers reading concurrently from the same inbox.
One message type.
┌────────┐
┌────────┐ ┏━━━━━━━━┓ ─ ─│Consumer│
│Producer│──────▷┃ ┃ │ └────────┘
├────────┤ ┃ ┃ ┌────────┐
│Producer│──────▷┃ inbox ┃◁──┤─ ─│Consumer│
├────────┤ ┃ ┃ └────────┘
│Producer│──────▷┃ ┃ │ ┌────────┐
└────────┘ ┗━━━━━━━━┛ ─ ─│Consumer│
└────────┘
This example showcases the use of 'x9_read_from_shared_inbox'.
Data structures used:
- x9_inbox
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_write_to_inbox_spin
- x9_read_from_shared_inbox
- x9_free_inbox
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
- Each consumer processes at least one message.
```
-------------------------------------------------------------------------------
```
x9_example_6.c
One producer
Two consumers reading from the same inbox concurrently (busy loop).
One message type.
┏━━━━━━━━┓ ┌────────┐
┃ ┃ ─ ─│Consumer│
┌────────┐ ┃ ┃ │ └────────┘
│Producer│──────▷┃ inbox ┃◁ ─
└────────┘ ┃ ┃ │ ┌────────┐
┃ ┃ ─ ─│Consumer│
┗━━━━━━━━┛ └────────┘
This example showcases the use of 'x9_read_from_shared_inbox_spin'.
Data structures used:
- x9_inbox
Functions used:
- x9_create_inbox
- x9_inbox_is_valid
- x9_write_to_inbox_spin
- x9_read_from_shared_inbox_spin
- x9_free_inbox
Test is considered passed iff:
- None of the threads stall and exit cleanly after doing the work.
- All messages sent by the producer(s) are received and asserted to be
valid by the consumer(s).
- Each consumer processes at least one message.
```
-------------------------------------------------------------------------------
26 changes: 26 additions & 0 deletions Framework/Foundation/3rdparty/x9/examples/run_examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#/bin/bash

echo "- Running examples with GCC with \"-fsanitize=thread,undefined\" enabled.";
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_1.c ../x9.c -o X9_TEST_1 -fsanitize=thread,undefined -D X9_DEBUG
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_2.c ../x9.c -o X9_TEST_2 -fsanitize=thread,undefined -D X9_DEBUG
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_3.c ../x9.c -o X9_TEST_3 -fsanitize=thread,undefined -D X9_DEBUG
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_4.c ../x9.c -o X9_TEST_4 -fsanitize=thread,undefined -D X9_DEBUG
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_5.c ../x9.c -o X9_TEST_5 -fsanitize=thread,undefined -D X9_DEBUG
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_6.c ../x9.c -o X9_TEST_6 -fsanitize=thread,undefined -D X9_DEBUG

./X9_TEST_1; ./X9_TEST_2; ./X9_TEST_3; ./X9_TEST_4; ./X9_TEST_5; ./X9_TEST_6
rm X9_TEST_1 X9_TEST_2 X9_TEST_3 X9_TEST_4 X9_TEST_5 X9_TEST_6

echo ""
echo "- Running examples with clang with \"-fsanitize=address,undefined,leak\" enabled.";

clang -Wextra -Wall -Werror -O3 -march=native x9_example_1.c ../x9.c -o X9_TEST_1 -fsanitize=address,undefined,leak -D X9_DEBUG
clang -Wextra -Wall -Werror -O3 -march=native x9_example_2.c ../x9.c -o X9_TEST_2 -fsanitize=address,undefined,leak -D X9_DEBUG
clang -Wextra -Wall -Werror -O3 -march=native x9_example_3.c ../x9.c -o X9_TEST_3 -fsanitize=address,undefined,leak -D X9_DEBUG
clang -Wextra -Wall -Werror -O3 -march=native x9_example_4.c ../x9.c -o X9_TEST_4 -fsanitize=address,undefined,leak -D X9_DEBUG
clang -Wextra -Wall -Werror -O3 -march=native x9_example_5.c ../x9.c -o X9_TEST_5 -fsanitize=address,undefined,leak -D X9_DEBUG
clang -Wextra -Wall -Werror -O3 -march=native x9_example_6.c ../x9.c -o X9_TEST_6 -fsanitize=address,undefined,leak -D X9_DEBUG

./X9_TEST_1; ./X9_TEST_2; ./X9_TEST_3; ./X9_TEST_4; ./X9_TEST_5; ./X9_TEST_6
rm X9_TEST_1 X9_TEST_2 X9_TEST_3 X9_TEST_4 X9_TEST_5 X9_TEST_6

Loading

0 comments on commit 11febb5

Please sign in to comment.