views:

42

answers:

2

Hi,

I'm working on some applications that require very low latency and push a lot of memory and was doing some testing of how e.g. allocating a list ad-hoc vs. pre-allocating and clearing a list performs. I was expecting the test runs that pre-allocate the memory to perform a lot faster but to my surprise they're actually slightly slower (when I let the test run for 10 minutes, the avg. difference is about 400ms).

Here is the test code that I used:

    class Program
{
    private static byte[] buffer = new byte[50];
    private static List<byte[]> preAlloctedList = new List<byte[]>(500);

    static void Main(string[] args)
    {
        for (int k = 0; k < 5; k++)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            for (int i = 0; i < 1000000; i++)
            {
                List<byte[]> list = new List<byte[]>(300);

                for (int j = 0; j < 300; j++)
                {
                    list.Add(buffer);
                }
            }

            sw.Stop();
            Console.WriteLine("#1: " + sw.Elapsed);
            sw.Reset();
            sw.Start();

            for (int i = 0; i < 1000000; i++)
            {
                for (int j = 0; j < 300; j++)
                {
                    preAlloctedList.Add(buffer);
                }

                preAlloctedList.Clear();
            }
            sw.Stop();
            Console.WriteLine("#2: " + sw.Elapsed);
        }

        Console.ReadLine();
    }
}

Now, what's really interesting, I was running perfmon side by side and saw the following pattern which looks like I expected:

Green = Gen 0 collections
Blue = Allocated Bytes/sec
Red = %Time in GC

The console application below shows the test runtimes for #1 and #2
alt text

So, my question is, why is Test #1 faster than #2?
Obviously, I'd rather have the perfmon statistics of Test #2 in my app as there is basically no memory pressure, no GC collections, etc. yet #1 seems to be slightly faster?
Does List.Clear() carry that much overhead?

Thanks,

Tom

EDIT I did another test, with the same setup but running the app with server GC enabled, now #2 becomes slightly faster alt text

+3  A: 

I suspect the reason Test #1 is faster is that the garbage collection is happening on a separate thread, and the overhead of allocation is lower than the extra List<T>.Clear call. Since none of these lists are large (just 300 references each), and they're all being created and unrooted in a tight loop, they'll all typically stay in Gen 0.

I've noticed this during profiling in the past - reusing a List<T> and calling Clear on it is often slower than just reallocating. Clear() actually clears the internal array as well as resets the parameters of the list, which I believe has (slightly) more overhead than the initial allocation of the list.

However, this example, in my opinion, really just shows that the GC in .NET is very, very efficient.

Reed Copsey
Thanks, for your reply, this makes sense. Which option would you pick though in a production scenario that is very similar? Now, what's very interesting though, when I run the same app with Server GC enabled, #2 actually becomes faster. I updated the post and attached a screenshot
Tom Frey
@Tom: Server mode makes the overall throughput faster at the cost of higher latency. Which is better is up to you. That being said, I typically just use #1 in my production environment - the code is cleaner, and it tends to perform better overall.
Reed Copsey
+2  A: 

Does List.Clear() carry that much overhead?

Yes, compared to a (single) GC.Collect(0) , doing a few thousand calls to Clear() might very well be slower.

For what I have learned, the dotNet memory system is very fast in allocating/deallocating shortlived memory blocks.

But be careful to carry conclusions from this simple test over to your real app.

Henk Holterman
I looked at the List<T> implementation and Clear() makes an extern call. Do you happen to know what that achieves? I could make a List<T> implementation with Clear() only resetting the size field
Tom Frey
nevermind, this is what Clear does: Sets a range of elements in the Array to zero, to false, or to null, depending on the element type.
Tom Frey
@Tom: You have to clear the array, or you'll leave all of the elements rooted, so the GC can't collect them. In general, I'd try to avoid reinventing the wheel like this...
Reed Copsey