Home > Software design >  Why is accessing variables outside of the current thread slow?
Why is accessing variables outside of the current thread slow?

Time:10-24

I have the following code with two methods:

using System.Diagnostics;

internal class Program
{
    public static double SeqArraySum(double[] x)
    {
        var watch = new Stopwatch();
        watch.Start();
        double sum1 = 0;
        double sum2 = 0;

        for (int i = 0; i < x.Length / 2; i  )
            sum1  = x[i];

        for (int i = x.Length / 2; i < x.Length; i  )
            sum2  = x[i];

        double sum = sum1   sum2;
        watch.Stop();
        Console.WriteLine($"Seq Milliseconds: {watch.ElapsedMilliseconds}, sum:{sum}");
        return sum;
    }

    public static double ParArraySum(double[] x)
    {
        var watch = new Stopwatch();
        watch.Start();
        double sum1 = 0;
        double sum2 = 0;

        var t1 = new Thread(() =>
        {
            for (int i = 0; i < x.Length / 2; i  )
                sum1  = x[i];
        });
        t1.Start();

        for (int i = x.Length / 2; i < x.Length; i  )
            sum2  = x[i];

        t1.Join();
        double sum = sum1   sum2;
        watch.Stop();
        Console.WriteLine($"Par Milliseconds: {watch.ElapsedMilliseconds}, sum:{sum}");
        return sum;
    }

    static void Main()
    {
        var arr = new double[100000000];
        for (int i = 0; i < arr.Length; i  )
            arr[i] = i / 100000000000d;

        for(int i = 1; i <= 5; i  )
        {
            Console.WriteLine($"Attempt {i}");
            var resultSeq = SeqArraySum(arr);
            var resultPar = ParArraySum(arr);
        }
    }
}

If I run this program, I get the following results (.NET Core 6.0, Debugging Mode):

Attempt 1
Seq Milliseconds: 226, sum:49999,999499999976
Par Milliseconds: 448, sum:49999,999499999976
Attempt 2
Seq Milliseconds: 194, sum:49999,999499999976
Par Milliseconds: 409, sum:49999,999499999976
Attempt 3
Seq Milliseconds: 194, sum:49999,999499999976
Par Milliseconds: 463, sum:49999,999499999976
Attempt 4
Seq Milliseconds: 195, sum:49999,999499999976
Par Milliseconds: 460, sum:49999,999499999976
Attempt 5
Seq Milliseconds: 192, sum:49999,999499999976
Par Milliseconds: 457, sum:49999,999499999976

I would expect ParArraySum to run faster, but it isn't happening. But if replace

        var t1 = new Thread(() =>
        {
            for (int i = 0; i < x.Length / 2; i  )
                sum1  = x[i];
        });

with this:

        var t1 = new Thread(() =>
        {
            var tempSum = 0d;
            for (int i = 0; i < x.Length / 2; i  )
                tempSum  = x[i];
            sum1 = tempSum;
        });

I get the expected speedup.

Attempt 1
Seq Milliseconds: 217, sum:49999,999499999976
Par Milliseconds: 130, sum:49999,999499999976
Attempt 2
Seq Milliseconds: 241, sum:49999,999499999976
Par Milliseconds: 118, sum:49999,999499999976
Attempt 3
Seq Milliseconds: 199, sum:49999,999499999976
Par Milliseconds: 115, sum:49999,999499999976
Attempt 4
Seq Milliseconds: 191, sum:49999,999499999976
Par Milliseconds: 115, sum:49999,999499999976
Attempt 5
Seq Milliseconds: 192, sum:49999,999499999976
Par Milliseconds: 115, sum:49999,999499999976

Why is my original code so much slower? Is it possible to repeatedly access sum1 without the slowdown?

CodePudding user response:

By doing this:

double sum1 = 0;
var t1 = new Thread(() =>
{
    for (int i = 0; i < x.Length / 2; i  )
        sum1  = x[i];
});

You capture sum1 local variable in a closure. To make it work, compiler has to rewrite the code so that sum1 is no longer a regular local variable but instead is a field of compiler generated class, and instance of that class is then passed to the closure (the code which another thread runs).

This is important for perfomance in this case, because accessing "regular" local variable, stored on stack, is much more efficient in case of summing large array, and compiler can use various optimizations. Accessing a field of some instance over and over again is less efficient.

In this case:

var t1 = new Thread(() =>
{
    var tempSum = 0d;
    for (int i = 0; i < x.Length / 2; i  )
        tempSum  = x[i];
    sum1 = tempSum;
});

You create local variable inside the method you pass to new thread, so it will be "regular" local variable, and repeated access to it is efficient. You then only access captured variable sum1 only once at the end of the loop, which does not negatively affect perfomance.

  • Related