Intel® C++ Compiler Classic Developer Guide and Reference

ID 767249
Date 3/31/2023
Public

A newer version of this document is available. Customers should click here to go to the newest version.

Document Table of Contents

Usage Model

A typical usage model for using the intrinsics in the Short Vector Random Number Generator (SVRNG) library is the same as for standard C++ or Intel® oneAPI Math Kernel Library (oneMKL) vector statistics random number generator and looks something like the following:

  • Include svrng.h header file
  • Create and initialize basic SVRNG generator engine, create and initialize distribution (if necessary).
  • Call one or more SVRNG generation function.
  • Process the output.
  • Delete the SVRNG engines and distributions.

On Windows*, users will need to explicitly link the static or dynamic libraries: static: libirng.lib, dynamic: libirngmd.lib. On Linux* and macOS the compiler driver will link automatically.

The following example demonstrates generation of a random stream that is output of basic generator engine MT19937 with seed equal to 777. The engine is used to generate two arrays: 1024 uniformly distributed random numbers between <a = 0.0, b = 4.0> via scalar generator call which should be vectorized by the compiler and 1024 normally distributed with parameters <mean = 2.0, standard deviation = 1.0> random numbers in blocks by 16 elements via direct call of SIMD-vector implementation. Delete engines and distributions after completing the generation. Check status for possible errors happened. The purpose of the example is to calculate the sample mean for both distributions with the given parameters.

#include <stdio.h>
#include <svrng.h>

int main( void )
{
  int                   i, st = SVRNG_STATUS_OK;
  double                res1[1024],  res2[1024];
  double                sum1 = 0, sum2 = 0;
  double                mean1, mean2;
  svrng_engine_t        engine;
  svrng_distribution_t  distr1, distr2;

    /* Create mt19937 engine */
    engine = svrng_new_mt19937_engine( 777 );

    /* Create uniform distribution */
    distr1 = svrng_new_uniform_distribution_double( 0.0, 4.0 );

    /* Create normal distribution */
    distr2 = svrng_new_normal_distribution_double( 2.0, 1.0 );

    /* Scalar generator call, can be vectorized by compiler */
    #pragma ivdep
    #pragma vector always
    for( i = 0; i < 1024; i ++ ) {
        res1[i] = svrng_generate_double( engine, distr1 );
    }

    /* Direct call to SIMD-vector implementation */
    /* generating 16 packed elements */
    for( i = 0; i < 1024; i += 16 ) {
        *((svrng_double16_t*)(&res2[i])) = 
        svrng_generate16_double( engine, distr2 );
    }

    /* Compute mean values */
    for( i = 0; i < 1024; i++ ) {
        sum1 += res1[i];
        sum2 += res2[i];
    }

    mean1 = sum1 / 1024.0;
    mean2 = sum2 / 1024.0;

    /* Printing results */
    printf( "Sample mean of uniform distribution = %f\n", mean1 );
    printf( "Sample mean of normal  distribution = %f\n", mean2 );

    /* Check for resulted status */
    st = svrng_get_status();

    if(st != SVRNG_STATUS_OK) {
        printf("FAILED: status error %d returned\n", st);
    }

    /* Delete distributions */    
    svrng_delete_distribution( distr1 );
    svrng_delete_distribution( distr2 );

    /* Delete engine */
    svrng_delete_engine( engine );

return st;
}

Another example demonstrates the "skip-ahead" technique which ensures identical random number sequences in cases of parallel and sequential generation for certain engines. The rand0 engine is being created and copied to T "threads" with the "skip-ahead" adjustments applied. Each "thread" generates N uniformly distributed unsigned integer random values and then all LEN=T*N numbers are compared to the sequential call:

#include <stdio.h>
#include <stdint.h>
#include <svrng.h>

#define LEN        1024
#define T          8
#define N          (LEN/T)

int main( void ) {
  uint32_t       seq_res[LEN+32], parallel_res[LEN+32];
  svrng_engine_t seq_engine;
  svrng_engine_t parallel_engine[T];
  int            l, n, t, errs = 0, st = SVRNG_STATUS_OK;

    /* Create sequential engine and distr */
    seq_engine = svrng_new_rand0_engine( 777 );

    /* Copy existing sequential engine to new T parallel ones */
    /* with t*N offsets using skipahead method */
    for( t = 0; t < T; t++ ) { 
        int thr_offset = t*N;
        parallel_engine[t] = svrng_copy_engine( seq_engine );
        parallel_engine[t] = \
          svrng_skipahead_engine( parallel_engine[t], thr_offset );
    }

    /* Sequential loop using scalar function (can be vectorized) */
    #pragma ivdep
    #pragma vector always
    for( l = 0; l < LEN; l++ ) {
        seq_res[l] = svrng_generate_uint( seq_engine );
    }

    /* Parallel loop using SIMD-vector function, */ 
    /* may be spreaded by threads */
    for( t = 0; t < T; t++ ) {
        for( n = 0; n < N; n += 8 ) {
            *((svrng_uint8_t*)(&(parallel_res[t*N+n]))) = \
            svrng_generate8_uint( parallel_engine[t] );
        } 
    }

    /* Compare seq and parallel results */
    for(l = 0; l < LEN; l++) {
        if( parallel_res[l] != seq_res[l]) {
            errs++;
        }
    }

    /* Check for resulted status */
    st = svrng_get_status();

    /* Print overall result */
    if(st != SVRNG_STATUS_OK) {
        printf("FAILED: status error %d returned\n", st);
    }
    else if(errs) {
        printf("FAILED: %d skipahead errors\n",  errs);
    }
    else {
        printf("PASSED\n");
    }

    /* Delete engines */
    svrng_delete_engine( seq_engine );
    for( t = 0; t < T; t++ ) {
        svrng_delete_engine(parallel_engine[t]);
    }

  return (errs-st);
}