forked from AliceO2Group/AliceO2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Import x9 library for blazing fast interthread message passing
- Loading branch information
Showing
16 changed files
with
2,584 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
------------------------------------------------- | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
``` | ||
------------------------------------------------------------------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
Oops, something went wrong.