35,99 €
Find bottlenecks, identify the proper algorithm to use, optimize performance, and create really efficient Rust applications
At 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 algorithms
The 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.
This book is for Rust developers keen to improve the speed of their code or simply to take their skills to the next level.
Iban Eguia Moraza is a passionate Rust developer. He has a bachelor's degree in computer engineering and a master's degree in information and communication security. He has over 10 years of experience in web development, and since 2015, he has been developing Rust applications. Iban loves space exploration and the latest technologies. In this regard, he has developed open source software for stratospheric balloons from the ground up, and he now works at the CERN particle physics laboratory. He likes to travel to learn from the most experienced people.Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 365
Veröffentlichungsjahr: 2018
Copyright © 2018 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Merint MathewAcquisition Editor: Sandeep MishraContent Development Editor: Akshada IyerTechnical Editor: Abhishek SharmaCopy Editor: Safis EditingProject Coordinator: Prajakta NaikProofreader: Safis EditingIndexer: Mariammal ChettiyarGraphics: Jisha ChirayilProduction Coordinator: Arvindkumar Gupta
First published: March 2018
Production reference: 1270318
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
ISBN 978-1-78839-948-7
www.packtpub.com
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Mapt is fully searchable
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Iban Eguia Moraza is a passionate Rust developer. He has a bachelor's degree in computer engineering and a master's degree in information and communication security. He has over 10 years of experience in web development, and since 2015, he has been developing Rust applications.
Iban loves space exploration and the latest technologies. In this regard, he has developed open source software for stratospheric balloons from the ground up, and he now works at the CERN particle physics laboratory. He likes to travel to learn from the most experienced people.
Daniel Durante is an avid coffee drinker/roaster, motorcyclist, archer, welder, and carpenter whenever he isn't programming. From the age of 12, he has been involved with web and embedded programming with PHP, Node.js, Golang, Rust, and C.
He has worked on text-based browser games that have reached over 1,000,000 active players, created bin-packing software for CNC machines, embedded programming with cortex-m and PIC circuits, high-frequency trading applications, and helped contribute to one of the oldest ORMs of Node.js (SequelizeJS).
He has also reviewed the following books for Packt:
PostgresSQL Developer's Guide
PostgreSQL 9.0 High Performance
Rust Programming By Example
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
Title Page
Copyright and Credits
Rust High Performance
Dedication
Packt Upsell
Why subscribe?
PacktPub.com
Contributors
About the author
About the reviewer
Packt is searching for authors like you
Preface
Who this book is for
What this book covers
To get the most out of this book
Download the example code files
Conventions used
Get in touch
Reviews
Common Performance Pitfalls
Asking the Rust compiler about performance
Optimizations
Build configuration
Optimization level
Debug information
Link-time optimizations
Debug assertions
Panic behavior
Runtime library paths
Translation issues
Indexing degradations
Using iterators
Iterator adaptors
Real-life example
Specialized adaptors
Interaction between adaptors
Itertools
Borrowing degradations
Cyclomatic complexity
Summary
Extra Performance Enhancements
Compile-time checks
Sequential state machines
Complex state machines
Real-life type system check example
Extra performance tips
Using closures to avoid runtime evaluation
Unstable sorting
Map hashing
Perfect hash functions
Standard library collections
Sequences
Maps
Sets
Summary
Memory Management in Rust
Mastering the borrow checker
Allocations
Mutability, borrowing, and owning
Lifetimes
Memory representation
Alignment
Complex enumerations
Unions
Shared pointers
The cell module
Cells
RefCell
The rc module
Summary
Lints and Clippy
Using Rust compiler lints
Lints
Avoiding anonymous parameters
Avoiding heap allocated box pointers
Avoiding missing implementations
Enforcing documentation
Pointing out trivial casts
Linting unsafe code blocks
Unused lints
Variant size differences
Lint groups
Clippy
Installation
Configuration
Lints
Casting
Bad practice
Performance lints
Unwraps
Shadowing
Integer overflow
Lint groups
Summary
Profiling Your Rust Application
Understanding the hardware
Understanding how the CPU works
Speeding up memory access with the cache
Cache misses
How can you fix it?
Cache invalidation
CPU pipeline
Branch prediction
The relevance of branch prediction for our code
Profiling tools
Valgrind
Callgrind
Cachegrind
OProfile
Summary
Benchmarking
Selecting what to benchmark
Benchmarking in nightly Rust
Benchmarking in stable Rust
Continuous integration for benchmarks
Travis-CI integration
Benchmark statistics with Criterion
Summary
Built-in Macros and Configuration Items
Understanding attributes
Trait derivations
Crate features
Configuration attributes
Macros
Console printing
String formatting
Compilation environment
Loading byte arrays and strings at compile time
Code paths
Checking preconditions and postconditions
Others
Nightly Rust
Conservative trait return
Constant functions
Inline assembly and naked functions
Using bigger integers
Single instruction multiple data
Allocation API
Compiler plugins
Summary
Must-Have Macro Crates
Working with external data
Data serialization and deserialization
Serializing and deserializing complex structures
Parsing byte streams
Learning about useful small crates
Creating lazily evaluated statics
Avoiding boilerplate code for the builder pattern
Managing errors
Logging efficiently in Rust
Creating command-line interfaces
Using Rust for web development
Creating extremely efficient templates
Connecting with a database
Creating a complete web server
Summary
Creating Your Own Macros
Creating your own standard macros
Macro variants
Complex macros
Creating procedural macros
Implementing a simple trait
Implementing complex derivations
Implementing getters
Implementing setters
Metaprogramming in nightly Rust
Understanding compiler plugins
Declarative macros
Summary
Multithreading
Concurrency in Rust
Understanding the Send and Sync traits
The Send trait
The Sync trait
Other types of concurrency in Rust
Understanding multithreading
Creating threads
Panicking in Rust
Moving data between threads
The move keyword
Sharing data between threads
Channels between threads
Multithreading crates
Non-blocking data structures
Scoped threads
Thread pooling
Parallel iterators
Summary
Asynchronous Programming
Introduction to asynchronous programming
Understanding I/O in the CPU
Getting the kernel to control the I/O
Asynchronous programming from the programmer's perspective
Understanding futures
Future combinators
Asynchronous I/O in Rust
Creating Tokio codecs
WebSockets in Rust
Understanding the new Generators
Summary
Other Books You May Enjoy
Leave a review - let other readers know what you think
Welcome to Rust High Performance. In this book, you will get a gentle introduction to high-performance programming by learning how to improve the performance of your Rust code. It will show you how to translate your code from other languages properly by avoiding common bottlenecks, and it will show you how to easily increase the performance of your application using some idiomatic Rust APIs.
You will learn about the great Rust community by finding great crates that will increase the development efficiency while also improving the performance of your application, and you will write examples to use all your knowledge. You will write your own macros and custom derives, and you will learn about asynchronous and multithreaded programming.
In this book, you will find everything you need to improve the performance of your Rust code; you will learn many tricks and use helpful crates and tools. Therefore, the book is written from the basis that you already have some knowledge of programming in Rust.
This book will not cover the whole world of high-performance programming since it's an incredibly wide topic. You will find a gentle introduction to most of the generic high-performance programming concepts and learn how specific patterns can be used in the Rust programming language.
Chapter 1, Common Performance Pitfalls, helps you learn why translating from languages such as C/C++ can lead to big performance decline, how to improve your algorithms using different Copy/Clone types and references, and understand how cyclomatic complexity can make compiler optimizations less effective.
Chapter 2, Extra Performance Enhancements, takes a step forward to understand some tips and tricks Rust gives us to improve the performance of your applications. After learning about common mistakes in the previous chapters, you will learn how to use the Rust type system to your advantage, creating complex compile-time checks and evaluations. You will also understand the difference between the common standard library collections so that you can choose the right one for your algorithm.
Chapter 3, Memory Management in Rust, shows you how to improve the memory footprint of your applications by taking advantage of the borrow checker. You will learn about lifetimes and how to properly use them, understand the different representation attributes that will help your data be properly structured in memory, and finally, learn how to create efficient shared pointer structures for your application using standard library types.
Chapter 4, Lints and Clippy, teaches you the power of lints and how to configure them to give you proper suggestions. You'll learn how to configure clippy, an incredibly powerful tool that will point out common errors and potential performance improvements. In this chapter, you will learn the most important clippy lints and use them in your development workflow.
Chapter 5, Profiling Your Rust Application, covers how to use profiling software so that you can easily find performance bottlenecks in your applications. You'll learn how cache misses impact your code and how to find where in the code is the application spending more time. You will learn to fix those bottlenecks and therefore improve the overall performance of the application.
Chapter 6, Benchmarking, discusses how to detect performance critical code and how to benchmark it in both Rust stable and nightly. You will also learn how to set up your continuous integration environment to get performance reports and track them during the development process of your project.
Chapter 7, Built-in Macros and Configuration Items, brings you to the world of attributes that can personalize your code so that you target specific platforms with each section of your code using all of each platform's potential. You will understand how to divide your crate so that not all the code has to be compiled for each use, and you will finally learn how to use nightly features to improve the efficiency of your code and the amount of code to write.
Chapter 8, Must-Have Macro Crates, introduces you to multiple metaprogramming crates—create serializable structures, deserialize data from languages such as JSON or TOML, parse log files, or create a lot of boilerplate code for your data structures. Here, you can understand how to initialize complex static structures and use a proper error handling. Finally, thanks to nightly Rust and plugins, you will be able to create a small web server with a database and even attach to it the fastest template system in existence.
Chapter 9, Creating Your Own Macros, covers how to write your own macros to avoid code boilerplate. You will understand how the new macros 1.1 work and create your first custom derive. Finally, you will learn how compiler plugins internally work and will create your own compiler plugin.
Chapter 10, Multithreading, outlines how to create multiple threads to balance the work of your application. You will understand the full power of Rust's threads and the synchronization primitives in the standard library. In addition, you will learn how to send information between threads. Finally, you will read about some useful crates that will enable you to implement work stealing algorithms, parallel iterators, and more.
Chapter 11, Asynchronous Programming, helps you understand how asynchronous programming works. Here, you can learn how to develop asynchronous algorithms in Rust, thanks to mio and futures, and learn the new async/await syntax. You can also create asynchronous applications using tokio and WebSockets.
This book assumes some basic knowledge of the Rust programming language. If you are new to Rust, the first few chapters of the official Rust book are a great prelude. Nevertheless, you should have moderate to deep knowledge of at least one programming language; basic knowledge of terminal usage will also be needed.
Having basic knowledge of computer architectures is a plus, along with basic knowledge of high-performance programming in C/C++. They are not required, though, since in this book we will cover all the base theory to understand how the performance improvements work behind the scenes.
You will need a code editor or an IDE to follow the book. Rust has been heavily tested in Microsoft's Visual Studio Code, GitHub's Atom, and IntelliJ's IDEA IDE. I have personally used Atom to write the code examples, but feel free to use your favorite text editor or IDE. You will probably find plugins or extensions for your editor.
In the case of VS Code, Atom, and IntelliJ IDEA, you will find official Rust packages along with unofficial extensions. Personally, I've been using the Tokamak package for Atom.
You can download the example code files for this book from your account at www.packtpub.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.
You can download the code files by following these steps:
Log in or register at
www.packtpub.com
.
Select the
SUPPORT
tab.
Click on
Code Downloads & Errata
.
Enter the name of the book in the
Search
box and follow the onscreen instructions.
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
WinRAR/7-Zip for Windows
Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Rust-High-Performance. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "The iterator will not run until you call collect() method or use it in a loop. Those are the moments in which the next() method gets executed."
A block of code is set as follows:
for row in arr1.iter().cartesian_product(arr2.iter()) { print!("{:?}, ", row); }
Any command-line input or output is written as follows:
cargo install --no-default-features --features sqlite diesel_cli
Bold: Indicates a new term, an important word, or words that you see onscreen.
Feedback from our readers is always welcome.
General feedback: Email [email protected] and mention the book title in the subject of your message. If you have questions about any aspect of this book, please email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/submit-errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packtpub.com.
If you are reading this book, you are probably concerned about the performance of your Rust code. It's known that Rust can offer performance close to that of C/C++ programs, and in some cases, Rust can even win those benchmarks. The main issue, though, is that it's sometimes hard to get that efficiency, especially if you are coming from C/C++. Some concepts don't apply, and some simple efficient approaches in those languages are notably worse in Rust.
In this book, you will learn how to really take advantage of Rust to make it perform at its best while maintaining all the benefits it brings—safety, zero-cost abstractions, and great concurrency. The book can be read from start to finish, and you will probably learn new concepts in every chapter. You can go directly to the chapter that interests you, though, as each chapter contains all the required information for its completion, so that it can be used as a reference.
In this first part of the book, we will start with an introduction on how to improve the performance of your sequential code. You will learn how to avoid common performance pitfalls and how to fix direct translations from other languages. You will then learn how to get better performance from your code, and finally understand memory management in Rust.
In this chapter, we will be looking into:
Configuration of the compilation process with profiles
Translation pitfalls—learning how to avoid performance pitfalls with array/slice indexing and master iterators
New iterator adaptors, both in the standard library and in external crates, and coding any complex behavior at zero cost
How to use the borrow checker to your advantage
Most of the people that start learning Rust, myself included, tend to bring lessons learned in other languages to Rust. This is usually a great thing, as it will enable you to learn the language faster. The main issue with this approach is that some patterns used in other languages can actually be a trade-off in Rust. We will learn about the most common ones and the not-so-common ones, so that anyone trying to get better performance in Rust can learn how to do it.
Rust sometimes has interesting and lesser-known features that really make a difference when talking about performance enhancements. When it comes to big improvements with small changes, the first thing that you should understand is the release mode. Rust by default compiles your software in development mode, which is pretty good to check for compiling errors quickly, but if you want to run it, it will run slowly. This is due to the development mode not performing any optimizations. It will create object (machine) code directly related to the Rust code without optimizing it.
Rust makes use of the LLVM backend, which makes it easy to take advantage of its performance optimizations without having to develop all of these by themselves. They will only need to use LLVM intermediate representation. An intermediate language between Rust and assembly code that the LLVM compiler understands. While in development mode, no optimizations get performed by Rust or LLVM; enabling them is as easy as adding the --release flag to the cargo compilation. So, for example, if you were running your software by typing cargo run in the console, just by using cargo run --release it will compile with optimizations and run much, much faster. Usually, the gain is of more than one order of magnitude.
By default, Rust will perform level 3 optimizations in the code. Optimizations get divided into levels depending on how complex they are. Higher-level optimizations, in theory, improve the performance of the code greatly, but they might have bugs that could change the behavior of the program. Usually, level 1 optimizations are totally safe, and level 2 optimizations are the most-used ones in the C/C++ ecosystem. Level 3 optimizations have not been known to cause any issues, but in some critical situations, it might be better to avoid them. This can be configured, but we should first understand how the Rust compiler compiles the code to machine instructions so that we know what different options accomplish.
Rust first starts with parsing your code files. It will get the keywords and the different symbols to create a representation of the code in memory. This parsing will find common errors such as a missing semicolon or an invalid keyword. This memory representation of the code is called High Intermediate Representation (HIR). This representation of the code will be greatly simplified, removing complex flow structures and converting it into Middle Intermediate Representation (MIR).
The MIR representation is then used to check more complex flows of the software, and enables complex variable lifetime checks, along with some other improvements. This is then converted to the LLVM Intermediate Representation and gets passed to the LLVM compiler. When passing this code to LLVM, Rust adds some flags that will modify the way that LLVM optimizes the code. We have already seen that by default one of the flags it passes is the -O0 flag, or do not optimize flag, so it simply translates to machine code. When compiling in release mode, though, a -O3 gets passed so that level 3 optimizations get performed.
This behavior can be configured in the Cargo.toml file of the project and it can be configured for each profile. You can configure how to compile for tests, development, documentation, benchmarks, and release. You will probably want to keep development and documentation optimizations to a minimum, as in those profiles the main idea is to compile quickly. In the case of the development profile, you will want to check if everything compiles properly, and even test the behavior of the program a little bit, but you probably won't be concerned about the performance. When generating the documentation, the performance of the application doesn't matter at all, so the best idea is to just not optimized.
When testing, the optimization level you need will depend on how many tests you want to run and how computationally expensive they are. If it takes a really long time to run the tests, it may make sense to compile them optimized. Also, in some critical situations in which you might not be 100% sure that optimizations get performed in a completely secure way, you might want to optimize the tests the same way you optimize the release, and that way you can check if all unit and integration tests pass properly even after optimizations. If they don't, this is a compiler malfunction, and you should report it to the Rust compiler team. They will be glad to help.
Of course, benchmarks and release profiles should be the most optimized ones. In benchmarks, you will want to know the real optimized performance of the code, while in the release, you will want your users to get the best out of their hardware and your software to make things run as efficiently as possible. In these cases, you will want to optimize up to level 2 at least, and if you are not sending satellites to space or programming a pacemaker, you will probably want to optimize all the way up to level 3.
There is one section in the Cargo.toml file that enables these configurations: the profile section. In this section, you will find one subsection for each of the profiles. Each of them gets declared with the [profile.{profile}] format. So, for example, for the development profile, it would be [profile.dev]. The different profile configuration keywords are the following:
dev
for the development profile, used in
cargo build
or
cargo run
release
for the release profile, used in
cargo build --release
or
cargo run --release
test
for the testing profile, used in
cargo test
bench
for the benchmarking profile, used in
cargo bench
doc
for the documentation profile, used in
cargo doc
When configuring each profile, you will have many options, and we will check all of them out here.
The last configuration option is rpath. This configuration item accepts a Boolean and allows you to ask the Rust compiler to set loader paths when the executable looks OK for libraries at runtime. Even though, most of the time, Rust will link crates and libraries statically, you can ask a specific library to be linked dynamically. In this case, that library will be searched at runtime, not at compile time, and it will therefore use system libraries installed where the program is running.
This configuration option asks cargo to add -C rpath to the rustc compiler invocation. This will add paths to the dynamic library search paths. Nevertheless, this should not be required in most cases, and you should avoid it if it's not necessary by using false as the option value. If you are having issues making your application run in multiple operating systems, you might try it, since it might make the executable look for dynamic libraries in new places.
When translating your C/C++/Java mindset, or directly porting a project to Rust, you might find yourself writing similar code to what you wrote in your native language, but if you have tried it, you might have noticed that it performs poorly, or at least much worse than your old code. This happens, especially with C/C++, since in the case of Java the performance issue is much lower compared to the high memory and computation footprint of a Java application, with its Java Virtual Machine and its garbage collector.
But why does a direct translation hurt the performance? We will see in this section how Rust's guarantees can sometimes create unnecessary boilerplate instructions, and we will learn how to bypass them by using safe and efficient code. Of course, in some performance-critical situations, unsafe scopes might be needed, but in general, that's not the case.
