Rust¶
Rust is a programming language with a strong focus on performance, reliability, and productivity. That translates to C++-like speeds, best-in-class tooling, a strong focus on good error messages, and designs that shift bug detection to compile time, essentially eliminating segfaults and whole classes of parallelism bugs.
This is particularly interesting to high-performance computing in that it gives you modern tooling and easy access to an ecosystem of libraries without sacrificing speed and great support for parallelism and "low-level" control over your computation.
Using Rust at NERSC¶
Installing Rust¶
NERSC provides a Rust module that can be loaded with:
module load rust
Once loaded, you should be able to use cargo
as usual.
The module comes with out-of-the-box support for MPI (making sure the mpi
library will pick up on our MPI modules) and bindgen
for language bindings (providing the necessary compiler and linker).
Note that the module does not let you change the default Rust version and channel via rustup override
. You can switch channels (i.e., from stable
to nightly
) using a rust-toolchain.toml
file in your project or explicitly adding +nightly
to your cargo
calls.
You will need your own installation of Rust if you want fine-grained control of the Rust version you are using (which, due to Rust's strong investment in backward compatibility, is mostly needed if you need to be pinned to a particular nightly
version). In which case, you can install Rust using the default instructions and contact us for help getting MPI and bindgen
running if needed.
NERSC-Specific Quirks¶
Cargo Home¶
Cargo places its cache in the CARGO_HOME
folder (~/.cargo
), which can quickly fill your HOME
folder.
If needed, you can deal with this by deleting the git
and registry
folders in your CARGO_HOME
(or running cargo +nightly -Z gc clean gc
for less aggressive deletion). This will cause a re-downloading and re-building of the dependencies of your Rust projects but will not otherwise damage your Rust installation.
For a more scalable solution, you can have those folders be symbolic links to a folder in your SCRATCH
(this might break in case of a scratch purge but that would still not break your Rust installation) with the following command:
# Create $SCRATCH/cargo_home folder
mkdir -p "$SCRATCH/cargo_home"
# Move git and registry to subfolders in $SCRATCH/cargo_home
mv "$HOME/.cargo/git" "$SCRATCH/cargo_home/git"
mv "$HOME/.cargo/registry" "$SCRATCH/cargo_home/registry"
# Create symbolic links pointing to the moved subfolders
ln -s "$SCRATCH/cargo_home/git" "$HOME/.cargo/git"
ln -s "$SCRATCH/cargo_home/registry" "$HOME/.cargo/registry"
IDE Support¶
Getting rust-analyzer
(and, by extension, VSCode, Emacs, Vim, etc.) to pick up on the Rust module requires having our cargo
and RUSTUP_HOME
in your environment.
This can be done by adding the following to your ~/.bashrc
(if needed, up-to-date paths can be found by loading the Rust module):
export RUST_HOME=/global/common/software/nersc9/rust/stable
export RUSTUP_HOME=${RUST_HOME}/rustup_home
export CARGO_HOME=${HOME}/.cargo
export PATH=${RUST_HOME}/cargo_home/bin:${PATH}
High-Performance Rust¶
Once you are familiar with the fundamentals of Rust, we highly recommend that you take a look at The Rust Performance Book for further recommendations on making the best use of the available hardware, including a discussion of build configuration settings (e.g. using the target-cpu=native
build flag), and a list of compatible profilers.
CPU Parallelism¶
Rust does not have support for OpenMP. However, its compiler was written with CPU parallelism in mind (e.g., data races are a compile-time bug in Rust), and it has best-in-class support for multi-core computation.
The standard library comes with support for native threads and synchronization primitives.
Beyond those, we recommend that you take a look at the rayon
(which, in its own way, is Rust's closest equivalent to OpenMP) and crossbeam
libraries.
Note that, similarly to using dynamic scheduling in OpenMP, those libraries tend to come with a larger base overhead (compared to base OpenMP) in exchange for being better at balancing the work between cores.
MPI¶
We recommend you use the mpi
library (and, secondarily, the mpi-sys
library if you are missing some functionalities) to parallelize your code over several nodes with MPI.
We currently support both cray-mpi
, the default, and mpich
. Our Rust module will set the necessary environment variables and ensure that bindgen
works.
Switching MPI Modules
The MPI paths are built into your binary statically, at compile time. This means that loading another MPI module before running your binary will not result in using a different MPI implementation. You will need to rebuild your application's binary (which might require calling cargo clean
before rebuilding) to ensure that you are using the expected MPI implementation.
GPU Parallelism¶
We recommend that you use the cudarc
library to call your own CUDA kernels as well as parts of the CUDA Toolkit from Rust.
For more portable GPU support in Rust, you can use, for example, the ocl
library to load kernels written in OpenCL.
Writing kernels in Rust is still very much an experimental practice. You will find up-to-date information on the Rust GPU page.
However, you might find the Arrayfire bindings, which give you access to array computing on GPUs, useful.
SIMD¶
Beyond compiler autovectorization, the Rust language supports SIMD instructions, both architecture-specific (on stable
) and portable (currently restricted to nightly
).
While you wait for full portable SIMD support to be merged into the compiler, and to benefit from up-to-date ergonomics compared to std::simd
, you might want to use the packed_simd
library.
See also the SIMD Book for further information.
Numerical Computing in Rust¶
The following is a non-exhaustive list of libraries that you are likely to need for various numerical computing tasks.
See the Rust Cookbook and blessed.rs, as well as lib.rs (which has a better search function than crates.io), for a more generalist list of libraries.
Linear Algebra¶
While Rust has bindings for BLAS and LAPACK, you might want to use the faer-rs
library for dense and sparse linear algebra operations (you might also be interested in the nalgebra
library, though it specializes in smaller sized matrices).
For an API closer to Numpy, you might like the ndarray
library (it even has a conversion guide for Numpy users).
Random Number Generation¶
You will likely want the rand
library (which comes with its dedicated book) for your random number generation needs.
See in particular its dedicated page on parallel random number generation.
Machine Learning¶
Rust has a number of libraries for machine learning, and in particular deep learning for which the ecosystem is now mature.
Regarding deep learning, you might be interested in the Torch bindings or the lighter candle
library, both of which will let you use a PyTorch-like interface.
I/O¶
Rust has library support for a wide number of input and output types, often built on top of the serde
library which lets you serialize and deserialize Rust data structures in an efficient and generic way.
Of particular interest might be the csv
(CSV), hdf5-metno
(HDF5), serde_json
(JSON, see also simd-json
), and bincode
(stable binary format) libraries.
In addition, Rust has mature support for asynchronous programming and non-blocking operations, often done via the tokio
asynchronous runtime.
Others¶
Other libraries you might want to use include:
- Fast Fourier Transform: the
rustfft
library, - Dataframes: the
polars
library, - Plots: the
plotters
library.
Interfacing with Other Languages¶
The following will be useful if you want to either load an existing numerical library in Rust (e.g., something written in Fortran / C / C++) or make your Rust code available to another language (e.g., writing a Rust high-performance kernel for Python code).
In general, you will want to use the extern
keyword to generate bindings to some non-Rust code. However, some language-specific libraries can make your life significantly easier:
C¶
You can use the bindgen
library to talk to some C codes in Rust. See their book for detailed usage instructions.
The reverse operation, generating C bindings to Rust code, is covered by the cbindgen
library.
C++¶
The cxx
library and (higher level) autocxx
library let you call C++ code from Rust and vice versa.
You might also be interested in the cpp
library, which lets you write some C++ code inline inside your Rust code.
Python¶
pyo3
lets you call Rust code from Python and vice versa.
If you are working specifically on a Rust-based Python package, then you might want to use the maturin
build tool to simplify the process.
Getting Help with Rust¶
If you have questions or problems using Rust at NERSC, do not hesitate to contact NERSC's online help desk!