Rust High Performance
eBook - ePub

Rust High Performance

Iban Eguia Moraza

Share book
  1. 272 pages
  2. English
  3. ePUB (mobile friendly)
  4. Available on iOS & Android
eBook - ePub

Rust High Performance

Iban Eguia Moraza

Book details
Book preview
Table of contents
Citations

About This Book

Find bottlenecks, identify the proper algorithm to use, optimize performance, and create really efficient Rust applicationsAbout This Book• Understand common performance pitfalls and improve the performance of your applications.• Get to grips with parallel programming and multithreading with Rust.• Learn metaprogramming in Rust.Who This Book Is ForThis book is for Rust developers keen to improve the speed of their code or simply to take their skills to the next level.What You Will Learn• Master tips and tricks to make your code faster.• Learn how to identify bottlenecks in your Rust applications• Discover how to profile your Rust software.• Understand the type system to create compile-time optimizations.• Master the borrow checker.• Learn metaprogramming in Rust to avoid boilerplate code.• Discover multithreading and work stealing in Rust.• Understand asynchronous programming in Rust.In DetailAt times, it is difficult to get the best performance out of Rust. This book teaches you to optimize the speed of your Rust code to the level of languages such as C/C++. You'll understand and fix common pitfalls, learn how to improve your productivity by using metaprogramming, and speed up your code by concurrently executing parts of it safely and easily. You will master the features of the language which will make you stand out and use them to really improve the efficiency of your algorithmsThe book begins with a gentle introduction to help you identify bottlenecks when programming in Rust. We highlight common performance pitfalls, along with strategies to detect and resolve these issues early. We move on to mastering Rust's type system, which will enable us to create impressive optimizations in both performance and safety at compile time. You will then learn how to effectively manage memory in Rust, mastering the borrow checker. We move on to measuring performance and you will see how this affects the way you write code. Moving ahead, you will perform metaprogramming in Rust to boost the performance of your code and your productivity. You will finally learn parallel programming in Rust, which enables efficient and faster execution by using multithreading and asynchronous programming.Style and approachYou'll embark on a learning journey that will teach about you deep-core concepts in the Rust language. Learning those concepts—such as understanding the borrow checker—will make you competent to write more efficient Rust code. To learn those core concepts, you'll perform practical work and see for yourself how specific patterns improve the performance of your code.

Frequently asked questions

How do I cancel my subscription?
Simply head over to the account section in settings and click on “Cancel Subscription” - it’s as simple as that. After you cancel, your membership will stay active for the remainder of the time you’ve paid for. Learn more here.
Can/how do I download books?
At the moment all of our mobile-responsive ePub books are available to download via the app. Most of our PDFs are also available to download and we're working on making the final remaining ones downloadable now. Learn more here.
What is the difference between the pricing plans?
Both plans give you full access to the library and all of Perlego’s features. The only differences are the price and subscription period: With the annual plan you’ll save around 30% compared to 12 months on the monthly plan.
What is Perlego?
We are an online textbook subscription service, where you can get access to an entire online library for less than the price of a single book per month. With over 1 million books across 1000+ topics, we’ve got you covered! Learn more here.
Do you support text-to-speech?
Look out for the read-aloud symbol on your next book to see if you can listen to it. The read-aloud tool reads text aloud for you, highlighting the text as it is being read. You can pause it, speed it up and slow it down. Learn more here.
Is Rust High Performance an online PDF/ePUB?
Yes, you can access Rust High Performance by Iban Eguia Moraza in PDF and/or ePUB format, as well as other popular books in Computer Science & Programming in C++. We have over one million books available in our catalogue for you to explore.

Information

Year
2018
ISBN
9781788478236
Edition
1

Multithreading

So far, we have seen how to make our code faster and faster by optimizing various aspects of how we code, but there is still one point left to optimize: making our code work in parallel. In this chapter, you will learn how fearless concurrency works in Rust by using threads to process your data.
During this chapter, you will learn the following:
  • Send and Sync traits—how does Rust achieve memory safety?
  • Basic threading in Rust—creating and managing threads
  • Moving data between threads
  • Crates to make multithreading easier and faster

Concurrency in Rust

For a long time, it has not made sense to perform all tasks sequentially in a computer. Of course, sometimes you need to perform some tasks before others, but in most real-world applications, you will want to run some tasks in parallel.
You might, for example, want to respond to HTTP requests. If you do one after the other, the overall server will be slow. Especially when you get many requests per second and some of them take time to complete. You probably want to start responding to others before you finish with the current one.
Furthermore, we now have multiple processors in almost any computer or server, even in most mobile phones. This means that not only can we process other tasks in parallel while our main task is idle, we can really use one processor for each task by using threads. This is a feature that we must use to our advantage when developing high-performance applications.
The main issue with concurrency is that it's hard. We are not used to thinking in parallel, and as programmers we make mistakes. We only have to check some of the security vulnerabilities or bugs in our most-used systems, developed by the greatest programmers, to see that it's difficult to make it right.
Sometimes, we try to change a variable without remembering that another task might be reading it, or even changing it at the same time. Imagine a request counter in the HTTP example. If we separate the load between two processors, and each processor receives a request, the shared counter should go up by two, right?
Each thread wants to add 1 to the counter. For that, they load the current counter in the CPU, they add one to it and then save it again in the RAM. This takes some time, especially loading it from RAM, which means that if they both load the counter at the same time, they will both have the current counter in the CPU.
If both add one to the counter and save it back, the value in the RAM will only add one request, instead of two, because both processors will save the new +1 value in the RAM. This is what we call a data race. There are some tools that avoid this behavior, such as atomic variables, semaphores, and mutexes, but we sometimes forget to use them.
One of the best-known features in Rust is the fearless concurrency. This means that as long as we use safe Rust, we shouldn't be able to create a data race. This solves our issue but, how do they do it?

