Do C# collections care about cache friendlyness?

600 views Asked by At

I've been running a lot of tests comparing an array of structs with an array of classes and a list of classes. Here's the test I've been running:

struct AStruct {
    public int val;
}
class AClass {
    public int val;
}

static void TestCacheCoherence() 
{
    int num = 10000;
    int iterations = 1000;
    int padding = 64;

    List<Object> paddingL = new List<Object>();

    AStruct[] structArray = new AStruct[num];
    AClass[] classArray = new AClass[num];
    List<AClass> classList = new List<AClass>();

    for(int i=0;i<num;i++){
        classArray[i] = new AClass();
        if(padding >0) paddingL.Add(new byte[padding]);
    }
    for (int i = 0; i < num; i++)
    {
        classList.Add(new AClass());
        if (padding > 0) paddingL.Add(new byte[padding]);
    }

    Console.WriteLine("\n");
    stopwatch("StructArray", iterations, () =>
    {
        for (int i = 0; i < num; i++)
        {
            structArray[i].val *= 3;
        }
    });
    stopwatch("ClassArray ", iterations, () =>
    {
        for (int i = 0; i < num; i++)
        {
            classArray[i].val *= 3;
        }
    });
    stopwatch("ClassList  ", iterations, () =>
    {
        for (int i = 0; i < num; i++)
        {
            classList[i].val *= 3;
        }
    });

}

static Stopwatch watch = new Stopwatch();

public static long stopwatch(string msg, int iterations, Action c)
{
    watch.Restart();
    for (int i = 0; i < iterations; i++)
    {
        c();
    }
    watch.Stop();

    Console.WriteLine(msg +":  " + watch.ElapsedTicks);
    return watch.ElapsedTicks;
}

I'm running this in release mode with the following:

 Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); // Use only the second core 
 Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
 Thread.CurrentThread.Priority = ThreadPriority.Highest;

RESULTS:

With padding=0 I get:

StructArray: 21517
ClassArray:  42637
ClassList:   80679

With padding=64 I get:

 StructArray: 21871
 ClassArray:  82139
 ClassList:   105309

With padding=128 I get:

 StructArray: 21694
 ClassArray:  76455
 ClassList:   107330

I am a bit confused with these results, since I was expecting the difference to be bigger. After all the structures are tiny and are laid one after the other in memory, while the classes are separated by up to 128 bytes of garbage.

Does this mean that I shouldn't even worry about cache friendlyness? Or is my test flawed?

1

There are 1 answers

2
Chris Shain On

There are a number of things going on here. The first is that your tests don't take GC's into account- it is distinctly possible that the arrays are being GC'd during the loop over the list (because the arrays are no longer used while you are iterating the list, they are eligible for collection).

The second is that you need to keep in mind that List<T> is backed by an array anyway. The only reading overhead is the additional function calls to go through List.