jeudi 26 septembre 2019

How to get random number in forked processes?

I'm forking few processes and I need each process to perform a task on a randomly selected items. I found that the forked processes pick the exact same random numbers. I tried generating seeds and call srand(), but it didn't help much. Actually, most documentation I've read suggest to avoid srand() "unless you know exactly what you're doing".

Here is my code:

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long qw(GetOptions);
use Time::HiRes qw(time);
use MongoDB;
#use Math::Random::Secure qw(rand);

my $num_clients = shift;
my $num_loops = 50_000_000;

sub test_forked_sub {
    my $sum=0;
    my $random_offset = rand(120);
    printf "time: %10.4f -  Random offset: %6.2f at pid: %s  \n", time(), $random_offset, $$;
    for (my $i=0; $i < $num_loops  ; $i++) {
        $sum += rand(120);
    }
}

my $nub_processes = 0;
for (my $client_id =0; $client_id < $num_clients; $client_id++ ) {
    if (my $pid = fork) {     #Parent
        $nub_processes++;
    }
    else {         # child
        die "cannot fork: $!" unless defined $pid;
        test_forked_sub();
        exit(0);
    }
}

while ($nub_processes){
    wait;
    $nub_processes--;
}

When I run it I get the exact same "random" number in each of the processes:

$ time ./test_rand_fork.pl 10
time: 1569510011.6891 -  Random offset:  46.64 at pid: 2091
time: 1569510011.6937 -  Random offset:  46.64 at pid: 2092
time: 1569510011.6987 -  Random offset:  46.64 at pid: 2093
time: 1569510011.7028 -  Random offset:  46.64 at pid: 2094
time: 1569510011.7070 -  Random offset:  46.64 at pid: 2095
time: 1569510011.7097 -  Random offset:  46.64 at pid: 2096
time: 1569510011.7144 -  Random offset:  46.64 at pid: 2097
time: 1569510011.7203 -  Random offset:  46.64 at pid: 2098
time: 1569510011.7230 -  Random offset:  46.64 at pid: 2099
time: 1569510011.7249 -  Random offset:  46.64 at pid: 2100

real    0m3.974s
user    0m32.955s
sys     0m1.444s

A possible but ugly solution, it not to fork. Instead, to run multiple instances from my shell as follows:

# shell processes
$ for i in `seq 1 10`; do  ./test_rand_fork.pl 1 &  done
time: 1569511908.7708 -  Random offset:   7.44 at pid: 4129
time: 1569511908.8070 -  Random offset:  19.50 at pid: 4131
time: 1569511908.8068 -  Random offset:  97.59 at pid: 4132
time: 1569511908.8073 -  Random offset:  14.51 at pid: 4133
time: 1569511908.8077 -  Random offset:  16.70 at pid: 4134
time: 1569511908.8080 -  Random offset: 108.63 at pid: 4138
time: 1569511908.8079 -  Random offset:  69.44 at pid: 4137
time: 1569511908.8080 -  Random offset:  83.25 at pid: 4136
time: 1569511908.8080 -  Random offset:  43.33 at pid: 4135
time: 1569511908.8203 -  Random offset:  33.82 at pid: 4139

As the "shell processes" is an ugly solution, I tried to use Math::Random::Secure. Here are the results I get randomness as needed but it is 50 times slower than the "shell processes" approach:

# using: Math::Random::Secure qw(rand);
$ time ./test_rand_fork.pl 10
time: 1569510036.9331 -  Random offset: 112.48 at pid: 2128
time: 1569510036.9470 -  Random offset:  47.15 at pid: 2129
time: 1569510036.9501 -  Random offset:  20.77 at pid: 2130
time: 1569510036.9517 -  Random offset:  40.98 at pid: 2131
time: 1569510036.9521 -  Random offset:  13.84 at pid: 2132
time: 1569510036.9538 -  Random offset:  20.43 at pid: 2133
time: 1569510036.9543 -  Random offset:  48.48 at pid: 2134
time: 1569510036.9563 -  Random offset: 109.29 at pid: 2135
time: 1569510036.9579 -  Random offset:  70.30 at pid: 2136
time: 1569510036.9601 -  Random offset:  24.31 at pid: 2137

real    3m17.251s
user    32m31.129s
sys     0m1.054s

The randomness I need is not for security purposes, I just need a good spread. Is there a way to generate a good-enough seed for the forked processes and still use the standard rand() or alternative faster way? or at least something that doesn't require installing additional library?




Aucun commentaire:

Enregistrer un commentaire