Understanding the Send and Sync traits

The secret ingredients for this to work are the Send and Sync traits. They are traits known to the compiler, so it will check whether they are the types we use want to use to implement them and act accordingly. You cannot implement Send or Sync for your types directly. The compiler will know whether your types are Send or Sync by checking whether the contained fields are Sync or Send, in the case of structures or enumerations with fields.
Let's now understand how they work. First of all, you should note that neither Send nor Sync traits add methods to a given type. This means that, once compiled, they will not occupy any memory or add any extra overhead to your binary. They will only be checked at compile time to make sure that multithreading is safe. You cannot directly implement Send or Sync for your types unless you are using an unsafe block, so the compiler will do it for you where appropriate.

The Send trait

A structure implementing the Send trait is safe to be moved between threads. This means that you can safely transfer ownership of a Send type between threads. The standard library implements Send for the types that can actually be moved across thread boundaries, and the compiler will automatically implement it for your types if they can also be moved between threads. If a type is only composed of Send types, it will be a Send type.
Most types in the standard library implement the Send trait. You can safely move ownership of a u32 to another thread, for example. This means that the previous thread will not be able to use it again and that the new thread will be in charge of dropping it once it gets out of scope.
There are some exceptions, though. Raw pointers cannot be safely moved to another thread, since they have no safety guards. You can copy a raw pointer multiple times, and it could happen that one gets to one thread and the other stays in the current one. If both try to manipulate the same memory at the same time, it will create undefined behavior.
The other exception is the reference-counted pointer or Rc type. This type can easily and efficiently create shared pointers to a given memory location. It will be safe since the type itself has some memory guarantees to make sure that if a mutable borrow exists, no other borrows can be made, and that if one or more non-mutable borrows exists, no mutable borrow can be made. The information pointed by the pointer will be dropped at the same time the last reference gets out of scope.
This works by having a counter that adds 1 each time a reference gets created by calling the clone() method and that subtracts 1 once a reference gets dropped. You might have already realized the issue that will arise when sharing it between threads: if two threads drop a reference at the same time, the reference count might only subtract 1. This means that when the last reference gets dropped, the counter won't be zero, and it will not drop the Rc, creating a memory leak.
Since Rust cannot allow memory leaks, the Rc type is not Send. There is an equivalent shared pointer that can be shared between threads, the atomically reference-counted pointer or Arc. This type makes sure that each addition or subtraction to the reference count gets performed atomically, so that if a new thread wants to add or subtract one reference, it will need to wait for the other threads to finish updating that counter. This makes it thread-safe, but it will be slower than an Rc due to the checks that need to be performed. So, you should use Rc if you don't need to send a reference to another thread.

The Sync trait

The Sync trait, on the other hand, represents a type that can be shared between threads. This refers to actually sharing the variable without transferring its ownership to the new thread.
As with the Send trait, raw pointers and Rc are not Sync, but there is another family of types that implement not Send but not Sync. A Cell can be safely sent between threads, but it cannot be shared. Let's review how a Cell works.
A cell that can be found in the std::cell module is a container that will have some inner data. This data will be another type. Cells are used for interior mutability, but what is that? Interior mutability is the option to change the contents of a variable without it being mutable. This might sound counter-intuitive, especially in Rust, but it's possible.
The two safe types of cells are Cell and RefCell. The first ones implement interior mutability by moving values in and out of the Cell. This means that you will be able to insert a new value in the cell or get the current cell value if it's a Copy type, but you won't be able to use its mutable methods if you are using a complex type, such as a vector or a HashMap. It's useful for small types such as integers, for example. An Rc will use a Cell to store a count of the references so that you can call the clone() method on a non-mutable Rc and still update the count of references. Let's see an example:
use std::cell::Cell;

fn main() {
let my_cell = Cell::new(0);
println!("Initial cell value: {}", my_cell.get());

my_cell.set(my_cell.get() + 1);
println!("Final cell value: {}", my_cell.get());
}
Note that the my_cell variable is not mutable, but the program still compiles and the output is the following:
A RefCell does a similar thing, but it can be used with any kind of type, and you can get mutable references to the value inside if there are no other references to it. This internally uses unsafe code, of course, since Rust does not allow this. For this to work, it has a flag that lets the RefCell know whether it's currently borrowed or not. If it's borrowed for read, more read-only borrows can be generated with the borrow() method, but no mutable borrow can be done. If it's mutably borrowed with the borrow_mut() method, you will not be able to borrow it mutably or non-mutably.
These two methods will check the current borrow status at runtime, not at compile time, which is standard for Rust rules, and panic if the current state is not correct. They have non-panicking alternatives named try_borrow() and try_borrow_mut(). Since all the checks are done at runtime, they will be slower than the usual Rust rules, but they allow for this interior mutability. Let's see an example:
use std::cell::RefCell;
use std::collections::HashMap;

fn main() {
let hm = HashMap::new();
let my_cell = RefCell::new(hm);
println!("Initial cell value: {:?}", my_cell.borrow());

my_cell.borrow_mut().insert("test_key", "test_value");
println!("Final cell value: {:?}", my_cell.borrow());
}
Once again, note that the my_cell variable is not mutable, and yet this code compiles and we get a mutable borrow to it, which allows us to insert a new key/value pair into the hash map. The output, as expected, is the f...

Table of contents