vendredi 10 mars 2023

RNG with Parallel and Thread safe goes magic after refactor

First of all, please be patient with this question because I'm very very new at these scenarios, I don't fully understand the work behind this and I'm asking in order to learn.

I feel I'm trapped in the third law of Arthur Clarke: Any sufficiently advanced technology is indistinguishable from magic.

My scenario:

I'm working with 1 billions of simulations to predict some behavior that not come in case. I made a functional mockup of logic that with some improvements with Parallel.For runs 4500 simulations per second and CPU at 5%.

Then after a deep search in S.O. I found some thing to change and for my surprise, I get 150000 sims/sec with CPU at ~25% with high notes.

Because the change I did is in a fundamental RNG method (previous used locked) and the new uses... magic?

One thing that call my attention is that the ratio, instead of being slower or fixed after some minutes (like the old method) is highly incremental until the end.

My Question:

What I did is really an improvement OR I just wasted my random number generation and now I'm getting repeated values? Or viceversa? It doesn't seems to be repeated values in neither case because I'm getting 700k different values (of course with repetitions of them) because I'm looking for probability of results in my main algorithm. But doesn't seems to be the repeated random values at all.

The results are very different in both ways, but I can't say that the first one was right. The new one seems to be a little more precise at big scale (128 permutations) but the old one seems to be far more precise at micro scale (8192 permutations)

Wich one is right? There are both wrong? Why?

My code

I didn't refactor the For but for the health of the question I'll show it:

                var lockTarget = new object();
                int counter = 1;
                ParallelOptions options = new() { MaxDegreeOfParallelism = Environment.ProcessorCount };
                   Parallel.For(1, totalSIM, options, i => {
                        /* do some stuff with the RNG inside */ 
                        int current = Interlocked.Increment(ref counter);
                        if (current % 500 == 0)
                           {
                             /* just for console report */
                               lock (options) _printConcurrent(++current);
                           }
                    });

The code that call the RNG contains (doesn't change neither):

        bool[] count_sides= new bool[3];
        count_sides[0] = GetRandomSide();
        count_sides[1] = GetRandomSide();
        count_sides[2] = GetRandomSide();

        return count_sides.Count(c => c) 

My old RNG:


at class level:

private static Random seedGenerator = new Random(Environment.TickCount);

and a method inside the class:

    private static bool GetRandomSide()
    {
        int seed;
        lock (locker)
        {
            seed = seedGenerator.Next(int.MinValue, int.MaxValue);
        }
        var random = new Random(seed);
        return random.NextDouble() < 0.5;
    }

My new RNG:


From: https://stackoverflow.com/a/57962385/888472

I replace the same method with:

    private static bool GetRandomSide() {
        return ThreadSafeRandom.Next(0, 2) == 1;
    }

and then just use the answer:

 public static class ThreadSafeRandom
    {
        private static readonly System.Random GlobalRandom = new Random();
        private static readonly ThreadLocal<Random> LocalRandom = new ThreadLocal<Random>(() =>
        {
            lock (GlobalRandom)
            {
                return new Random(GlobalRandom.Next());
            }
        });

        public static int Next(int min = 0, int max = Int32.MaxValue)
        {
            return LocalRandom.Value.Next(min, max);
        }
    }

Thanks for reading and sorry for my english and my little knowledge about this.




Aucun commentaire:

Enregistrer un commentaire