mathbench is a suite of unit tests and benchmarks comparing the output and
performance of a number of different Rust linear algebra libraries for common
game and graphics development tasks.
mathbench is written by the author of glam and has been used to
compare the performance of glam with other similar 3D math libraries targeting
games and graphics development, including:
All benchmarks are performed using Criterion.rs. Benchmarks are logically into the following categories:
Despite best attempts, take the results of micro benchmarks with a pinch of salt.
matrix benches - performs common matrix operations such as transpose,
inverse, determinant and multiply.rotation 3d benches - perform common 3D rotation operations.transform 2d & 3d benches - bench special purpose 2D and 3D transform types.
These can be compared to 3x3 and 4x4 matrix benches to some extent.transformations benches - performs affine transformations on vectors - uses
the best available type for the job, either matrix or transform types
depending on the library.vector benches - perform common vector operations.euler bench - performs an Euler integration on arrays of 2D and 3D vectorsThe benchmarks are currently focused on f32 types as that is all glam
currently supports.
Different libraries have different features and different ways of achieving the
same goal. For the purpose of trying to get a performance comparison sometimes
mathbench compares similar functionality, but sometimes it's not exactly the
same. Below is a list of differences between libraries that are notable for
performance comparisons.
The euclid library does not support generic square matrix types like the other
libraries tested. Rather it has 2D and 3D transform types which can transform 2D
and 3D vector and point types. Each library has different types for supporting
transforms but euclid is unique amongst the libraries tested in that is
doesn't have generic square matrix types.
The Transform2D is stored as a 3x2 row major matrix that can be used to
transform 2D vectors and points.
Similarly Transform3D is used for transforming 3D vectors and points. This
is represented as a 4x4 matrix so it is more directly comparable to the other
libraries however it doesn't support some operations like transpose.
There is no equivalent to a 2x2 matrix type in euclid.
Note that cgmath and nalgebra matrix inverse methods return an Option
whereas glam and euclid do not. If a non-invertible matrix is inverted by
glam or euclid the result will be invalid (it will contain NaNs).
Most libraries provide quaternions for performing rotations except for
ultraviolet which provides rotors.
All benchmarks are gated as either "wide" or "scalar". This division allows us to more fairly compare these different styles of libraries.
"scalar" benchmarks operate on standard scalar f32 values, doing calculations
on one piece of data at a time (or in the case of a "horizontal" SIMD library
like glam, one Vec3/Vec4 at a time).
"wide" benchmarks operate in a "vertical" AoSoA (Array-of-Struct-of-Arrays) fashion, which is a programming model that allows the potential to more fully use the advantages of SIMD operations. However, it has the cost of making algorithm design harder, as scalar algorithms cannot be directly used by "wide" architectures. Because of this difference in algorithms, we also can't really directly compare the performance of "scalar" vs "wide" types because they don't quite do the same thing (wide types operate on multiple pieces of data at the same time).
The "wide" benchmarks still include glam, a scalar-only library, as a
comparison. Even though the comparison is somewhat apples-to-oranges, in each of
these cases, when running "wide" benchmark variants, glam is configured to do
the exact same amount of final work, producing the same outputs that the
"wide" versions would. The purpose is to give an idea of the possible throughput
benefits of "wide" types compared to writing the same algorithms with a scalar
type, at the cost of extra care being needed to write the algorithm.
To learn more about AoSoA architecture, see this blog
post by the
author of nalgebra which goes more in depth to how AoSoA works and its
possible benefits. Also take a look at the "Examples"
section of ultraviolet's
README, which contains a discussion of how to port scalar algorithms to wide
ones, with the examples of the Euler integration and ray-sphere intersection
benchmarks from mathbench.
Note that the nalgebra_f32x4 and nalgebra_f32x8 benchmarks require a Rust
Additionally the f32x8 benchmarks will require the AVX2 instruction set, to
enable that you will need to build with RUSTFLAGS='-C target-feature=+avx2.
The default profile.bench settings are used, these are documented in the
cargo reference.
Some math libraries are optimized to use specific instruction sets and may
benefit building with settings different to the defaults. Typically a game team
will need to decided on a minimum specification that they will target. Deciding
on a minimum specifiction dictates the potential audience size for a project.
This is an important decision for any game and it will be different for every
project. mathbench doesn't want to make assumptions about what build settings
any particular project may want to use which is why default settings are used.
I would encourage users who to use build settigs different to the defaults to run the benchmarks themselves and consider publishing their results.
The following is a table of benchmarks produced by mathbench comparing glam
performance to cgmath, nalgebra, euclid, vek, pathfinder_geometry,
static-math and ultraviolet on f32 data.
These benchmarks were performed on an [Intel i7-4710HQ] CPU on Linux. They were
compiled with the 1.49.0-nightly (ffa2e7ae8 2020-10-24) Rust compiler. Lower
(better) numbers are highlighted within a 2.5% range of the minimum for each
row.
The versions of the libraries tested were:
cgmath - 0.17.0euclid - 0.22.1glam - 0.10.0nalgebra - 0.23.0pathfinder_geometry - 0.5.1static-math - 0.1.7ultraviolet - 0.5.1vek - 0.12.0 (repr_c types)See the full [mathbench report] for more detailed results.
Run with the command:
cargo +nightly bench --features scalar scalar
| benchmark | glam | cgmath | nalgebra | euclid | vek | pathfinder | static-math | ultraviolet |
|---|---|---|---|---|---|---|---|---|
| euler 2d x10000 | 16.18 us | 16.3 us | 16.25 us | 16.23 us | 16.25 us | 10.42 us | 11.84 us | 16.28 us |
| euler 3d x10000 | 16.13 us | 32.09 us | 25.49 us | 32.2 us | 32.21 us | 16.23 us | 34.74 us | 32.07 us |
| matrix2 determinant | 2.0417 ns | 2.1235 ns | 2.1118 ns | N/A | 2.1132 ns | 2.1182 ns | 2.1173 ns | 2.1161 ns |
| matrix2 inverse | 2.8321 ns | 8.4686 ns | 7.6035 ns | N/A | N/A | 3.4420 ns | 8.3189 ns | 5.8985 ns |
| matrix2 mul matrix2 | 6.1247 ns | 4.8130 ns | 2.5461 ns | N/A | 11.6360 ns | 2.5541 ns | 4.7587 ns | 4.7334 ns |
| matrix2 mul vector2 x1 | 2.8408 ns | 2.6186 ns | 2.6343 ns | N/A | 5.4199 ns | 2.1706 ns | 2.6969 ns | 2.6153 ns |
| matrix2 mul vector2 x100 | 276.1011 ns | 237.2400 ns | 243.3239 ns | N/A | 545.8342 ns | 220.7986 ns | 264.6844 ns | 236.9462 ns |
| matrix2 return self | 2.8687 ns | 2.8712 ns | 2.8892 ns | N/A | 2.8857 ns | 2.4157 ns | 2.8777 ns | 2.9764 ns |
| matrix2 transpose | 2.2713 ns | 3.0883 ns | 2.2310 ns | N/A | 3.0914 ns | N/A | 3.0835 ns | 3.0775 ns |
| matrix3 determinant | 3.8307 ns | 3.7721 ns | 3.8148 ns | N/A | 3.8240 ns | N/A | 3.8223 ns | 8.9148 ns |
| matrix3 inverse | 15.2042 ns | 18.2388 ns | 12.7075 ns | N/A | N/A | N/A | 12.8133 ns | 22.1096 ns |
| matrix3 mul matrix3 | 10.4010 ns | 11.2899 ns | 10.3583 ns | N/A | 40.1530 ns | N/A | 10.1117 ns | 11.2713 ns |
| matrix3 mul vector3 x1 | 4.7889 ns | 4.4906 ns | 4.3330 ns | N/A | 13.2860 ns | N/A | 4.7966 ns | 4.4801 ns |
| matrix3 mul vector3 x100 | 0.5121 us | 0.4669 us | 0.4754 us | N/A | 1.348 us | N/A | 0.4767 us | 0.4728 us |
| matrix3 return self | 5.4364 ns | 5.4451 ns | 5.4552 ns | N/A | 5.4463 ns | N/A | 5.4450 ns | 5.4534 ns |
| matrix3 transpose | 10.0869 ns | 10.1385 ns | 10.0176 ns | N/A | 10.1395 ns | N/A | 10.8063 ns | 9.7977 ns |
| matrix4 determinant | 6.1510 ns | 11.6457 ns | 52.3414 ns | 17.0240 ns | 18.3800 ns | N/A | 16.9031 ns | 8.5125 ns |
| matrix4 inverse | 16.5764 ns | 47.0562 ns | 69.0789 ns | 65.0189 ns | 299.8796 ns | N/A | 52.1599 ns | 42.0630 ns |
| matrix4 mul matrix4 | 7.5811 ns | 26.6004 ns | 8.2055 ns | 11.5513 ns | 91.5766 ns | N/A | 21.0343 ns | 26.5077 ns |
| matrix4 mul vector4 x1 | 3.1131 ns | 6.8211 ns | 3.5017 ns | N/A | 23.9593 ns | N/A | 7.0599 ns | 6.8278 ns |
| matrix4 mul vector4 x100 | 0.6175 us | 0.768 us | 0.6271 us | N/A | 2.26 us | N/A | 0.8465 us | 0.7875 us |
| matrix4 return self | 7.3269 ns | 7.1310 ns | 7.3162 ns | N/A | 7.3160 ns | N/A | 7.2881 ns | 7.1189 ns |
| matrix4 transpose | 7.4352 ns | 12.0065 ns | 14.8833 ns | N/A | 11.8665 ns | N/A | 16.1124 ns | 12.6715 ns |
| ray-sphere intersection x10000 | 16.09 us | 16.12 us | 90.09 us | 16.06 us | 69.34 us | N/A | N/A | 16.12 us |
| rotation3 inverse | 2.2081 ns | 3.4053 ns | 7.6562 ns | 3.3040 ns | 3.3085 ns | N/A | 3.4015 ns | 3.2964 ns |
| rotation3 mul rotation3 | 3.3522 ns | 6.9520 ns | 7.0618 ns | 7.1581 ns | 7.0768 ns | N/A | 7.5341 ns | 7.0063 ns |
| rotation3 mul vector3 x1 | 6.5538 ns | 7.4976 ns | 7.9157 ns | 7.5374 ns | 17.6267 ns | N/A | 7.4405 ns | 8.6141 ns |
| rotation3 mul vector3 x100 | 0.6592 us | 0.7402 us | 0.7623 us | 0.7663 us | 1.786 us | N/A | 0.742 us | 0.8601 us |
| rotation3 return self | 2.8756 ns | 2.8689 ns | 2.8714 ns | N/A | 2.8778 ns | N/A | 2.8630 ns | 2.8725 ns |
| transform point2 x1 | 4.9832 ns | 2.8866 ns | 4.8093 ns | 2.8645 ns | 12.92 |
$ claude mcp add mathbench-rs \
-- python -m otcore.mcp_server <graph>