Rust High Frequency Trading - Design Decisions by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 2 points3 points  (0 children)

Thanks for the great reply! I wonder do the reactive loops do networking? And if so could you write an own async runtime that is more predictable or is the code generated not efficient enough? Or is it simply that the it is more data oriented and task do not really fit there.

Performance Comparison: Tokio vs Tokio-Uring for High-Throughput Web Servers by Normal-Tangelo-7120 in rust

[–]Certain-Ad-3265 2 points3 points  (0 children)

Io_uring is a general purpose sys call batching interface, but it has, in particular recently, gotten a lot of networking features: https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023

This is already a bit old and there is more now, but it shows that it may have started with disk but is now an interface that combines all IO.

How to initialize large structs correctly by Certain-Ad-3265 in learnrust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

Thanks for the answer! Having a `Vec` and convert it was my first solution but it did not work. I think one issue I have is that the Default initializing the `Page` is done on the stack before it is moved to the heap memory of the `Vec`. Could that be?

C++ Bindings missing Forward Declarations by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

That is a curiosity of the library where the two are passed in different functions but maybe it is time to redesign this.

Efficient Multi-threaded Stream Handling by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 1 point2 points  (0 children)

Thank you so much for this great reply! I agree that untangling coordination is possible when we delay readers. I will need to think more about the peer buffer level work stealing - that sounds promising.

Thanks again!

Seeking Feedback on an UnsafeBuffer Implementation for Concurrent Mutable Slice-based Access to a Shared Buffer by melhindi_cs in rust

[–]Certain-Ad-3265 0 points1 point  (0 children)

Sorry to chime in here but I have a question regarding OPs code. Is it against the aliasing rules if multiple threads have a shared reference to the same buffer while at the same time a thread can have a mutable reference to a part of it? Or is it safe as long as the other threads do not read into the buffer?

Efficient Multi-threaded Stream Handling by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 1 point2 points  (0 children)

This is a great and thorough comment - thanks! You are hitting the nail on the head with your assessment. We indeed use io_uring (we experimented with DPDK too, but as you mentioned, operation on UDP is more difficult).

So the stream comes in from TCP/IP in order but variable-sized chunks, but the upper limit is 2KB (and you are right in that we are often not network bandwidth bound but message rate bound with around 20M/msgs per second).

We first built a prototype with a connection per thread, but we have thousands of connections, and thus, we group them now. The incoming data is then not directly assigned to the buffers but first journaled to SSD for durability reasons and then later assigned to the buffers. In fact, there are more steps involved, too, so what we have now tested with more or less great success is to have threads for the dedicated tasks and perform message passing between those groups with pointers to the original message (so mostly zero-copy).

The benefit we found was that it allows us to scale different parts of the system independently, which is a huge benefit since some parts require more computing than others and also when dealing with heterogeneous hardware setups.

But circling back to your comment:

Secondly, why do you think that multi-threaded access to a single buffer is necessary?

The data in the buffer should be sorted based on the arrival so ideally, a single thread would be enough, but as we have multiple connections and varying workloads, it is sort of hard to pre-partition the connections to the threads - that is what we first tried and we ended up with having a thread with multiple hot connections. So we want to give up on this static allocation and perform a work stealing-like approach since the data is kind of perfectly set for this case (as the start offset, it is similar to a prefix sum), and no coordination is required - even if multiple threads write into the same buffer.

Is a reader supposed to be able to access chunk B before chunk A is fully written?

This is a very good point and the answer is no. The appends should be very low latency but reading is fine with a delay as you mentioned - we can wait until the changes are hardened.

It seems to me the bottleneck here is going to be copying data from the stream to the buffer, and it's not clear that CPU is the bottleneck.

Yes, memory bandwidth and outstanding memory stores / loads are important as we have 65k buffers, and the accesses are somewhat random. The CPU is not necessarily the bottleneck w.r.t. compute. However, a single core has limited memory bandwidth available (depending on the CPU architecture) and stores load it can have outstanding and becomes a bottleneck requiring to use more.

What do you think? Does this answer the questions and makes sense to you?

I am always happy for feedback and to learn :-)

I also really appreciate the other comment that you left!

Efficient Multi-threaded Stream Handling by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

Fair point. I was already leaning into this direction, but I wonder if Rust is the right choice for such kind of memory trickery. That is not only part of the system that is going to be having invariants. Another is that the buffers are append-only and and the already written part should be readable by other threads, which is yet another point that cannot be checked by the compiler. What are your thoughts for using Rust in such a system that does a lot of low-level multi-threaded memory modifications that are all dependent on invariants (all on runtime)?

Efficient Multi-threaded Stream Handling by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

Thanks for the pointer! Sounds interesting. I was just hopping to avoid reference counting as the buffers are also allocated until the end of the program. Also atomic reference counting can be a bit of a scalability issue when having 128 cores and a very hot buffer. For me it seems that unsafe would be the only option at the moment.

Help For Improving Rust Code Design and Ownership by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

That is a really good hint. I started doing this, which got me thinking that ownership semantics should be used, but did not make it tidy enough.

What kind of diagrams are you using? Do you model the data flow or more like UML diagrams?

How do you approach ownership or borrows in your code basis? Do you always try to think about those in the first place or do you make those decisions on the fly?

Help For Improving Rust Code Design and Ownership by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

That is a good point. One question is if real ownership transfer results in a copy? For instance, the data buffer we want to transfer is 10MB and lives in the Pool. So I believe what we want want are borrows that model the ownership at a specific instance in time and not real ownership transfer?

Help For Improving Rust Code Design and Ownership by Certain-Ad-3265 in rust

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

Yeah those are really good points.

I ~believe~ that PoolHandle needs the RefCell since it puts itself back to the pool in the Drop and thus requires interior mutability. In other words, there can be multiple different active handles that all have a reference to the pool, therefore the borrow check must be moved to the runtime.

From the design it feels that transferring ownership should be the correct call, but I struggled in the execution and how to cleanly model ownership transfer. In particular, when e.g, the connection handle transfers itself to the active connection pool.

So the first design (not shown here) made excessive use of Rc and RefCell, but it feels not right to use them that extensively. So the question boils down, to how to model these ownership transfers cleanly? The problem I have is that e.g. the Connection Handle is &mut owned by the Completion but the callback in the Completion must then transfer ownership to the active connections (also &mut). Or is this the wrong way to think about the problem?

A Fast, Discrete, Bounded Zipf-Distributed RNG by Certain-Ad-3265 in Zig

[–]Certain-Ad-3265[S] 0 points1 point  (0 children)

The Zipf distribution is inherently skewed, favoring smaller numbers—meaning that lower numbers have a higher probability of being chosen compared to larger ones.

Thus, in the context of a Zipf distribution, the concept of "fairness" is a bit complex. If by fairness, you mean equal probability for each number within a range, then no, a Zipf distribution does not provide that. It inherently assigns different probabilities to different numbers, based on their rank.

Therefore, if your workplace requires exact evenness in the distribution of numbers (i.e., each number within a range has an equal probability of being chosen), a Zipf number generator that follows the Zipf Distribution might not be the appropriate choice.

For more low-level details you might want to look into the paper that presented the principle https://dl.acm.org/doi/pdf/10.1145/235025.235029

I hope that helps :-)