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
crate 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 Rust version and channel via rustup
. 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 (i.e. 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 (i.e., data races are a compile-time bug in Rust) and it has best-in-class support for multi-core computation.
In particular, we recommend that you take a look at the rayon
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
crate (and, secondarily, the mpi-sys
crate 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
crate 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
crate 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¶
The Rust compiler has support for 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
crate.
See also the SIMD Book for further information.
Numerical Computing in Rust¶
The following is a non-exhaustive list of crates that you are likely to need for various numerical computing tasks.
See the Rust Cookbook and Blessed, as well as lib.rs (which has a better search function than crates.io), for a more generalist list of crates.
Linear Algebra¶
While Rust has bindings for BLAS and LAPACK, you might want to use the nalgebra
crate and the nalgebra_sparse
crate for dense and sparse linear algebra, respectively.
For an API closer to Numpy, you might like the ndarray
crate (it even has a conversion guide for Numpy users).
Random Number Generation¶
You will likely want the rand
crate (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 crates for machine learning and in particular deep learning, for which the ecosystem is now very mature.
Regarding deep learning, you might be interested in the Torch bindings or the lighter candle
crate, both of which will let you use a PyTorch-like interface.
Others¶
Other crates you might want to use include:
- Fast Fourier Transform: the
rustfft
crate, - Dataframes: the
polars
crate, - HDF5 support: the
hdf5-metno
crate.
Interfacing with Other Languages¶
The following will be useful if you want to either load an existing numerical library in Rust (i.e., something written in Fortran / C / C++) or make your Rust code available to another language (i.e., 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 crates can make your life significantly easier:
C¶
You can use the bindgen
crate 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
crate.
C++¶
The cxx
crate and (higher level) autocxx
crate let you call C++ code from Rust and vice versa.
You might also be interested in the cpp
crate, 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!