Skip to content

Commit 30680d5

Browse files
committed
Import x9 library for blazing fast interthread message passing
1 parent 2a45b16 commit 30680d5

File tree

16 files changed

+2584
-0
lines changed

16 files changed

+2584
-0
lines changed

Framework/Foundation/3rdparty/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,12 @@ o2_add_library(Catch2
1616
TARGETVARNAME targetName
1717
PUBLIC_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/catch2)
1818

19+
o2_add_library(X9
20+
SOURCES x9/x9.c
21+
TARGETVARNAME targetName
22+
PUBLIC_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/x9)
23+
1924
install(FILES ${CMAKE_CURRENT_LIST_DIR}/catch2/catch_amalgamated.hpp
2025
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
26+
install(FILES ${CMAKE_CURRENT_LIST_DIR}/x9/x9.h
27+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
BSD 2-Clause License
2+
3+
Copyright (c) 2023, Diogo Flores
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
X9
2+
---
3+
4+
*Note: I am currently working on v2.0 which will bring further performance
5+
gains and flexibility to the user at the (unfortunate) cost of breaking the
6+
current API. I expect to release v2.0 by September/October 2024 and for it to be the
7+
last major/API-breaking change to X9.*
8+
9+
X9 is a low level high performance message passing library, based on a
10+
lock-free ring buffer implemented with atomic variables, for low latency
11+
multithreading work.
12+
It allows for multiple producers/consumers to concurrently access the same
13+
underlying ring buffer and provides both spinning (busy loop) and non-blocking
14+
read and write functions.
15+
16+
The library is based on three concepts:
17+
18+
- A message, which is a user defined struct.
19+
- A `x9_inbox`, which is where messages are both written to and read from.
20+
- A `x9_node`, which is an abstraction that unifies x9_inbox(es).
21+
22+
The library provides multiple functions to both read from and write to a
23+
`x9_inbox`, as the right choice depends on the user needs.
24+
Refer to _x9.h_, where all public functions are properly documented and their
25+
use cases explained, and the examples folder for comprehensive examples of
26+
different architectures.
27+
28+
Enabling `X9_DEBUG` at compile time will print to stdout the reason why the
29+
functions `x9_inbox_is_valid` and `x9_node_is_valid` returned 'false' (if they
30+
indeed returned 'false'), or why `x9_select_inbox_from_node` did not return a
31+
valid `x9_inbox`.
32+
33+
To use the library just link with x9.c and include x9.h where necessary.
34+
35+
X9 is as generic, performant and intuitive as C allows, without forcing the
36+
user to any sort of build system preprocessor hell, pseudo-C macro based
37+
library, or worse.
38+
It was originally written in the context of an high-frequency-trading system
39+
that this author developed, and was made public in June of 2023.
40+
It is released under the BSD-2-Clause license, with the purpose of serving
41+
others and other programs.
42+
43+
Benchmarks
44+
---
45+
46+
- Single producer and single consumer transmitting 100M messages via a single
47+
`x9_inbox`.
48+
- Run on Intel 11900k (cpu and ram untuned).
49+
- _Msg size_ expressed in bytes, and _Inbox size_ in number of slots in the
50+
ring buffer.
51+
- _(See /profiling for how to run your own tests)_
52+
53+
```
54+
Inbox size | Msg size | Time (secs) | Msgs/second
55+
-------------------------------------------------
56+
1024 | 16 | 4.00 | 25.01M
57+
1024 | 32 | 4.03 | 24.82M
58+
1024 | 64 | 4.17 | 23.99M
59+
-------------------------------------------------
60+
2048 | 16 | 3.90 | 25.63M
61+
2048 | 32 | 3.96 | 25.26M
62+
2048 | 64 | 4.09 | 24.43M
63+
-------------------------------------------------
64+
4096 | 16 | 3.86 | 25.88M
65+
4096 | 32 | 3.92 | 25.50M
66+
4096 | 64 | 4.05 | 24.67M
67+
-------------------------------------------------
68+
```
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
Notes:
2+
---
3+
4+
- Inbox N is always associated with a message of type N, hence to inbox 1
5+
producers will write messages of type 1 and consumers will read messages of
6+
type 1, and so on.
7+
8+
- Each producer/consumer runs in a unique thread.
9+
10+
- All x9_inbox sizes equal 4. This is not ideal for performance and should not
11+
be used as a guideline. The reason why I use such a small buffer is because
12+
I wanted to saturate the inbox and make it much more likely to trigger a data
13+
race (which there is none).
14+
15+
- All examples/tests can be run by executing ./run_examples.sh
16+
17+
```
18+
──────▷ write to
19+
─ ─ ─ ▷ read from
20+
```
21+
22+
Examples
23+
---
24+
```
25+
x9_example_1.c:
26+
27+
One producer
28+
One consumer
29+
One message type
30+
31+
┌────────┐ ┏━━━━━━━━┓ ┌────────┐
32+
│Producer│──────▷┃ inbox ┃◁ ─ ─ ─│Consumer│
33+
└────────┘ ┗━━━━━━━━┛ └────────┘
34+
35+
This example showcases the simplest (multi-threading) pattern.
36+
37+
Data structures used:
38+
- x9_inbox
39+
40+
Functions used:
41+
- x9_create_inbox
42+
- x9_inbox_is_valid
43+
- x9_write_to_inbox_spin
44+
- x9_read_from_inbox_spin
45+
- x9_free_inbox
46+
47+
Test is considered passed iff:
48+
- None of the threads stall and exit cleanly after doing the work.
49+
- All messages sent by the producer(s) are received and asserted to be
50+
valid by the consumer(s).
51+
```
52+
-------------------------------------------------------------------------------
53+
```
54+
x9_example_2.c
55+
56+
Two producers.
57+
One consumer and producer simultaneously.
58+
One consumer.
59+
60+
┌────────┐ ┏━━━━━━━━┓ ┏━━━━━━━━┓
61+
│Producer│─────▷┃ ┃ ┌────────┐ ┃ ┃
62+
└────────┘ ┃ ┃ │Consumer│ ┃ ┃ ┌────────┐
63+
┃inbox 1 ┃◁ ─ ─ │ and │─────▷┃inbox 2 ┃◁ ─ ─ │Consumer│
64+
┌────────┐ ┃ ┃ │Producer│ ┃ ┃ └────────┘
65+
│Producer│─────▷┃ ┃ └────────┘ ┃ ┃
66+
└────────┘ ┗━━━━━━━━┛ ┗━━━━━━━━┛
67+
68+
This example showcase using multiple threads to write to the same inbox,
69+
using multiple message types, the 'x9_node' abstraction, and
70+
respective create/free and select functions.
71+
72+
Data structures used:
73+
- x9_inbox
74+
- x9_node
75+
76+
Functions used:
77+
- x9_create_inbox
78+
- x9_inbox_is_valid
79+
- x9_create_node
80+
- x9_node_is_valid
81+
- x9_select_inbox_from_node
82+
- x9_write_to_inbox_spin
83+
- x9_read_from_inbox_spin
84+
- x9_free_node_and_attached_inboxes
85+
86+
Test is considered passed iff:
87+
- None of the threads stall and exit cleanly after doing the work.
88+
- All messages sent by the producer(s) are received and asserted to be
89+
valid by the consumer(s).
90+
```
91+
-------------------------------------------------------------------------------
92+
```
93+
x9_example_3.c
94+
95+
Two producers and simultaneously consumers.
96+
97+
┌────────┐ ┏━━━━━━━━┓ ┌────────┐
98+
│Producer│──────▷┃inbox 1 ┃◁ ─ ─ ─│Producer│
99+
│ │ ┗━━━━━━━━┛ │ │
100+
│ and │ │ and │
101+
│ │ ┏━━━━━━━━┓ │ │
102+
│Consumer│─ ─ ─ ▷┃inbox 2 ┃◁──────│Consumer│
103+
└────────┘ ┗━━━━━━━━┛ └────────┘
104+
105+
This example showcases the use of: 'x9_write_to_inbox'
106+
and 'x9_read_from_inbox', which, unlike 'x9_write_to_inbox_spin' and
107+
'x9_read_from_inbox_spin' do not block until are able to write/read a msg.
108+
109+
Data structures used:
110+
- x9_inbox
111+
- x9_node
112+
113+
Functions used:
114+
- x9_create_inbox
115+
- x9_inbox_is_valid
116+
- x9_create_node
117+
- x9_node_is_valid
118+
- x9_select_inbox_from_node
119+
- x9_write_to_inbox
120+
- x9_read_from_inbox
121+
- x9_free_node_and_attached_inboxes
122+
123+
Test is considered passed iff:
124+
- None of the threads stall and exit cleanly after doing the work.
125+
- All messages sent by the producer(s) are received and asserted to be
126+
valid by the consumer(s).
127+
```
128+
-------------------------------------------------------------------------------
129+
```
130+
x9_example_4.c
131+
132+
One producer broadcasts the same message to three inboxes.
133+
Three consumers read from each inbox.
134+
One message type.
135+
136+
┏━━━━━━━━┓ ┌────────┐
137+
┌──▷┃ inbox ┃◁─ ─ ─│Consumer│
138+
│ ┗━━━━━━━━┛ └────────┘
139+
┌────────┐ │ ┏━━━━━━━━┓ ┌────────┐
140+
│Producer│───┼──▷┃ inbox ┃◁─ ─ ─│Consumer│
141+
└────────┘ │ ┗━━━━━━━━┛ └────────┘
142+
│ ┏━━━━━━━━┓ ┌────────┐
143+
└──▷┃ inbox ┃◁─ ─ ─│Consumer│
144+
┗━━━━━━━━┛ └────────┘
145+
146+
147+
This example showcases the use of 'x9_broadcast_msg_to_all_node_inboxes'.
148+
149+
Data structures used:
150+
- x9_inbox
151+
- x9_node
152+
153+
Functions used:
154+
- x9_create_inbox
155+
- x9_inbox_is_valid
156+
- x9_create_node
157+
- x9_node_is_valid
158+
- x9_broadcast_msg_to_all_node_inboxes
159+
- x9_read_from_inbox_spin
160+
- x9_free_node_and_attached_inboxes
161+
162+
IMPORTANT:
163+
- All inboxes must receive messages of the same type (or at least of the
164+
same size) that its being broadcasted.
165+
166+
Test is considered passed iff:
167+
- None of the threads stall and exit cleanly after doing the work.
168+
- All messages sent by the producer(s) are received and asserted to be
169+
valid by the consumer(s).
170+
```
171+
-------------------------------------------------------------------------------
172+
```
173+
x9_example_5.c
174+
175+
Three producers.
176+
Three consumers reading concurrently from the same inbox.
177+
One message type.
178+
179+
┌────────┐
180+
┌────────┐ ┏━━━━━━━━┓ ─ ─│Consumer│
181+
│Producer│──────▷┃ ┃ │ └────────┘
182+
├────────┤ ┃ ┃ ┌────────┐
183+
│Producer│──────▷┃ inbox ┃◁──┤─ ─│Consumer│
184+
├────────┤ ┃ ┃ └────────┘
185+
│Producer│──────▷┃ ┃ │ ┌────────┐
186+
└────────┘ ┗━━━━━━━━┛ ─ ─│Consumer│
187+
└────────┘
188+
189+
This example showcases the use of 'x9_read_from_shared_inbox'.
190+
191+
Data structures used:
192+
- x9_inbox
193+
194+
Functions used:
195+
- x9_create_inbox
196+
- x9_inbox_is_valid
197+
- x9_write_to_inbox_spin
198+
- x9_read_from_shared_inbox
199+
- x9_free_inbox
200+
201+
Test is considered passed iff:
202+
- None of the threads stall and exit cleanly after doing the work.
203+
- All messages sent by the producer(s) are received and asserted to be
204+
valid by the consumer(s).
205+
- Each consumer processes at least one message.
206+
```
207+
-------------------------------------------------------------------------------
208+
```
209+
x9_example_6.c
210+
211+
One producer
212+
Two consumers reading from the same inbox concurrently (busy loop).
213+
One message type.
214+
215+
┏━━━━━━━━┓ ┌────────┐
216+
┃ ┃ ─ ─│Consumer│
217+
┌────────┐ ┃ ┃ │ └────────┘
218+
│Producer│──────▷┃ inbox ┃◁ ─
219+
└────────┘ ┃ ┃ │ ┌────────┐
220+
┃ ┃ ─ ─│Consumer│
221+
┗━━━━━━━━┛ └────────┘
222+
223+
This example showcases the use of 'x9_read_from_shared_inbox_spin'.
224+
225+
Data structures used:
226+
- x9_inbox
227+
228+
Functions used:
229+
- x9_create_inbox
230+
- x9_inbox_is_valid
231+
- x9_write_to_inbox_spin
232+
- x9_read_from_shared_inbox_spin
233+
- x9_free_inbox
234+
235+
Test is considered passed iff:
236+
- None of the threads stall and exit cleanly after doing the work.
237+
- All messages sent by the producer(s) are received and asserted to be
238+
valid by the consumer(s).
239+
- Each consumer processes at least one message.
240+
```
241+
-------------------------------------------------------------------------------
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#/bin/bash
2+
3+
echo "- Running examples with GCC with \"-fsanitize=thread,undefined\" enabled.";
4+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_1.c ../x9.c -o X9_TEST_1 -fsanitize=thread,undefined -D X9_DEBUG
5+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_2.c ../x9.c -o X9_TEST_2 -fsanitize=thread,undefined -D X9_DEBUG
6+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_3.c ../x9.c -o X9_TEST_3 -fsanitize=thread,undefined -D X9_DEBUG
7+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_4.c ../x9.c -o X9_TEST_4 -fsanitize=thread,undefined -D X9_DEBUG
8+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_5.c ../x9.c -o X9_TEST_5 -fsanitize=thread,undefined -D X9_DEBUG
9+
gcc -Wextra -Wall -Werror -pedantic -O3 -march=native x9_example_6.c ../x9.c -o X9_TEST_6 -fsanitize=thread,undefined -D X9_DEBUG
10+
11+
./X9_TEST_1; ./X9_TEST_2; ./X9_TEST_3; ./X9_TEST_4; ./X9_TEST_5; ./X9_TEST_6
12+
rm X9_TEST_1 X9_TEST_2 X9_TEST_3 X9_TEST_4 X9_TEST_5 X9_TEST_6
13+
14+
echo ""
15+
echo "- Running examples with clang with \"-fsanitize=address,undefined,leak\" enabled.";
16+
17+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_1.c ../x9.c -o X9_TEST_1 -fsanitize=address,undefined,leak -D X9_DEBUG
18+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_2.c ../x9.c -o X9_TEST_2 -fsanitize=address,undefined,leak -D X9_DEBUG
19+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_3.c ../x9.c -o X9_TEST_3 -fsanitize=address,undefined,leak -D X9_DEBUG
20+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_4.c ../x9.c -o X9_TEST_4 -fsanitize=address,undefined,leak -D X9_DEBUG
21+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_5.c ../x9.c -o X9_TEST_5 -fsanitize=address,undefined,leak -D X9_DEBUG
22+
clang -Wextra -Wall -Werror -O3 -march=native x9_example_6.c ../x9.c -o X9_TEST_6 -fsanitize=address,undefined,leak -D X9_DEBUG
23+
24+
./X9_TEST_1; ./X9_TEST_2; ./X9_TEST_3; ./X9_TEST_4; ./X9_TEST_5; ./X9_TEST_6
25+
rm X9_TEST_1 X9_TEST_2 X9_TEST_3 X9_TEST_4 X9_TEST_5 X9_TEST_6
26+

0 commit comments

Comments
 (0)