Search code examples
c#performancexnacomparison

Creating a new array, discluding an item is faster than creating a list and removing it?


I was desperately trying to find a faster way of creating an array without one of the items in it, opposed to creating a list of those objects and removing that item.

The reason being is that my array has like 5 items on average, less most times, and creating a list just to remove one item that I no longer wanted in there, seemed way too much to me.

Since I coded my own profiler a while back, I decided to put that method, and another one I thought of, under a test, and heck are the results significant.

int[] a1 = new int[] { 1, 2, 3 };

Method 1 (creating a list of the objects and removing the 2nd item):

 Profiler.Start("Method 1");
 List<int> a2 = new List<int>(a1);
 a2.RemoveAt(1);
 Profiler.Stop("Method 1");

Method 2 (creating an array of the same size but one, and discluding the 2nd item):

 Profiler.Start("Method 2");
 int[] a3 = new int[a1.Length - 1];
 int l = 0;
 for (int i = 0; i < a1.Length; i++) if (a1[i] != 2) { a3[l] = a1[i]; l++; }
 Profiler.Stop("Method 2");

Profiler results:

https://i.sstatic.net/al8i6.png

The question Why is the difference in performance so significant?

I never actually took the time to study the differences between an array and a list.


Solution

  • It seems that the overhead is coming from the initialisation for the contents of the list rather than from the call to List.RemoveAt().

    In other words, the line List<int> a2 = new List<int>(a1); is dominating the time.

    Here's some code that attempts to eliminate the time taken for List.RemoveAt() and Array.Copy().

    The results from a release build on my PC are:

    LIST Elapsed: 00:00:00.4724841
    ARRAY Elapsed: 00:00:00.4670488
    LIST Elapsed: 00:00:00.4714016
    ARRAY Elapsed: 00:00:00.4675552
    LIST Elapsed: 00:00:00.4703538
    ARRAY Elapsed: 00:00:00.4698310
    

    which shows the times to be fairly similar.

    The test program is:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    
    namespace Demo
    {
        public static class Program
        {
            public static void Main()
            {
                int[] a1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
    
                int trials = 3;
                int iterations = 10000000;
    
                Stopwatch sw = new Stopwatch();
    
                for (int i = 0; i < trials; ++i)
                {
                    for (int j = 0; j < iterations; ++j)
                    {
                        List<int> a2 = new List<int>(a1);
    
                        sw.Start();
                        a2.RemoveAt(1);
                        sw.Stop();
                    }
    
                    Console.WriteLine("LIST Elapsed: " + sw.Elapsed);
                    sw.Reset();
    
                    for (int j = 0; j < iterations; ++j)
                    {
                        int[] a3 = new int[a1.Length - 1];
                        int l = 0;
    
                        sw.Start();
    
                        for (int k = 0; k < a1.Length; k++)
                        {
                            if (a1[k] != 2)
                            {
                                a3[l] = a1[k];
                                l++;
                            }
                        }
    
                        sw.Stop();
                    }
    
                    Console.WriteLine("ARRAY Elapsed: " + sw.Elapsed);
                    sw.Reset();
                }
            }
        }
    }