C++ Header-Only Library and Applications for Fast Random Number Generators
- SSE4.1-Vectorized Pseudorandom Number Generation
- AVX2-Vectorized Pseudorandom Number Generation
- Scalar Pseudorandom Number Generation
- Fast Uniform Distribution
- Simple Interface and Modern API
- Different PRNGs: MT19937, Xoroshiro128+, MSWS
master | |||
Current |
- Markus Pawellek "lyrahgames" (lyrahgames@mailbox.org)
- Standard: C++17
- Build System: build2 | CMake
- Intel Processor for Vectorized Components
- Operating System: Linux | Window | MacOS
- Compiler: GCC | Clang | Intel | MinGW | MSVC
Add the following entry to the repositories.manifest
file of your build2 package.
:
role: prerequisite
location: https://github.com/lyrahgames/pxart.git
Instead of the GitHub repository you can also use the official pkg.cppget.org
package repositories.
Optionally, add a trusted key.
:
role: prerequisite
location: https://pkg.cppget.org/1/alpha
Furthermore, add the following dependency entry to the manifest
file.
Here, you are allowed to specify the version range.
depends: pxart ^0.1.0
Now, import the library in the according buildfile
and link it to your target by putting it in the prerequisites.
import pxart_lib = pxart%lib{pxart}
Create a build2 configuration for packages if it does not exist already. Define a valid installation path which can be found by the compiler. Use specific options, such as to state the compiler with its flags, if necessary.
bpkg -d build2-packages cc \
config.install.root=/usr/local \
config.install.sudo=sudo
Get the latest package release and build it.
bpkg build https://github.com/lyrahgames/pxart.git
Install the built package.
bpkg install pxart
For uninstalling, do the following.
bpkg uninstall pxart
Because the library consists only of header files, the following can be omitted.
But it is recommended to do it otherwise, such that all dependencies are stated explicitly.
In the appropriate buildfile
, import the library by the following code and put the variable into the prerequisites of your target.
import pxart_lib = pxart%lib{pxart}
If you are using a manifest
file, you can state pxart
as a requirement.
So build2 will try to find the appropriate pkg-config
file in the standard paths when importing pxart in a buildfile.
requires: pxart
Alternatively, if your package uses an explicit depends: pxart
make sure to initialize this dependency as a system dependency when creating a new configuration.
bdep init -C @build cc config.cxx=g++ "config.cxx.coptions=-O3" -- "?sys:pxart/*"
Download the repository and create a configuration.
git clone https://github.com/lyrahgames/pxart.git
mkdir pxart-cmake-build
cd pxart-cmake-build
cmake ../pxart
Optionally, build and run the tests.
cmake --build .
ctest --verbose
Install the library and the CMake package.
sudo cmake --build . --target install
To uninstall the library do the following.
sudo cmake --build . --target uninstall
Because the library consists only of header files, the following can be omitted.
But it is recommended to do it otherwise, such that all dependencies are stated explicitly.
In the appropriate CMakeLists.txt
file, use find_package(pxart)
to find the library and link with the imported target pxart::pxart
by using target_link_libraries
.
The following code shows an example.
find_package(pxart REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE pxart::pxart)
Download the repository and create a configuration.
git clone https://github.com/lyrahgames/pxart.git
mkdir pxart-cmake-build
cd pxart-cmake-build
cmake ../pxart
Through the standard CMake package export you can externally use the library from the build tree.
In the appropriate CMakeLists.txt
file, use find_package(pxart)
to find the library and link with the imported target pxart::pxart
by using target_link_libraries
.
The following code shows an example.
find_package(pxart REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE pxart::pxart)
pXart explicitly supports build2 and CMake. If you would like to use another build system or no build system at all, you will need to directly set a standard include path for the pXart library by using the specific compiler flags.
Go into your project folder.
Optionally, go into the specific folder of your project for external source code.
Copy the folder pxart/pxart
of the git repository into the current directory.
If you are using no build system, make sure to add the current directory to the standard include paths of your compiler by using a flag.
In CMake, you can do this by using include_directories
or target_include_directories
.
In build2, use the configuration variable cxx.poptions
in your buildfile
and add the specific include flag.
#include <iostream>
//
#include <pxart/pxart.hpp>
int main(){
constexpr int samples = 100'000'000;
int result = 0;
pxart::mt19937 rng{};
for (auto i = samples; i > 0; --i){
const auto x = pxart::uniform<float>(rng);
const auto y = pxart::uniform<float>(rng);
result += (x*x + y*y <= 1);
}
std::cout << "pi = " << 4.0f * result / samples << "\n";
}
The following code has to be compiled with AVX2 support.
For GCC and Clang, the easiest method may be to use compilation flags -O3
and -march=native
.
#include <iostream>
//
#include <pxart/pxart.hpp>
int main() {
const int samples = 10'000'000;
pxart::simd256::mt19937 rng{};
// Initialize vectorized buffer to count the samples that lie inside the
// circle for every component.
auto samples_in_circle = _mm256_setzero_si256();
// Iterate over all samples by using the length of the SIMD register.
for (auto i = samples; i > 0; i -= 8) {
// Generate vectorized samples by using pxart.
const auto x = pxart::simd256::uniform<float>(rng);
const auto y = pxart::simd256::uniform<float>(rng);
// Test if samples lie inside the circle.
const auto radius = _mm256_add_ps(_mm256_mul_ps(x, x), _mm256_mul_ps(y, y));
const auto mask = _mm256_castps_si256(
_mm256_cmp_ps(radius, _mm256_set1_ps(1.0f), _CMP_LE_OQ));
// Add mask to the samples inside the circle for every component.
samples_in_circle = _mm256_add_epi32(
samples_in_circle, _mm256_and_si256(_mm256_set1_epi32(1), mask));
}
// Sum up all components and scale to estimate pi.
samples_in_circle = _mm256_hadd_epi32(samples_in_circle, samples_in_circle);
samples_in_circle = _mm256_hadd_epi32(samples_in_circle, samples_in_circle);
const auto pi = 4.0f *
(reinterpret_cast<uint32_t*>(&samples_in_circle)[0] +
reinterpret_cast<uint32_t*>(&samples_in_circle)[4]) /
samples;
std::cout << "pi = " << pi << '\n';
}
By using build2, pxart can be easily tested with multiple configurations.
For this, create a new folder pxart
in your favorite project folder, like ~/projects
.
mkdir pxart
We call this one the developer folder, because it will contain the actual Git repository of the code as well as all the configurations the tests will be built for. Go into this folder and clone this repository.
cd pxart
git clone https://github.com/lyrahgames/pxart
We call this one the repository folder, the source folder, or the project folder. Change the directory into the source folder and create all the configurations that you would like to test for. In this example, we will create two configurations for the default GCC and Clang compiler with maximal optimization enabled.
cd pxart
bdep init -C @gcc cc config.cxx=g++ "config.cxx.coptions=-03 -march=native" config.install.root=../install
bdep init -C @clang cc config.cxx=clang++ "config.cxx.coptions=-03 -march=native"
These commands have create two configurations, namely @gcc
and @clang
, with their respective folders pxart-gcc
and pxart-clang
in the developer folder.
The configuration @gcc
is the default configuration and we have enabled local installation support for the folder named install
inside the developer folder to be able to test the installation process.
The folder structure should now look like the following.
pxart/ # Developer Folder
│
├── pxart/... # Project Folder
│
├── pxart-gcc/... # Configuration Folder 'gcc'
│
└── pxart-clang/... # Configuration Folder 'clang'
To build all the packages of the project for all initialized configurations, run the following command.
bdep update -a
Furthermore, we would like to create symbolic links to the executable for the default configuration. For this, we have to run the build system of build2 directly.
b
To test the installation run the build system with the appropriate target.
b install
Your developer folder should now contain a folder, called install
, with all installed packages.
To only have a look at the library installation itself, uninstall all files and go into the library package inside the project folder and run the installation command again.
b uninstall
cd pxart
b install
The folder structure of install
should look similar to the following.
install/
├── include
│ └── pxart/... # pXart Header Files
├── lib
│ └── pkgconfig
│ ├── libpxart.pc
│ ├── libpxart.shared.pc
│ └── libpxart.static.pc
└── share
└── doc
└── pxart
├── LICENSE
├── manifest
└── README.md
To run all the unit tests for all configurations, go into the tests
package of the project folder and run the tests.
cd tests
bdep test -a
Running the benchmarks for all configurations at the same time seems to be inconvenient.
Therefore use the name to specify the configuration.
To run the benchmarks, we use perfevent
which calls perf
in C++.
We first have to allow the paranoid mode.
sudo sysctl -w kernel.perf_event_paranoid=-1
Then go into the benchmarks
package of the project folder and execute the benchmarks by using the build2.
bdep test @gcc
There is more than one example package.
Typically, the executables of the examples have to be called manually.
For the basics
example, the test
command can still be used.
cd examples/basics
bdep test @gcc
To run the photon propagation example in default configuration, do the following.
cd examples/photon_propagation
photons/photons
The output should look like the following.